C#的反射(一)
1.什么是元数据(MetaData)和反射(reflection)
一般情况下我们的程序都在处理数据的读、写、操作和展示。但是有些程序操作的数据不是数字、文本、图片,而是程序和程序类型本身的信息。
①元数据是包含程序以及类型信息的数据,它保存在程序的程序集当中。
②程序在运行的时候,可以查看其他程序集或者其本身的元数据。这个行为就是反射。
2.Type类
BCL声明了一个Type类型(它是抽象类),用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息。
由于Type是抽象类,所以它不能被实例化。而是在运行时,CLR创建从Type(RuntimeType)派生的类型的实例。当我们要访问这些实例的时候,CLR不会返回派生类的引用而是返回Type基类的引用。
关于Type有如下重要的点:
①对于程序每一个需要用到的类型,CLR会穿件一个包含这个类型信息的Type类型的对象(真实的是上面说的派生的类型的实例)。
②程序中用到的每一个类型都会关联到独立的Type类的对两个象。
③无论创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例。就像下面的图表示的一样。创建了一个OtherClass的实例oc、以及两个MyClass的实例mc1和mc2,但是在堆上都只会有一个Type对象来的对应他们,如下面的图示:
简单看一下Type这个类型,里面可以看到如下的一些方法和属性。
官方文档更全面哦: https://docs.microsoft.com/zh-cn/dotnet/api/system.type?view=netframework-4.8
3.学习如何获取一个Type类对象
方法一:通过GetType方法
object类型包含了一个GetType方法,它可以用来返回事例的Type对象引用。由于所有的类都是继承自object类型,所以所有的类都可以调用GetType来获得Type类型对象的引用。下面的图很好的说明了,基类、派生类和object之间的关系
所以下面的代码,在遍历派生类的Field的时候才能,把基类的也输出出来。
//基类
class BaseClass
{
public int BaseField = ;
} //派生类
class DerivedClass : BaseClass
{
public int DerivedField = ;
} class Program
{
static void Main(string[] args)
{
var bc = new BaseClass();
var dc = new DerivedClass();
BaseClass[] bca = new BaseClass[] { bc, dc };
foreach(var v in bca)
{
//获取类型
Type t = v.GetType();
Console.WriteLine("Object Type: {0}", t.Name);
//获取类中的字段
FieldInfo[] fi = t.GetFields();
foreach (var f in fi)
Console.WriteLine(" Field:{0}", f.Name);
Console.WriteLine();
}
Console.WriteLine("End!");
Console.ReadKey();
}
}
结果:
Object Type: BaseClass
Field:BaseField
Object Type: DerivedClass
Field:DerivedField
Field:BaseField
End!
方法二:还可以通过typeof()方法来获取一个类型的Type对象引用。例如下面的代码:
Type t = typeof(DerivedClass);
此外我们可以根据程序集来获取程序集内的类型
//通过程序集获取类型
var baseType = Assembly.GetExecutingAssembly().GetType("TestDemo.BaseClass");
var derivedType = Assembly.GetExecutingAssembly().GetType("TestDemo.DerivedClass");
4.常用的操作
结合GetType和typeof操作,可以做很多事情。
①获取数组类型
static void Main(string[] args)
{
var intArray = typeof(int).MakeArrayType();
var int3Array = typeof(int).MakeArrayType(); Console.WriteLine($"是否是int 数组 intArray == typeof(int[]) :{intArray == typeof(int[]) }");
Console.WriteLine($"是否是int 3维数组 intArray3 == typeof(int[]) :{int3Array == typeof(int[]) }");
Console.WriteLine($"是否是int 3维数组 intArray3 == typeof(int[,,]):{int3Array == typeof(int[,,]) }"); //数组元素的类型
Type elementType = intArray.GetElementType();
Type elementType2 = int3Array.GetElementType(); Console.WriteLine($"{intArray}类型元素类型:{elementType }");
Console.WriteLine($"{int3Array}类型元素类型:{elementType2 }"); //获取数组的维数
var rank = int3Array.GetArrayRank();
Console.WriteLine($"{int3Array}类型维数:{rank }");
Console.ReadKey();
}
如上面的例子,
MakeArrayType() 可以用来获取数组类型,有一个参数是数组的维数
GetElementType() 可以用来获取数组元素的类型
GetArrayRank() 可以获取数组的维数
②获取嵌套类型
public class Class
{
public class Student
{
public string Name { get; set; }
}
} class Program
{
static void Main(string[] args)
{
#region 嵌套类型
var classType = typeof(Class); foreach (var t in classType.GetNestedTypes())
{
Console.WriteLine($"NestedType ={t}");
//获取一个值,该值指示 System.Type 是否声明为公共类型。
Console.WriteLine($"{t}访问 {t.IsPublic}");
//获取一个值,通过该值指示类是否是嵌套的并且声明为公共的。
Console.WriteLine($"{t}访问 {t.IsNestedPublic}");
} Console.ReadKey();
#endregion
}
}
输出:
NestedType =TestDemo.Class+Student
TestDemo.Class+Student访问 False
TestDemo.Class+Student访问 True
③获取类型名称
Type里面具有NameSpace、Name和FullName属性。一般FullName是两者的组合。但是对于嵌套类型和封闭式泛型不成立。可以参考下面的demo
static void Main(string[] args)
{
#region 获取名称
var type = typeof(Class);
Console.WriteLine($"\n------------一般类型-------------");
PrintTypeName(type); //嵌套类型
Console.WriteLine($"\n------------嵌套类型-------------");
foreach (var t in type.GetNestedTypes())
{
PrintTypeName(t);
} var type2 = typeof(Dictionary<,>); //非封闭式泛型
var type3 = typeof(Dictionary<string, int>); //封闭式泛型 Console.WriteLine($"\n------------非封闭式泛型-------------");
PrintTypeName(type2);
Console.WriteLine($"\n------------封闭式泛型-------------");
PrintTypeName(type3);
Console.ReadKey();
#endregion } private static void PrintTypeName(Type t)
{
Console.WriteLine($"NameSpace: {t.Namespace}");
Console.WriteLine($"Name :{t.Name}");
Console.WriteLine($"FullName: {t.FullName}");
}
结果:
------------一般类型-------------
NameSpace: TestDemo
Name :Class
FullName: TestDemo.Class ------------嵌套类型-------------
NameSpace: TestDemo
Name :Student
FullName: TestDemo.Class+Student
NameSpace: TestDemo
Name :Teacher
FullName: TestDemo.Class+Teacher ------------非封闭式泛型-------------
NameSpace: System.Collections.Generic
Name :Dictionary`
FullName: System.Collections.Generic.Dictionary` ------------封闭式泛型-------------
NameSpace: System.Collections.Generic
Name :Dictionary`
FullName: System.Collections.Generic.Dictionary`[
[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],
[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
④获取基类类型和接口类型
var base1 = typeof(System.String).BaseType;
var base2 = typeof(System.IO.FileStream).BaseType;
var base3 = typeof(DerivedClass).BaseType; Console.WriteLine($"base1 :{base1.Name}");
Console.WriteLine($"base2 :{base2.Name}");
Console.WriteLine($"base3 :{base3.Name}"); foreach (var iType in typeof(Guid).GetInterfaces())
{
Console.WriteLine($"iType :{iType.Name}");
}
输出:
base1 :Object
base2 :Stream
base3 :BaseClass
iType :IFormattable
iType :IComparable
iType :IComparable`
iType :IEquatable`
此外Type还有两个方法:
我们在判断某个实例对象是否是某个类型的时候,经常使用 is语句。
Type中的方法 IsInstanceOfType 其实和is是等价的。
var baseClassObject = new BaseClass();
var check1 = baseClassObject is BaseClass;
var check2 = base3.IsInstanceOfType(baseClassObject);
Console.WriteLine($"使用is判断类型是否相同 :{check1}"); //结果True
Console.WriteLine($"使用IsInstanceOfType类型是否相同 :{check2 }"); //结果True
返回结果都是True的。
还有一个是 IsAssignableFrom ,它的作用是确定指定类型的实例是否可以分配给当前类型的实例。
var base4 = typeof(BaseClass); //baseClass的实例
var baseClassObject = new BaseClass();
var derivedClassObject = new DerivedClass();
var classObject = new Class();
var checkResult1 = base4.IsAssignableFrom(baseClassObject.GetType()); //判断BaseClass类型是否可以分配给BassClass类型
var checkResult2 = base4.IsAssignableFrom(derivedClassObject.GetType()); //判断DerivedClass类型是否可以分配给BassClass类型
var checkResult3 = base4.IsAssignableFrom(classObject.GetType()); //判断Class类型是否可以分配给BassClass类型
Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult1}"); //True
Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult2}"); //True
Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult3}"); //False
⑤实例化类型
I. 有两种方法可以动态的实例化类型。
方法一 通过静态的 Activator.CreateInstance()方法创建,它有多个重载函数。
var dateTime1 = (DateTime)Activator.CreateInstance(typeof(DateTime),2019,6,19);
var dateTime2 = (DateTime)Activator.CreateInstance(typeof(DateTime), 2019,6,19,10,10,10);
Console.WriteLine($"DateTime1: {dateTime1}"); //DateTime1: 2019/6/19 0:00:00
Console.WriteLine($"DateTime2: {dateTime2}"); //DateTime2: 2019/6/19 10:10:10
一般我们像上面一样都是传一个Type和构造函数的参数。当不存在这个构造函数的时候,就会抛出错误。
方法二 调用ConstructInfo对象上面的Invoke方法,ConstructInfo对象是通过调用类型(高级环境)上的GetConstructor方法获取的。
先分析一下场景,例如我有下面这样的一个类型:
public class InvokeClass
{
private string _testString;
private long _testInt; public InvokeClass(string abc)
{
_testString = abc;
}
public InvokeClass(StringBuilder abc)
{
_testString = abc.ToString();
} public InvokeClass(string abc,long def)
{
_testString = abc;
_testInt = def;
}
}
存在两个构造函数,一个传入的是string类型,一个传入的是StringBuilder类型,此时如果我通过new 的方式去创建一个对象,并传入构造函数为null,那么就是报出下面的错误:说明存在二义性,也就是说找不到对应使用哪个来构造。
同样的,如果我使用方法一 Activator.CreateInstance 去创建对象,会出现下面的问题:找不到对应的构造函数。
但是采用ConstructInfo的方式就可以指定对应的构造函数了。类似如下代码
//找到一个参数为string的构造函数
var constructorInfo = typeof(InvokeClass).GetConstructor(new[] { typeof(string)});
//使用该构造函数传入一个null参数
var obj4 = (InvokeClass)constructorInfo.Invoke(new object[] { null });
还可以结合查询来找到对应的构造函数
//获取所有的构造函数
var constructorInfoArray = typeof(InvokeClass).GetConstructors();
//过滤一次,获取所有两个参数的构造函数
var constructorInfoArray2 = Array.FindAll(constructorInfoArray, x => x.GetParameters().Length == 2);
//最后找的第二个参数是long类型的构造函数
var constructorInfo2 = Array.Find(constructorInfoArray2, x => x.GetParameters()[1].ParameterType == typeof(long));
//如果存在,就创建对象
if (constructorInfo2 != null)
{
var obj5 = (InvokeClass)constructorInfo2.Invoke(new object[] { "abc", 123 });
}
动态构造对象的缺点就是慢,简单对比一下,采用反射和new创建100万个对象,耗时对比还是比较明显的。
var sw = new Stopwatch();
sw.Start();
for (int i = ; i < ; i++)
{
var obj3 = (InvokeClass)Activator.CreateInstance(typeof(InvokeClass), "abc", );
}
sw.Stop();
Console.WriteLine($"时间:{sw.ElapsedMilliseconds}ms"); var sw2 = new Stopwatch();
sw2.Start();
for (int i = ; i < ; i++)
{
var obj = new InvokeClass("abc", ); }
sw2.Stop();
Console.WriteLine($"时间:{sw2.ElapsedMilliseconds}ms");
输出:
时间:280ms
时间:1ms
II. 实例化委托
动态创建静态方法和实例方法的委托传入的参数不太一样,使用的是CreateDelegate的重载,可以参考下面的例子
/// <summary>
/// 创建指定类型的委托,该委托表示要对指定的类实例调用的指定实例方法。
/// </summary>
/// <param name="type">要创建的委托的 System.Type</param>
/// <param name="target"> 类实例,对其调用 method</param>
/// <param name="method">委托要表示的实例方法的名称</param>
/// <returns></returns>
public static Delegate CreateDelegate(Type type, object target, string method); /// <summary>
/// 创建指定类型的委托,该委托表示指定类的指定静态方法。
/// </summary>
/// <param name="type">要创建的委托的 System.Type</param>
/// <param name="target"> 表示实现 method 的类的 System.Type</param>
/// <param name="method"> 委托要表示的静态方法的名称。</param>
/// <returns></returns>
public static Delegate CreateDelegate(Type type, Type target, string method);
例如:
class Program
{
public static int StaticSum(int a, int b) {
return a + b;
} public int InstanceSum(int a, int b)
{
return a + b;
} //创建一个委托
delegate int delegateOperate(int a, int b);
static void Main(string[] args)
{
#region 实例化委托
//静态方法的委托
Delegate staticD = Delegate.CreateDelegate(typeof(delegateOperate), typeof(Program), "StaticSum");
//实例方法的委托
Delegate instanceD = Delegate.CreateDelegate(typeof(delegateOperate), new Program(), "InstanceSum"); Console.WriteLine($"staticD:{staticD.DynamicInvoke(1,2)}");
Console.WriteLine($"instanceD:{instanceD.DynamicInvoke(10,20)}");
#endregion
Console.ReadKey();
}
}
III.范型的实例化
泛型分为封闭型和未封闭型,对于封闭类型的泛型是可以通过反射进行实例化的,而未封闭的泛型不能实例化。如下图所示:
封闭式的泛型和未绑定的泛型是可以相互转换的。
①未绑定的泛型可以通过 MakeGenericType 变成封闭的
②封闭的可以通过GetGenericTypeDefinition 获取未绑定的类型。
class Program
{
static void Main(string[] args)
{
Type closed = typeof(List<int>);
Type unBound = typeof(List<>); //转换
var newClosed = unBound.MakeGenericType(typeof(int));
var newUnBound = closed.GetGenericTypeDefinition(); Console.WriteLine($"List<int> 类型{closed}");
Console.WriteLine($"List<> 类型{unBound}");
Console.WriteLine($"List<> MakeGenericType执行后 类型{newClosed}");
Console.WriteLine($"List<int> GetGenericTypeDefinition执行后 类型{newUnBound}");
}
}
参考: 《C#图解教程》、《果壳中的C#》
C#的反射(一)的更多相关文章
- 隐私泄露杀手锏 —— Flash 权限反射
[简版:http://weibo.com/p/1001603881940380956046] 前言 一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱.社交网站 ...
- Java学习之反射机制及应用场景
前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框 ...
- 关于 CSS 反射倒影的研究思考
原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...
- 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)
建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...
- 运用Mono.Cecil 反射读取.NET程序集元数据
CLR自带的反射机智和API可以很轻松的读取.NET程序集信息,但是不能对程序集进行修改.CLR提供的是只读的API,但是开源项目Mono.Cecil不仅仅可以读取.NET程序集的元数据,还可以进行修 ...
- .NET面试题系列[6] - 反射
反射 - 定义,实例与优化 在面试中,通常会考察反射的定义(操作元数据),可以用反射做什么(获得程序集及其各个部件),反射有什么使用场景(ORM,序列化,反序列化,值类型比较等).如果答得好,还可能会 ...
- .NET基础拾遗(4)委托、事件、反射与特性
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...
- C++的性能C#的产能?! - .Net Native 系列五:.Net Native与反射
此系列系小九的学堂原创翻译,翻译自微软官方开发向导,一共分为六个主题.本文是第五个主题:.Net Native与反射. 向导文链接:<C++的性能C#的产能?! - .Net Native 系列 ...
- [源码]Literacy 快速反射读写对象属性,字段
Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...
- SI与EMI(一) - 反射是怎样影响EMI
Mark为期两天的EMC培训中大概分成四个时间差不多的部分,简单来说分别是SI.PI.回流.屏蔽.而在信号完整性的书籍中,也会把信号完整性分为:1.信号自身传输的问题(反射,损耗):2.信号与信号之间 ...
随机推荐
- Andrew Ng机器学习课程11之贝叶斯统计和正则化
Andrew Ng机器学习课程11之贝叶斯统计和正则化 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 在统计学中有两个学派,一个是频率学派,另一个是贝叶斯学 ...
- 简单介绍shell编程四剑客之sed
概要:分别的作用 grep:文本过滤(模式:pattern)工具,grep,egrep,fgrep,擅长过滤. sed:stream editor 文本编辑工具:(流编辑器),擅长取行.替换. awk ...
- PGA+SGA的几个参数操作
1.sga_max_size;是静态的必须重启之后生效需要加scope=spfile;(不要超过物理内存值) SQL> alter system set sga_max_size=500M sc ...
- 电子防抖(EIS)无效的相关修改
[DESCRIPTION] 电子防抖(EIS)无效的相关修改 [SOLUTION] 电子防抖(EIS)无效,根据不同的版本,可以先查看是否已经做了相关修改.1. MT6580/MT6735平台请参考如 ...
- java源码 -- TreeSet
这个TreeSet其实和HashSet类似.HashSet底层是通过HashMap实现的,TreeSet其实底层也是通过TreeMap实现的. 简介 TreeSet的作用是保存无重复的数据,不过还对这 ...
- 数据结构 -- 哈希表(hash table)
简介 哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函 ...
- C++:顺序表类实现约瑟夫问题_密码不同
//.h #pragma once #include <iostream> using namespace std; #define MAXSIZE 100 template <cl ...
- # VsCode 配置C++调试运行
VsCode 配置C++调试运行 打开命令面板快捷键为F1,软件上写的Ctrl+Shift+P似乎没用 先安装插件使得可以运行 先自行在vsc扩展中搜索C++安装C/C++插件 再参考知乎专栏中安装c ...
- 扩散:Apache2放开virtualhost,wamp启动apache服务失败
原文链接:https://blog.csdn.net/weixin_45688623/article/details/101423164 CSDN写过过程了,有点长,这里不赘述了,只写最后我设置的结果 ...
- 使用Duilib开发Windows软件(3)——控件的样式
摘抄下 https://www.cnblogs.com/Alberl/p/3344936.html 的一段代码 <?xml version="1.0" encoding=&q ...