首页 > 代码库 > 使用反射的四种方法

使用反射的四种方法

反射是.net提供的用来访问类或者类里面的内容的一项技术,它允许你在编译时对一个类基本上一无所知的情况下对一个类进行访问,支持利用一个字符串对类进行发现、访问、调用等,以下利用实例介绍四种使用反射的方法

首先祭出一个类,用于模拟被调用的类

public class TestClass 
    {
        private string testType;

        public string TestType
        {
            get { return testType; }
            set { testType = value; }
        }
        private string testType2;
        public string TestType2
        {
            get { return testType2; }
            set { testType2 = value; }
        }
        public TestClass()
        {
            testType2 = "test";
        }
        public int test1()
        {
            int i = 1;
            i++;
            return i;
        }
        public void testsomething()
        {
            int i = 0;
        }
    }
以上代码仅供测试,一般情况下估计没人会写出这样的代码,莫要鄙视,为了模拟反射的调用,我把这个类封装在一个叫testlibrary.dll的类库里面。

接着先把这四种方法的代码列出来先,之后注意解释

        delegate void delegatetest(Type t);

        private const BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
//方法1
        private void TestInvoke1(Type t)
        {
            object obj = t.InvokeMember(null, flags | BindingFlags.CreateInstance, null, null, null);
            Console.WriteLine(obj.GetType().ToString());
            string testresult= t.InvokeMember("testType2", flags | BindingFlags.GetField, null, obj, null).ToString();
            Console.WriteLine(testresult);
            t.InvokeMember("testType2", flags | BindingFlags.SetField, null, obj,new object[]{"草泥马"});
            testresult= t.InvokeMember("testType2", flags | BindingFlags.GetField, null, obj, null).ToString();
            Console.WriteLine(testresult);
            testresult = t.InvokeMember("test1", BindingFlags.InvokeMethod, null, obj, null).ToString();
            Console.WriteLine(testresult);
        }
//方法2
        private void TestInvoke2(Type t)
        {
            ConstructorInfo ctor = t.GetConstructor(new Type[] { });
            object obj = ctor.Invoke(new object[] { });
            FieldInfo fi = t.GetField("testType", flags);
            fi.SetValue(obj, "尼玛");
            Console.WriteLine(fi.GetValue(obj));
            MethodInfo mi = t.GetMethod("test1", flags);
            string result = mi.Invoke(obj, null).ToString();
            Console.WriteLine(result);
        }
//方法3
        delegate int testdelegate();
        private void TestInvoke3(Type t)
        {
            object obj = Activator.CreateInstance(t);
            MethodInfo mi = t.GetMethod("test1", flags);
            var test1 = (testdelegate)(mi.CreateDelegate(typeof(testdelegate), obj));
            int i = test1();
            Console.WriteLine(i.ToString());
            
            PropertyInfo pi = t.GetProperty("TestType", flags);
            var SetTestType = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), obj, pi.GetSetMethod());
            try
            {
                SetTestType("尼玛");
            }
            catch
            {
                Console.WriteLine("无法写入!");
            }
            var GetTestType = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>), obj, pi.GetGetMethod());
            Console.WriteLine("获取结果:" + GetTestType());
        }
//方法4
        private void TestInvoke4(Type t)
        {
            dynamic obj = Activator.CreateInstance(t);
            try
            {
                obj.testType = "泥煤";
                Console.WriteLine(obj.testType.ToString());
            }
            catch
            {
                Console.WriteLine("私有字段不能读取或写入");
            }
            try
            {

                obj.TestType = "泥煤2";
                Console.WriteLine(obj.TestType);
            }
            catch
            {
                Console.WriteLine("私有属性不能读取或写入");
            }
            try
            {
                Func<int> test = ()=>obj.test1();
                Console.WriteLine(test());
            }
            catch
            {
                Console.WriteLine("私有方法不能读取或写入");
            }
        }
首先观察一下这四个方法传入的参数,都有一个type类型的参数,这个参数获取的是利用反射想要访问的类的类型,这个先搁着,先介绍获得这个类型之后如何对这个类型的类进行访问。

