C#反射学习笔记

C#反射学习笔记

1. 反射的功能

  • 在程序运行时,通过反射得到其他命名空间程序集中的元数据或相同命名空间程序集中的元数据。

2. 数据信息的获取(Type和Assembly)

  • Assembly用来加载程序集

    • 程序集:编译时由.NET工具生成,主要是以.dll和.exe文件形式存在。包含IL代码,元数据等。

    • 元数据:包含程序集信息(引用的其他程序集等)、模块信息、所有类型信息(类、接口、结构体、委托、枚举等)、成员信息、特性、泛型等信息。

    • 加载程序集的方法

      • Assembly.Load():参数是程序集的强名称或者简单名称,只能在程序的输出目录中检索,通常是bin文件夹。加载目标程序集时会加载依赖的程序集

        • namespace 反射;
          
          class Program
          {
              static void Main(string[] args)
              {
                  Assembly assembly1 = Assembly.Load("排序");
                  Console.WriteLine(assembly1.FullName);//输出强名称
              }
          }
          
      • Assembly.LoadFrom():参数是具体路径。通过具体的路径加载程序集,会加载依赖程序集。

        • using System.Reflection;
          namespace 反射;
          
          class Program
          {
              static void Main(string[] args)
              {
                  Assembly assembly2 = Assembly.LoadFrom(@"D:\C#\排序\排序\bin\Debug\net9.0\排序.dll");
                  Console.WriteLine(assembly2.FullName);//输出强名称
              }
          }
          
      • Assembly.LoadFile():参数是具体路径。通过具体的路径加载程序集,不会加载依赖程序集。

        • using System.Reflection;
          namespace 反射;
          
          class Program
          {
              static void Main(string[] args)
              {
                  Assembly assembly3 = Assembly.LoadFile(@"D:\C#\排序\排序\bin\Debug\net9.0\排序.dll");
                  Console.WriteLine(assembly3.FullName);//输出强名称
              }
          }
          
    • 获取已经加载的程序集

      • AppDomain(应用程序域):一个进程中可以有很多个应用程序域,创建的一个控制台项目是一个进程,运行的代码是在进程默认的应用程序域中在执行,一个应用程序域中可以有多个程序集。

      • using System.Reflection;
        using ClassLibrary1;
        namespace 反射;
        
        class Program
        {
            static void Main(string[] args)
            {
                //如果注释了这行代码,那下面的输出,就不会有ClassLibrary1这个程序集。因为只有应用了程序集中的数据才会加载。
                Class1 v =  new Class1();
        
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();//获取当前应用程序域中已经加载的所有程序集。
                foreach (Assembly asm in assemblies)
                {
                    Console.WriteLine(asm.FullName);
                }
                
                Console.WriteLine("-----------------------------------");
                
                Assembly assembly = Assembly.GetExecutingAssembly();//获取当前正在执行的代码所在的程序集
                AssemblyName[] assemblyNames = assembly.GetReferencedAssemblies();//获取在编译时当前程序集直接引用的所有其他程序集的元数据信息
                foreach (AssemblyName assemblyName in assemblyNames)
                {
                    Console.WriteLine(assemblyName.FullName);
                }
            }
        }
        
  • 这个是下面反射获取的类的具体信息

namespace ClassLibrary1;

public class Class1
{
    public int publicField;
    private int privateField;
    
    public int PublicProperty { get; set; }
    private int PrivateProperty { get; set; }
    
    public Class1()
    {
        Console.WriteLine("Class1无参构造函数");
    }
    public Class1(string parameter)
    {
        Console.WriteLine($"Class1有参数构造函数(string):{parameter}");
    }
    private Class1(int parameter)
    {
        Console.WriteLine($"Class1有参数私有构造函数(int):{parameter}");
    }
    public void PublicMethod()
    {
        Console.WriteLine("PublicMethod无参");
    }
    public void PublicMethod(string parameter)
    {
        Console.WriteLine($"PublicMethod有参:{parameter}");
    }

