【C#反射】开篇
微软官方教程:https://docs.microsoft.com/zh-cn/dotnet/framework/reflection-and-codedom/viewing-type-information
元編程(英語:Metaprogramming),又譯超編程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的資料,或者在运行时完成部分本应在编译时完成的工作。多数情况下,与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译。
编写元程序的语言称之为元语言。被操纵的程序的语言称之为「目标语言」。一门编程语言同时也是自身的元语言的能力称之为「反射」或者「自反」。
反射是促进元编程的一种很有价值的语言特性。把编程语言自身作为一級資料類型(如LISP、Forth或Rebol)也很有用。支持泛型编程的语言也使用元编程能力。
元编程通常通过两种方式实现。一种是通过应用程序编程接口(APIs)将运行时引擎的内部信息暴露于编程代码。另一种是动态执行包含编程命令的字符串表达式。因此,“程序能够编写程序”。虽然两种方式都能用于同一种语言,但大多数语言趋向于偏向其中一种。
个人理解元编程
C# 目前的反射只能对现在类型进行操作,还不能创建新的类型或编辑现有字段的类型信息。
反射获取的字段、属性、方法 如一个万能钥匙,可以用它操控所有的类型实例。C#泛型 反射都是元编程的能力
什么是反射
反射机制对应设计模式中的策略模式。
反射的作用:
1. 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现 有对象中获取类型
2. 应用程序需要在运行时从某个特定的程序集中载入一个特定的类型,以便实现某个任务时可以用到反射。
3. 反射主要应用与类库,这些类库需要知道一个类型的定义,以便提供更多的功能。框架(Spring .net/ .Net MVC等)
4、破坏单例模式 反射可以突破访问限制,直接调用私有构造函数
5、依赖注入
6、插件编程
7、序列化
C#反射的局限
反射不能修改readonly、只读属性、const字段、字符串拘留池 。
当修改readonly字段时候出现异常 System.FieldAccessException:“Cannot set initonly static field 'data' after type 'Program1' is initialized.”
修改只读属性的时候出现:Setting static readonly properties fails 异常。
using System.Reflection; mod(); static void mod() { Program1 ss = new(); var fieldInfo = typeof(Program1).GetField("data", BindingFlags.Static | BindingFlags.NonPublic); // fieldInfo.SetValue(ss, "MOD"); fieldInfo.SetValue(ss, "MOD");//出现异常 System.FieldAccessException:“Cannot set initonly static field 'data' after type 'Program1' is initialized.” } class Program1 { static readonly string data = "df"; // //这句运行正常 static string data = "df"; }
反射在在设计模式实现中的使用
采用反射技术可以简化工厂的实现。
(1)工厂方法:通过反射可以将需要实现的子类名称传递给工厂方法,这样无须在子类中实现类的实例化。
(2)抽象工厂:使用反射可以减少抽象工厂的子类。
采用反射技术可以简化工厂代码的复杂程度,在.NET项目中,采用反射技术的工厂已经基本代替了工厂方法。
采用反射技术可以极大地简化对象的生成,对以下设计模式的实现也有很大影响。
(1)命令模式:可以采用命令的类型名称作为参数直接获得命令的实例,并且可以动态执行命令。
(2)享元模式:采用反射技术实例化享元可以简化享元工厂
(3).NET Framework提供了两种方法来访问类型上的元数据:命名空间中System.Reflection提供的反射 API 和TypeDescriptor类。 反射是适用于所有类型的通用机制,因为它的基础是在根Object类的方法中建立的GetType。 它为类型返回的信息不可扩展,因为它无法在目标类型的编译后对其进行修改。
反射用到命名空间
System.Reflection
System.Type
System.Reflection.Assembly
System.Reflection.Emit
System.Type 类:通过这个类可以访问任何给定数据类型的信息。 对于反射起着核心的作用。但它是一个抽象的基类,Type有与每种数据类型对应的派生类,我们使用这个派生类的对象的方法、字段、属性来查找有关该类型的所有信息。
System.Reflection.Assembly类:它可以用于访问给定程序集的信息,或者把这个程序集加载到程序中。
System.Reflection.Emit命名空间的类提供了一种特殊形式的反射,可以在运行时构造类型。
System.Reflection
MemberInfo 包含以下类型:
MemberTypes
[Flags] public enum MemberTypes { Constructor = 1, Event = 2, Field = 4, Method = 8, Property = 16, TypeInfo = 32, Custom = 64, NestedType = 128, All = 191 } }
TypeInfo和Type的区别与选择
Type 类:表示类型声明(类型的引用):类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。。
TypeInfo 类:表示类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型的类型声明。
TypeInfo对象表示类型定义本身,而 Type 对象表示对类型定义的引用。 获取 TypeInfo 对象将强制加载包含该类型的程序集。 相比之下,你可以操作 Type 对象,而无需运行时加载它们引用的程序集。
TypeInfo出现于.net framework 4.5之后,这次调整用于区分两个概念:“reference”和“definition”。
reference is a shallow representation of something
definition is a rich representation of something
例如System.Reflection.Assembly就代表了一个“definition” ,而System.Reflection.AssemblyName就代表了一个“reference”
在未区分这两种概念之前,System.Type是这两种概念的混合,通过一个Type实例,既可以获得该类型的“Name、Assembly、……”,也可以获得该类型的“NestTypes、Fields、Properties、Methods、Constructors、Events……”。这也导致了当我们获得一个Type的时候,就把它全部的信息都加载到了内存中,但是很多情况下,这并不是我们想要看到的。举例如下:
更多相关内容请查看:https://blog.csdn.net/fengsocool/article/details/85927995
public MyClass:BaseClass{ } //获得MyClass的类型对象 Type t=MyClass.GetType();
在.net framework 4中,获得类型对象时,同时获得了基类(BaseClass)的类型对象,同时包括基类对象的“reference”和“definition”,因此需要把基类所在的程序集也加载进来。
在.net framework 4.5中,如下代码:
Type baseType = MyClass.GetType().GetTypeInfo().BaseType;
在获得类型对象时,只获得了基类的"reference"
反射经常用方法
#define DEBUG ////C# 的宏定义必须出现在所有代码之,告诉编译器哪些特性要编译IL代码 using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; namespace TestReflection { public delegate void NoParametMethodEventHandler(object o, EventArgs e); public delegate void methodBing(string x); public class UnFinsedAttribute:Attribute { } public interface IPlay { void Play(); } [UnFinsed] class MyType:IPlay { public event NoParametMethodEventHandler DianMing=delegate { }; Int32 myField; //构造函数 public MyType() { } public MyType(ref Int32 x) { x *= 5; } public MyType(int x) { x = 5; } public MyType(Int32 x, int c) { x *= 5; } public MyType(Int32[] x, int c) { c *= 5; } public override String ToString()=>myField.ToString(); public void Pritn(string x) => Console.WriteLine("Pritn(string x)"); public void Pritn(string x,int s) => Console.WriteLine("Pritn(string x,int s)"); //属性 public Int32 MyProp { get { return myField; } set { if (value < 1) throw new ArgumentOutOfRangeException("value", value, "value must be > 0"); myField = value; } } public string Address { get; set; } public string Name => "自定义类"; //索引 public int this[string i] { get { return 1; } set { } } public int this[int i] { get { return 1; } set { } } //事件触发器 public void ClickMe() { DianMing += Huida; DianMing.Invoke(new object(),new EventArgs()); } //事件处理器 public void Huida(object o, EventArgs e) { Console.WriteLine($"{Name}到"); } public void Play() { Console.WriteLine("接口方法"); } public void BingMethodToDelegat(string x) { Console.WriteLine($"你调用了动态方法BingMethodToDelegat方法的参数x:{x} 该方法调用了实例属性MyProp:{MyProp}"); } public static void StaticMethod(string x) { Console.WriteLine("你调用了静态方法StaticMethod"); } } class MyApp { static void Main() { Type type = typeof(MyType); Type parameterType = typeof(int); //====================构造函数获取 // Create an instance of a type. Object[] args = new Object[] { 8 }; object obj = Activator.CreateInstance(type); //创建实例 Console.WriteLine("=========创建实例==============="); //创建泛型 Type open = typeof(Dictionary<,>);//创建泛型类型的实例,首先要获取对应的开放类型(Open type)的引用,然后调用Type类型的MakeGenericType方法 //传入一个包含泛型参数的数组即可获取一个封闭类型(Closed Type).使用该封闭类型,调用Activator接受Type参数的某个方法既可以构造出具体的实例。 Type closeType = open.MakeGenericType(typeof(String), typeof(object)); object obj1 = Activator.CreateInstance(closeType);//方法一、Activator创建实例 ConstructorInfo constructorCreature = type.GetConstructor(Array.Empty<Type>()); ConstructorInfo constructor6 = type.GetConstructor(new[] { typeof(int) }); var obj2 = constructor6.Invoke(new[]{(Object)3});//方法二、构造创建实例 Object obj3= type.Assembly.CreateInstance("MyType");//方法三、Assembly创建实例 Object obj4 = type.InvokeMember(null, BindingFlags.CreateInstance, null, null, new Object[] { 8 });//方法四、InvokeMember 创建实例 Type refParameter = parameterType.MakeByRefType(); //参数 MyType(ref Int32 x); Type arrayParameter = parameterType.MakeArrayType(2); //参数数组int[]= new int[2]; Type parameterGeneric = typeof(List<>);//泛型参数 Type parameterGeneric2 = parameterGeneric.MakeGenericType(typeof(int));//list<int> //获取字段 Console.WriteLine("=========获取字段==============="); FieldInfo field = type.GetField("myField",BindingFlags.NonPublic|BindingFlags.Instance); field.SetValue(obj, 1);//设置私有字段的 Console.WriteLine(field.GetValue(obj));//获取私有字段 Console.WriteLine(field.FieldType);//获取字段类型 //获取构造函数 Console.WriteLine("=========获取构造函数 ==========="); ConstructorInfo constructorInfo1 = type.GetConstructor(new Type[0]);//过时 获取无参构造函数 参数是无 new Type[0] 表示空数组 ConstructorInfo constructorInfo2 = type.GetConstructor(Array.Empty<Type>());//获取无参构造函数 参数是无 new Type[0] 表示空数组 ConstructorInfo constructorInfo3 = type.GetConstructor(new Type[] { });// 过时 获取无参构造函数 参数是无(new Type[] { }]表示空数组 ConstructorInfo constructorInfo4 = type.GetConstructor(Type.EmptyTypes); //获取无参构造函数 这个才是最正确符合逻辑的写法 ConstructorInfo constructorInfo5 = type.GetConstructor(new[] { refParameter });//MyType(ref Int32 x) ConstructorInfo constructorInfo6 = type.GetConstructor(new[] { typeof(int) });//MyType(Int32 x) ConstructorInfo constructorInfo7 = type.GetConstructor(new[] { typeof(int), typeof(int) });//MyType(Int32 x,int c) ConstructorInfo constructorInfo8 = type.GetConstructor(new[] { typeof(int[]), typeof(int) });//MyType(Int32 x) ConstructorInfo[] constructorInfoAll = type.GetConstructors();//获取所有构造函数 //获取属性 Console.WriteLine("========获取属性==============="); PropertyInfo propertyInfo1 = type.GetProperty("Name"); PropertyInfo propertyInfo2 = type.GetProperty("Address"); PropertyInfo[] propertyInfoAll = type.GetProperties();//获取所有的属性,把索引也获取进去了 索引是有参属性,名字item Console.WriteLine(propertyInfo2.PropertyType);//属性的类型 Console.WriteLine(propertyInfo1.GetValue(obj)); // propertyInfo1.SetValue(obj, "haha");//错误的,因为没有set属性,所以无法设置,set就是set函数 Console.WriteLine(propertyInfo1.GetValue(obj)); Console.WriteLine(propertyInfo2.GetValue(obj)); propertyInfo2.SetValue(obj, "haha");//正确的,因为有set属性。 Console.WriteLine(propertyInfo2.GetValue(obj)); //获取索引 Console.WriteLine("=========获取索引==============="); PropertyInfo propertyInfo3 = type.GetProperty("Item",typeof(int),new[] { typeof(int) });//获取索引,索引的名字都是Item发,获取返回值是int,参数是int的属性。 //获取调用方法 Console.WriteLine("=========获取调用方法============"); MethodInfo method1 = type.GetMethod("ToString"); MethodInfo method2 = type.GetMethod("Pritn",new[] { typeof(string) });//获取一个参数的方法 MethodInfo method3 = type.GetMethod("Pritn",new[] {typeof(string),typeof(int)});//获取2个参数的方法 MethodInfo method4 = type.GetMethod("ClickMe"); MethodInfo method5 = type.GetMethod("Huida",new[] { typeof(object), typeof(EventArgs) }); MethodInfo method6 = type.GetMethod("StaticMethod", new[] { typeof(string) });//获取一个参数的方法 MemberInfo[] methodAll = type.GetMethods(); method4.Invoke(obj, null);//执行方法 //事件 Console.WriteLine("=========事件==============="); NoParametMethodEventHandler de = Huida2 ; EventInfo @event = type.GetEvent("DianMing");// @event.AddEventHandler(obj, de);//订阅事件 void Huida2(object o, EventArgs e) //处理器 { Console.WriteLine("manin到"); } method4.Invoke(obj, null);//执行方法 Console.WriteLine(@event.EventHandlerType.Name);//事件类型 //接口 Console.WriteLine("=========接口==============="); Type interFace = type.GetInterface("IPlay"); //委托绑定 Console.WriteLine("=========委托绑定==============="); Delegate methodDelegat1 = Delegate.CreateDelegate(typeof(methodBing), obj, "BingMethodToDelegat");//调用实例方法,必须绑定实例 methodDelegat1.DynamicInvoke("methodDelegat1");//实例方法要绑定实例使用,要不很容易报错,因为方法内部会调用很多内部成员 Delegate methodDelegat2 = Delegate.CreateDelegate(typeof(methodBing), null, method6);//调用了静态方法,静态不依附实例 methodDelegat2.DynamicInvoke("methodDelegat2"); //获取特性 var typeAttribute= type.GetCustomAttribute(typeof(UnFinsedAttribute)); } } }
反射为什么慢?
使用反射来调用类型或者触发方法,或者访问一个字段或者属性时clr 需要做更多的工作:校验参数,检查权限等等,所以速度是非常慢的。