首先,方法1,利用最直接的方式,但是反射是一种比较消耗性能的调用方式,方法1每调用一次方法或属性都会进行一次绑定,对性能的消耗比较大,速度也比较慢,首先利用Type的 InvokeMember方法调用t类型的构造方法构造一个实例,但因为我们在运行过程中并不知道它是什么类型的,所以只能将它赋给一个object。InvokeMember的第一个参数代表要访问的内容的名字,如一个属性或一个方法的名字,因为是访问构造方法,我们传入空值null。第二个参数是控制绑定执行的标志,指定要绑定那些类型,用|符号隔开,如例子里面的flag,定义的DeclaredOnly指定反射不考虑继承类型,NonPublic指定反射非公开成员等等,所以反射在数据没有做特殊处理的情况下是可以访问私有成员的,第四个参数为绑定的实例,调用构造方法时传入空值即可,调用属性或字段或方法需传入由构造方法返回的object实例,最后一个参数是要传入的参数,用object[]类型传入。了解了这些,就基本能够用InvokeMember方法调用一个未知类里面的各种类型了,实例中给出了调用字段以及方法的示例,而且注意,访问的testType2是一个私有字段,但是也依然能够访问得到。


接着,方法2,,方法2提供了一种绑定一种属性,多次调用的方式,当多个实例需要重复调用同一个方法是,这个方法能够提供更好的性能,因为所有的操作只绑定了一次,这一次,使用了另外一个调用构造方法的方式,调用Type 类型的GetConstructor方法,这个方法会根据传入的参数指定的参数类型,搜索程序集里面匹配的构造方法并且返回一个ConstructorInfo实例,这个实例提供对构造函数的访问权,上面方法2我们传入了一个空的Type[]数组,那么这个方法返回的实例就会提供对无参构造方法的访问权,接着调用这个实例的invoke方法并且传入参数即可获得类型的实例,同样是一个object。接着我们通过Type类型的GetField和GetMethod方法获取对特定字段和方法的访问权,之后便能对相应的方法和字段进行访问,同理,可以通过GetProperty方法获取对属性的访问权。


方法3提供了一种利用委托访问类型的方法,这种方法适合需要重复调用同一个实例里面的相同的成员,当多次调用同一个对象里面的同一个成员时此方法的性能要优于方法2提供的方法。依然是构造一个实例,接着获取对成员的访问权,但这次是构造一个委托,并将这个委托指向这个成员,但是这个方法不提供对字段的访问方式,对属性的访问则通过构造一个委托指向属性的get访问器或set访问器,达到对属性访问的目的。


方法4提供了一种利用dynamic 对类进行访问的方法,之前我们构造完实例之后都是返回一个object,但这次我们将返回值赋给一个dynamic,这个类型将会在运行时才进行解析,并不会在编译时进行解析,用这种方式调用主要是易于理解,直观,并且它也提供了不错的性能,但有个缺点是,由于不在编译的时候进行解析,所以就无法利用vs强大的智能提示功能,编译的时候也不会报错,真正实现纯手打,可能你会主要到里面用了很多try catch,主要就是因为用这种方式有可能访问到不存在的成员或者私有成员,这个时候错误就会被捕捉到,这个也是必须的。

好了,简单说明了这四种方法,接下来说一下如何发现类成员,并且获取到类的类型。

Assembly a = Assembly.LoadFrom("testlibrary.dll");
            foreach (Type t in a.GetExportedTypes())
            {
                if (t.Name.ToString() == "TestClass")
                {
                    Console.WriteLine(t.FullName);
                    object o = Activator.CreateInstance(t);
                    Console.WriteLine(o.GetType());
                    foreach (MemberInfo info in t.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance))
                    {
                        Console.WriteLine(info);
                    }

                    TestInvoke1(t);
                 }  
              }
上述代码提供了发现类成员的方法利用Assembly.LoadFrom方法加载我们之前生成的程序集,GetExportedTypes方法获取所有的公共类型,便可通过获取的类型来获取这个类里面的成员的类型,而我们此时只需要的是类的类型,获取之后传入相应的方法中即可。具体的过程可参考示例代码,都比较简单,就不多啰嗦了。


使用反射的四种方法