    public void GenericMethod<T>(T parameter)
    {
        Console.WriteLine($"泛型类型:{typeof(T)},传入的值:{parameter}");
    }
    private void PrivateMethod(string parameter)
    {
        Console.WriteLine("PrivateMethod有参:" + parameter);
    }
}

public class GenericClass<T1,T2>
{
    public GenericClass()
    {
        Console.WriteLine($"GenericClass<T1,T2>, T1:{typeof(T1)}, T2:{typeof(T2)}");
    }
}
  • 获取元数据

    1. 获取类的Type实例
      • using System.Reflection;
        using ClassLibrary1;
        namespace 反射;
        
        class Program
        {
            static void Main(string[] args)
            {
                //从加载的程序集中获取
                Assembly assembly = Assembly.Load("ClassLibrary1");
                Type[] types = assembly.GetTypes();//获取该程序集中类的Type  
                Type class1Type = assembly.GetType("ClassLibrary1.Class1")!;//获取指定的类的Type
                
                //typeof方法
                Type type = typeof(Class1);
                Console.WriteLine(type1.FullName);
                
                //通过对象.GetType获取
                Class1 class1 =  new Class1(); 
                Type type1 = class1.GetType();
                Console.WriteLine(type2.FullName);
        		
                //通过Type的静态方法获取
                Type type2 = Type.GetType("ClassLibrary1.Class1");
                Console.WriteLine(type3.FullName);
            }
        }
        
    • 获取类成员涉及的相关类
      //获取所有公共成员的信息
      MemberInfo(GetMenber/GetMenbers)
      //获取公共构造函数的信息
      ConstructorInfo(GetConstructor/GetConstructors)
      //获取公共方法的信息
      MethodInfo(GetMethod/GetMethods)
      //获取公共事件的信息
      EventInfo(GetEvent/GetEvents)
      //获取方法参数的信息
      ParameterInfo(GetParameters)
      //获取属性的信息
      PropertyInfo(GetProperty/GetProperties)
      //获取字段的信息
      FieldInfo(GetField/GeFields)
      
    1. 获取类的全部公共成员
      • //获取所有公共成员 //type还是上面代码的Class1的Type实例(下面的也一样)
        MemberInfo[] members = type.GetMembers();
        
        foreach (var member in members)
        {
            Console.WriteLine(member.Name);
        }
        
    2. 获取类的全部公共构造方法
      • //获取所有公共构造函数
        ConstructorInfo[] constructorInfos = type.GetConstructors();
        
        foreach (ConstructorInfo constructor in constructorInfos)//遍历构造函数
        {
            //获取构造函数参数
            ParameterInfo[] parameterInfos = constructor.GetParameters();
            
            foreach (ParameterInfo parameter in parameterInfos)//遍历函数参数
            {
                Console.WriteLine($"参数类型:{parameter.ParameterType} 参数名称:{parameter.Name} 参数个数:{constructor.GetParameters().Length}");
            }
        }
        
    3. 获取类的全部公共方法
      • //获取所有公共方法
        MethodInfo[] methodInfos = class1Type.GetMethods();
        foreach (MethodInfo method in methodInfos)
        {
            Console.WriteLine($"方法名:{method.Name} 返回值:{method.ReturnType} 参数个数:{method.GetParameters().Length}");
        }
        
    4. 获取类的全部公共属性
      • //获取所有公共属性
        PropertyInfo[] propertyInfos = type.GetProperties();
        
        foreach (PropertyInfo property in propertyInfos)
        {
            Console.WriteLine($"属性名:{property.Name} 属性类型:{property.PropertyType}");
        }
        
    5. 获取类的全部公共字段
      • //获取所有公共字段
        FieldInfo[] fieldInfos = type.GetFields();
        foreach (FieldInfo field in fieldInfos)
        {
            Console.WriteLine($"字段名:{field.Name} 字段类型:	{field.FieldType}");
        }
        

3. 使用程序集元数据

  • 使用Assembly实例.CreateInstance()来创建指定类的实例,返回值是object
using System.Reflection;
using ClassLibrary1;
namespace 反射;

