C# 反射(Reflection)

C# 反射(Reflection)

概念:

反射指程序可以访问、检测和修改它本身状态或行为的一种能力。

程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。

我的理解是,反射的核心就是使用各种类型(Type)相关的 API。

public class MyClass
{

}

public class Test04 : MonoBehaviour
{
    void Start()
    {
        var type = typeof(MyClass);
        //程序集
        Debug.Log(type.Assembly);
        //模块
        Debug.Log(type.Module);
        //名称
        Debug.Log(type.Name);
    }

}

优点:

  • 1、反射提高了程序的灵活性和扩展性。
  • 2、降低耦合性,提高自适应能力。
  • 3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点:

  • 1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
  • 2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

Type 对象

上面说道反射其实就是利用一些type的API那就介绍一下我们通过API可以实现那些操作。

namespace Q {
    public class MyClass
    {

    }
}
public class Test04 : MonoBehaviour
{
    void Start()
    {

        var type = new MyClass().GetType();
        //命名空间+名字
        Debug.LogFormat("FullName:{0}", type.FullName);
        //是不是类
        Debug.LogFormat("IsClass:{0}", type.IsClass);
        //命名空间
        Debug.LogFormat("Namespace:{0}", type.Namespace);
        // 是否是抽象的(抽象类、接口)             
        Debug.LogFormat("IsAbstract:{0}", type.IsAbstract);
        // 是否是值类型             
        Debug.LogFormat("IsValueType:{0}", type.IsValueType);

    }

}

这些都是一些基本的操作,当然type的API不止这些。


namespace Q {
    public class MyClass
    {
        public void A()
        {

        }

        public void B()
        {

        }
        public string str;
        public int num;
        public int a;
        public string c { get; set; }
    }
}
public class Test04 : MonoBehaviour
{
    void Start()
    {

        var type = new MyClass().GetType();


        //获取当前 Type 直接从中继承的类型。
        Debug.LogFormat("BaseType:{0}", type.BaseType);
        //返回为当前 Type 的所有公共成员。
        Debug.LogFormat("Methods Length:{0}", type.GetMethods().Length);
        foreach (var memberInfo in type.GetMembers())
        {
            Debug.LogFormat("Member Name:{0}", memberInfo.Name);
        }
        //返回当前 Type 的所有公共字段数量。
        Debug.LogFormat("Fields Length:{0}", type.GetFields().Length);
        //返回为当前 Type 的所有公共属性数量。
        Debug.LogFormat("Properties Length:{0}", type.GetProperties().Length);
        //返回为当前 Type 的所有公共成员数量。
        Debug.LogFormat("Members Length:{0}", type.GetMembers().Length);
    }

}

但是我们发现Methods 的数量 和 Members 的数量和我们声明的成员的数量是不一致的。

  public class MyClass
    {
        public void A()
        {

        }

        public void B()
        {

        }
       public void C()
        {

        }
    }
}
public class Test04 : MonoBehaviour
{
    void Start()
    {

        var type = typeof (MyClass);


        Debug.LogFormat("Methods Count:{0}", type.GetMethods().Length);

        foreach (var methodInfo in type.GetMethods())
        {
            Debug.LogFormat("Method Name:{0}", methodInfo.Name);
        }

    }

}
www.zeeklog.com  - C# 反射(Reflection)

public 权限的属性器 也被当做两个 Method 了。 GetMethods 除了获取了类中的方法,也获取了父类中的方法,比如 Equals、GetHashCode、GetType、ToString。

GetMethods是没有办法直接获取到私有的成员的但是我们传入相应的flag就能获取到。


namespace Q {
    public class MyClass
    {
        public void A()
        {

        }

        private void B()
        {

        }
       public void C()
        {

        }
    }
}
public class Test04 : MonoBehaviour
{
    void Start()
    {
        // 通过对象的 GetType 获取对象      
        var type = typeof (MyClass);

              
      
        // 获取私有对象             
        // NonPublic 包含非 public 权限的方法             
        // Public 包含 public 权限的方法             
        // DeclaredOnly 不包含父类的方法             
        // Instance 指定实例成员将包括在搜索中。             
        var resultMethods = type.GetMethods(
              BindingFlags.NonPublic |
              BindingFlags.Public |
              BindingFlags.DeclaredOnly |
              BindingFlags.Instance);

        foreach (var methodInfo in resultMethods)
        {
            Debug.LogFormat("Method Name:{0}", methodInfo.Name);
        }
    }

}

Members同理我们可以传入相应的flag来限制父类成员的传入。


namespace Q {
    public class MyClass
    {
        public void A()
        {

        }

        private void B()
        {

        }
       public void C()
        {

        }
    }
    public class Test04 : MonoBehaviour
    {
        void Start()
        {
            // 通过对象的 GetType 获取对象      
            var type = typeof(MyClass);



            var members = type.GetMembers(
                   BindingFlags.Instance |
                   BindingFlags.NonPublic |
                   BindingFlags.Public |
                   BindingFlags.DeclaredOnly);

            foreach (var metInfo in members)
            {
                Debug.LogFormat("Members Name:{0}", metInfo.Name);
            }
        }

    }
}

Type 获取方式 : typeof , object.GetType

