关于C#反射 你需要知道的
通常,反射用于动态获取对象的类型、属性和方法等信息。今天带你玩转反射,来汇总一下反射的各种常见操作,捡漏看看有没有你不知道的。
获取类型的成员
Type类的GetMembers方法用来获取该类型的所有成员,包括方法和属性,可通过BindingFlags标志来筛选这些成员。
usingSystem; usingSystem.Reflection; usingSystem.Linq; publicclassProgram { publicstaticvoidMain() { varmembers=typeof(object).GetMembers(BindingFlags.Public| BindingFlags.Static|BindingFlags.Instance); foreach(varmemberinmembers) { Console.WriteLine($"{member.Name}isa{member.MemberType}"); } } }
输出:
GetTypeisaMethod
GetHashCodeisaMethod
ToStringisaMethod
EqualsisaMethod
ReferenceEqualsisaMethod
.ctorisaConstructor
GetMembers方法也可以不传BindingFlags,默认返回的是所有公开的成员。
获取并调用对象的方法
Type类型的GetMethod方法用来获取该类型的MethodInfo,然后可通过MethodInfo动态调用该方法。
对于非静态方法,需要传递对应的实例作为参数,示例:
classProgram { publicstaticvoidMain() { varstr="hello"; varmethod=str.GetType() .GetMethod("Substring",new[]{typeof(int),typeof(int)}); varresult=method.Invoke(str,newobject[]{0,4});//相当于str.Substring(0,4) Console.WriteLine(result);//输出:hell } }
对于静态方法,则对象参数传空,示例:
varmethod=typeof(Math).GetMethod("Exp"); //相当于Math.Exp(2) varresult=method.Invoke(null,newobject[]{2}); Console.WriteLine(result);//输出(e^2):7.38905609893065
如果是泛型方法,则还需要通过泛型参数来创建泛型方法,示例:
classProgram { publicstaticvoidMain() { //反射调用泛型方法 MethodInfomethod1=typeof(Sample).GetMethod("GenericMethod"); MethodInfogeneric1=method1.MakeGenericMethod(typeof(string)); generic1.Invoke(sample,null); //反射调用静态泛型方法 MethodInfomethod2=typeof(Sample).GetMethod("StaticMethod"); MethodInfogeneric2=method2.MakeGenericMethod(typeof(string)); generic2.Invoke(null,null); } } publicclassSample { publicvoidGenericMethod() { //... } publicstaticvoidStaticMethod () { //... } }
创建一个类型的实例
使用反射动态创建一个类型的实例有多种种方式。最简单的一种是用new()条件声明。
使用new条件声明
如果在一个方法内需要动态创建一个实例,可以直接使用new条件声明,例如:
TGetInstance()whereT:new() { Tinstance=newT(); returninstance; }
但这种方式适用场景有限,比如不适用于构造函数带参数的类型。
使用Activator类
使用Activator类动态创建一个类的实例是最常见的做法,示例:
Typetype=typeof(BigInteger); objectresult=Activator.CreateInstance(type); Console.WriteLine(result);//输出:0 result=Activator.CreateInstance(type,123); Console.WriteLine(result);//输出:123
动态创建泛类型实例,需要先创建开放泛型(如List<>),再根据泛型参数转换为具象泛型(如List
//先创建开放泛型 TypeopenType=typeof(List<>); //再创建具象泛型 Type[]tArgs={typeof(string)}; Typetarget=openType.MakeGenericType(tArgs); //最后创建泛型实例 Listresult=(List )Activator.CreateInstance(target);
如果你不知道什么是开放泛型和具象泛型,请看本文最后一节。
使用构造器反射
也可以通过反射构造器的方式动态创建类的实例,比上面使用Activator类要稍稍麻烦些,但性能要好些。示例:
ConstructorInfoc=typeof(T).GetConstructor(new[]{typeof(string)}); if(c==null) thrownewInvalidOperationException("..."); Tinstance=(T)c.Invoke(newobject[]{"test"});
使用FormatterServices类
如果你想创建某个类的实例的时候不执行构造函数和属性初始化,可以使用FormatterServices的GetUninitializedObject方法。示例:
classProgram { staticvoidMain() { MyClassinstance=(MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass)); Console.WriteLine(instance.MyProperty1);//输出:0 Console.WriteLine(instance.MyProperty2);//输出:0 } } publicclassMyClass { publicMyClass(intval) { MyProperty1=val<1?1:val; } publicintMyProperty1{get;} publicintMyProperty2{get;set;}=2; }
获取属性或方法的强类型委托
通过反射获取到对象的属性和方法后,如果你想通过强类型的方法来访问或调用,可以在中间加一层委托。这样的好处是有利于封装,调用者可以明确的知道调用时需要传什么参数。比如下面这个方法,把Math.Max方法提取为一个强类型委托:
vartArgs=newType[]{typeof(int),typeof(int)}; varmaxMethod=typeof(Math).GetMethod("Max",tArgs); varstrongTypeDelegate=(Func)Delegate .CreateDelegate(typeof(Func ),null,maxMethod); Console.WriteLine("3和5之间最大的是:{0}",strongTypeDelegate(3,5));//输出:5
这个技巧也适用于属性,可以获取强类型的Getter和Setter。示例:
vartheProperty=typeof(MyClass).GetProperty("MyIntProperty"); //强类型Getter vartheGetter=theProperty.GetGetMethod(); varstrongTypeGetter=(Func)Delegate .CreateDelegate(typeof(Func ),theGetter); varintVal=strongTypeGetter(target);//相关于:target.MyIntProperty //强类型Setter vartheSetter=theProperty.GetSetMethod(); varstrongTypeSetter=(Action )Delegate .CreateDelegate(typeof(Action ),theSetter); strongTypeSetter(target,5);//相当于:target.MyIntProperty=5
反射获取自定义特性
以下是四个常见的场景示例。
示例一,找出一个类中标注了某个自定义特性(比如MyAtrribute)的属性。
varprops=type .GetProperties(BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance) .Where(prop=>Attribute.IsDefined(prop,typeof(MyAttribute)));
示例二,找出某个属性的所有自定义特性。
varattributes=typeof(t).GetProperty("Name").GetCustomAttributes(false);
示例三,找出程序集所有标注了某个自定义特性的类。
staticIEnumerableGetTypesWithAttribute(Assemblyassembly) { foreach(Typetypeinassembly.GetTypes()) { if(type.GetCustomAttributes(typeof(MyAttribute),true).Length>0) { yieldreturntype; } } }
示例四,在运行时读取自定义特性的值
publicstaticclassAttributeExtensions { publicstaticTValueGetAttribute( thisTypetype, stringMemberName, Func valueSelector, boolinherit=false) whereTAttribute:Attribute { varatt=type.GetMember(MemberName).FirstOrDefault() .GetCustomAttributes(typeof(TAttribute),inherit) .FirstOrDefault()asTAttribute; if(att!=null) { returnvalueSelector(att); } returndefault; } } //使用: classProgram { staticvoidMain() { //读取MyClass类的MyMethod方法的Description特性的值 vardescription=typeof(MyClass) .GetAttribute("MyMethod",(DescriptionAttributed)=>d.Description); Console.WriteLine(description);//输出:Hello } } publicclassMyClass { [Description("Hello")] publicvoidMyMethod(){} }
动态实例化接口的所有实现类(插件激活)
通过反射来动态实例化某个接口的所有实现类,常用于实现系统的插件式开发。比如在程序启动的时候去读取指定文件夹(如Plugins)中的dll文件,通过反射获取dll中所有实现了某个接口的类,并在适当的时候将其实例化。大致实现如下:
interfaceIPlugin { stringDescription{get;} voidDoWork(); }
某个在独立dll中的类:
classHelloPlugin:IPlugin { publicstringDescription=>"ApluginthatsaysHello"; publicvoidDoWork() { Console.WriteLine("Hello"); } }
在你的系统启动的时候动态加载该dll,读取实现了IPlugin接口的所有类的信息,并将其实例化。
publicIEnumerableInstantiatePlugins(stringdirectory) { varassemblyNames=Directory.GetFiles(directory,"*.addin.dll") .Select(name=>newFileInfo(name).FullName).ToArray(); foreach(varfileNameassemblyNames) AppDomain.CurrentDomain.Load(File.ReadAllBytes(fileName)); varassemblies=assemblyNames.Select(System.Reflection.Assembly.LoadFile); vartypesInAssembly=assemblies.SelectMany(asm=>asm.GetTypes()); varpluginTypes=typesInAssembly.Where(type=>typeof(IPlugin).IsAssignableFrom(type)); returnpluginTypes.Select(Activator.CreateInstance).Cast (); }
检查泛型实例的泛型参数
前文提到了构造泛型和具象泛型,这里解释一下。大多时候我们所说的泛型都是指构造泛型,有时候也被称为具象泛型。比如List
假如现在有一个泛型实例,出于某种需求,我们想知道构建这个泛型实例需要用什么泛型参数。比如某人创建了一个List
varmyList=newList(); ShowGenericArguments(myList);
我们的方法签名是这样的:
publicvoidShowGenericArguments(objecto)
这时,作为此方法的编写者,我们并不知道这个o对象具体是用什么类型的泛型参数构建的。通过反射,我们可以得到泛型实例的很多信息,其中最简单的就是判断一个类型是不是泛型:
publicvoidShowGenericArguments(objecto) { if(o==null)return; Typet=o.GetType(); if(!t.IsGenericType)return; ... }
由于List<>本身也是泛型,所以上面的判断不严谨,我们需要知道的是对象是不是一个构造泛型(List
typeof(List<>).IsGenericType//true typeof(List<>).IsGenericTypeDefinition//true typeof(List<>).IsConstructedGenericType//false typeof(List).IsGenericType//true typeof(List ).IsGenericTypeDefinition//false typeof(List ).IsConstructedGenericType//true
IsConstructedGenericType和IsGenericTypeDefinition分别用来判断某个泛型是不是构造泛型和非构造泛型。
再结合Type的GetGenericArguments()方法,就可以很容易地知道某个泛型实例是用什么泛型参数构建的了,例如:
staticvoidShowGenericArguments(objecto) { if(o==null)return; Typet=o.GetType(); if(!t.IsConstructedGenericType)return; foreach(TypegenericTypeArgumentint.GetGenericArguments()) Console.WriteLine(genericTypeArgument.Name); }
以上就是深入了解C#之反射的详细内容,更多关于c#反射的资料请关注毛票票其它相关文章!