class Program
{
    static void Main(string[] args)
    {
        Assembly assembly = Assembly.Load("ClassLibrary1");
        Type[] types = assembly.GetTypes();
        Type class1Type = assembly.GetType(types[0].FullName)!;
        
        Class1 instance2 = class1Type.Assembly.CreateInstance(type.FullName) as Class1;
        instance2 = assembly.CreateInstance(class1Type.FullName) as Class1;
        //上面两种写法其实是一样的class1Type.Assembly获取到的程序集就是Assembly.Load("ClassLibrary1");
        instance2.PublicMethod();      
    }
}
  • 使用构造方法来创建实例。返回值是object
    • Type[]是用来作为方法参数类型的数组,需要和方法的参数类型和数量匹配。例:new Type[]{typeof(int), Type{string}},用于获取方法时。
    • object[]是用来作为方法参数类型的数组,需要和方法的参数类型和数量匹配。例:new object[]{typeof(int), Type{string}},用于Invok方法传入参数时。
ConstructorInfo cons = class1Type.GetConstructor(new Type[] {typeof(int)});
Class1 instance3 = cons.Invoke(new object[]{}) as Class1;
instance3.PublicMethod();
  • 使用**Activator.CreateInstance()**创建实例,会直接调用该类的构造方法。返回值是object
//调用公共无参构造方法
Class1 instance1 = Activator.CreateInstance(class1Type) as Class1;
//调用公共有参构造方法
Class1 instance2 = Activator.CreateInstance(class1Type, 99) as Class1;//这个99亦可以这样写new object[]{99}

instance.PublicMethod();

//调用私有有参构造方法
Class1 instance3 = Activator.CreateInstance(class1Type, BindingFlags.NonPublic | BindingFlags.Instance,null,new Object[]{22},null)! as Class1;
//和调用公共方法相比,多了三个参数,大概是这样:

//第二个参数(BindingFlags.NonPublic | BindingFlags.Instance):BindingFlags是枚举,用于指定反射搜索成员时的行为,
    //常用 BindingFlags 值:
    Instance//实例成员(非静态)
    Static//静态成员
    Public//公共成员
    NonPublic//非公共成员(private, protected, internal)
    FlattenHierarchy//包含基类的静态成员
    // 常用组合
    BindingFlags.NonPublic | BindingFlags.Instance// 非公共的实例成员
    BindingFlags.Public | BindingFlags.Instance// 公共的实例成员  
    BindingFlags.Static | BindingFlags.Public// 公共的静态成员
    BindingFlags.Static | BindingFlags.NonPublic// 非公共的静态成员

//第三个参数(null):是binder(绑定器)
    //作用:处理类型转换和重载解析
    //通常设为 null:使用默认绑定器
    //自定义使用场景:需要特殊类型转换时
    
//第五个参数(null):是culture(文化信息)
    //作用:控制参数转换时的区域设置
    //通常设为 null:使用当前线程的区域设置
    //需要指定时:处理日期、数字格式等区域性敏感数据

注:上面在创建实例时,直接就从object转换成Class1类型,这样就不是反射了,而且都可以直接获取到了Class1类型,说明不用反射就可以得到该程序集里的数据了(直接导入了Class Library.Class1的引用),所以要改一下上面的代码,真正的使用反射来使用数据。

using System.Reflection;

namespace 反射;