Type 对象的 API : 类信息查询 API , 检测 API ,类结构查询 API , BindingFlags

这就是一个简单的反射体系。

MethodInfo 对象

MethodInfo 顾名思义,就是方法信息的意思。

我们可以从两个地方获取到 MethodInfo 对象。

一个是通过 type.GetMethods() 获取到一个 MethodInfo 数组。

另一个是通过 type.GetMethod(name) 获取一个 MethodInfo 对象。


namespace Q {
    public class MyClass
    {
        public void A()
        {

        }

        private void B()
        {

        }
       public void C()
        {

        }
    }
    public class Test04 : MonoBehaviour
    {
        void Start()
        {
            // 通过对象的 GetType 获取对象      
            var type = typeof(MyClass);

            var methodAInfo = type.GetMethod("A");

            Debug.Log("方法信息查询 API");
            Debug.LogFormat("方法名:{0}", methodAInfo.Name);
            Debug.Log("方法检测 API");
            Debug.LogFormat("是否是泛型方法:{0}", methodAInfo.IsGenericMethod);
            Debug.LogFormat("是否是抽象方法:{0}", methodAInfo.IsAbstract);
            Debug.LogFormat("是否是构造方法:{0}", methodAInfo.IsConstructor);
            Debug.LogFormat("是否是虚方法:{0}", methodAInfo.IsVirtual);
            Debug.LogFormat("是否是 public 方法:{0}", methodAInfo.IsPublic);
            Debug.LogFormat("是否是 static 方法:{0}", methodAInfo.IsStatic);
      
            Debug.Log("方法结构查询 API");
            Debug.LogFormat("Method BaseType:{0}", methodAInfo.GetBaseDefinition());
            Debug.LogFormat("方法返回类型:{0}", methodAInfo.ReturnType);
            Debug.LogFormat("参数数量:{0}", methodAInfo.GetParameters().Length);
            Debug.LogFormat("获取方法体对象:{0}", methodAInfo.GetMethodBody());
        }

    }
}
www.zeeklog.com  - C# 反射(Reflection)

通过反射调用对象的方法

MethodInfo 与 Type 不同的是,MethodInfo 多了一个调用方法的 API 类型。

而这个 Invoke 方法,让我们知道了,我们是可以通过反射调用一个对象的方法的。


namespace Q {
    public class MyClass
    {
        public void A()
        {
            Debug.Log("hello world");
        }

        private void B()
        {

        }
       public void C()
        {

        }
    }
    public class Test04 : MonoBehaviour
    {
        void Start()
        {
           // 通过对象的 GetType 获取对象
           var someObj = new MyClass();
            var type = someObj.GetType();
            // 获取 SayHello 的 MethodInfo             
            var sayHelloMethodInfo = type.GetMethod("A");
            // 调用 someObj 的 SayHello 方法             
            // null 是指没有参数的意思             
            sayHelloMethodInfo.Invoke(someObj, null);
        }

    }
}

FieldInfo 对象

FIeldInfo 描述的是字段,字段是可以获取值(GetValue)和设置值的(SetValue),所以 FieldInfo 可以对一个对象设置值或获取值。

namespace Q {
    public class MyClass
    {
        public string str;
        public void A()
        {
            Debug.Log("hello world");
        }

        private void B()
        {

        }
       public void C()
        {

        }
    }
    public class Test04 : MonoBehaviour
    {
        void Start()
        {
            // 通过对象的 GetType 获取对象             
            var someObj = new MyClass();
            var type = someObj.GetType();
            var fieldAInfo = type.GetField("str");
            Debug.LogFormat("(信息)str Name:{0}", fieldAInfo.Name);
            Debug.LogFormat("(检测)是否是公开的 ?:{0}", fieldAInfo.IsPublic);
            Debug.LogFormat("(检测)是否是 Readonly 的 ?:{0}", fieldAInfo.IsInitOnly);
            Debug.LogFormat("(结构)Field 类型:{0}", fieldAInfo.FieldType);
            // 设置值             
            fieldAInfo.SetValue(someObj, "小A");
            // 获取值             
            var fieldAValue = fieldAInfo.GetValue(someObj);
            Debug.Log(fieldAValue);
            Debug.Log(someObj.str);
        }

    }
}

代码中的 Assembly 获取方式

准备一个类,把这个类编译成了 dll 文件,并放在磁盘上的某个位置。

using UnityEngine;

namespace q
{
    public class HelloWorld
    {
        public void Say()
        {
            Debug.Log("Hello World");
        }
    }
}

接着,我们来通过加载这个 dll 文件,并通过反射来调用它。

 void Start()
        {
            // 通过 dll 的完整路径加载 dll             
            var assembly = Assembly.LoadFile(Application.streamingAssetsPath + "/ConsoleApp1.dll");
            // 获取 SayHelloWorld 类             
            var type = assembly.GetType("q.HelloWorld");
            // 创建对象             
            var sayHelloWorldObj = Activator.CreateInstance(type);
            // 获取 Say 方法             
            var say = type.GetMethod("Say");
            // 调用 sayHelloWorldObj 的 Say 方法             
            say.Invoke(sayHelloWorldObj, null);
        }