class Program
{
    static void Main(string[] args)
    {
        //加载程序集
        Assembly assembly = Assembly.LoadFrom(@"D:\C#\反射\ClassLibrary1\bin\Debug\net9.0\ClassLibrary1.dll");
        //获取程序集中所有类的Type
        Type[] types = assembly.GetTypes();
        //输出所有类的信息
        foreach (Type type in types)
        {
            Console.WriteLine(type.FullName);
        }
        //获取指定类的Type
        Type class1Type = assembly.GetType(types[0].FullName)!;//可以直接等于types[0]
        //使用Activator.CreateInstance()创建实例
        object obj = Activator.CreateInstance(class1Type)!;
       
        //调用公共无参方法
        MethodInfo publicMethod1 = class1Type.GetMethod("PublicMethod", new Type[]{})!;
        publicMethod1.Invoke(obj, new object[]{});
        //调用公共有参方法
        MethodInfo publicMethod2 = class1Type.GetMethod("PublicMethod", new Type[]{typeof(string)})!;
        publicMethod2.Invoke(obj, new object[]{"有参方法被调用"});
        //调用有参私有方法,和前面的私有构造方法类似,不过绑定器和文化信息可以不写,也可以写
        MethodInfo privateMethod1 = class1Type.GetMethod("PrivateMethod", BindingFlags.NonPublic|BindingFlags.Instance,new Type[]{typeof(string)})!;
        privateMethod1.Invoke(obj, new object[]{"私有有参方法被调用"});
        //调用泛型方法
        MethodInfo genericMethod = class1Type.GetMethod("GenericMethod")!;//获取方法
        genericMethod = genericMethod.MakeGenericMethod(typeof(string));//构造泛型方法
        genericMethod.Invoke(obj, new object[]{"这是泛型方法"});//调用泛型方法
        //也可以将构造泛型方法和调用泛型方法合并
        //genericMethod.MakeGenericMethod(typeof(string)).Invoke(obj, new object[]{"这是泛型方法"});
        //同样调用私有或者静态泛型方法(普通方法也一样)时在获取方法时需要BingdingFlags
        
        //获取公共属性
        PropertyInfo publicProperty = class1Type.GetProperty("PublicProperty")!;
        publicProperty.SetValue(obj,1000);
        Console.WriteLine(publicProperty.GetValue(obj));
        //获取私有属性
        PropertyInfo privateProperty = class1Type.GetProperty("PrivateProperty",  BindingFlags.NonPublic|BindingFlags.Instance)!;
        privateProperty.SetValue(obj, 1500);
        Console.WriteLine(privateProperty.GetValue(obj));
        
        //获取公共字段
        FieldInfo publicField1 = class1Type.GetField("publicField")!;
        publicField1.SetValue(obj,2000);
        Console.WriteLine(publicField1.GetValue(obj));  
        //获取私有字段
        FieldInfo privateField1 = class1Type.GetField("privateField",  BindingFlags.NonPublic|BindingFlags.Instance)!;
        privateField1.SetValue(obj,2500);
        Console.WriteLine(privateField1.GetValue(obj));
        
        //创建泛型实例
        Type genericClassType = types[1];//获取类的信息
        //也可以这样写:
		//assembly.GetType(types[1].FullName)!
        //types[1].FullName等于ClassLibrary1.GenericClass`2
        genericClassType = genericClassType.MakeGenericType(new Type[]{typeof(string),  typeof(int)});//构造具体泛型类
        object obj3 = Activator.CreateInstance(genericClassType)!;//创建泛型类
    }
}
  • dynamic关键字。在编译时不会报错,运行时会解析实际类型。要是上面的obj.PublicMethod(),在编译时会直接报错。要是dynamic类型在运行时使用的成员实际类型没有,在运行时会报错。
using System.Reflection;

namespace 反射;

class Program
{
    static void Main(string[] args)
    {
        Assembly assembly = Assembly.LoadFrom(@"D:\C#\反射\ClassLibrary1\bin\Debug\net9.0\ClassLibrary1.dll");  
        Type[] types = assembly.GetTypes();
        Type class1Type = assembly.GetType(types[0].FullName)!;

        dynamic dynamicObject = Activator.CreateInstance(class1Type)!;
        //可以像直接使用对象一样操作
        dynamicObject.PublicMethod();
        dynamicObject.PublicMethod("有参");
        dynamicObject.PublicProperty = 3000;
        Console.WriteLine(dynamicObject.PublicProperty);
        dynamicObject.publicField = 4000;
        Console.WriteLine(dynamicObject.publicField);
    }
}

反射在游戏开发中的使用

  • 自定义编译器工具(获取带SerializeField的字段等)

  • 依赖注入

    [【C#入门详解16】-反射、依赖注入 - 苏格拉没有底的文章 - 知乎
    https://zhuanlan.zhihu.com/p/407070469]:

  • 制作插件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值