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#的反射(一)的更多相关文章

  1. 隐私泄露杀手锏 —— Flash 权限反射

    [简版:http://weibo.com/p/1001603881940380956046] 前言 一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱.社交网站 ...

  2. Java学习之反射机制及应用场景

    前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框 ...

  3. 关于 CSS 反射倒影的研究思考

    原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...

  4. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  5. 运用Mono.Cecil 反射读取.NET程序集元数据

    CLR自带的反射机智和API可以很轻松的读取.NET程序集信息,但是不能对程序集进行修改.CLR提供的是只读的API,但是开源项目Mono.Cecil不仅仅可以读取.NET程序集的元数据,还可以进行修 ...

  6. .NET面试题系列[6] - 反射

    反射 - 定义,实例与优化 在面试中,通常会考察反射的定义(操作元数据),可以用反射做什么(获得程序集及其各个部件),反射有什么使用场景(ORM,序列化,反序列化,值类型比较等).如果答得好,还可能会 ...

  7. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  8. C++的性能C#的产能?! - .Net Native 系列五:.Net Native与反射

    此系列系小九的学堂原创翻译,翻译自微软官方开发向导,一共分为六个主题.本文是第五个主题:.Net Native与反射. 向导文链接:<C++的性能C#的产能?! - .Net Native 系列 ...

  9. [源码]Literacy 快速反射读写对象属性,字段

    Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...

  10. SI与EMI(一) - 反射是怎样影响EMI

    Mark为期两天的EMC培训中大概分成四个时间差不多的部分,简单来说分别是SI.PI.回流.屏蔽.而在信号完整性的书籍中,也会把信号完整性分为:1.信号自身传输的问题(反射,损耗):2.信号与信号之间 ...

随机推荐

  1. OC入门笔记

    1OC概述OC主要负责UI界面:C语言和C++可以用于图形处理.OC是一门面向对象的语言.C语言是面向过程的.比C++简单很多以C语言为基础,完全兼容C语言.OC语言中的所有事物都是对象,都有isa指 ...

  2. ThinkPHP 5 文件上传及指定宽高生成缩略图公共方法

    这个是非常常用的案例,ThinkPHP 5 文件上传及指定宽高生成缩略图公共方法/** * 单文件上传 * name:表单上传文件的名字 * ext: 文件允许的后缀,字符串形式 * path:文件保 ...

  3. 《Hadoop大数据技术开发实战》学习笔记(一)

    基于CentOS7系统 新建用户 1.使用"su-"命令切换到root用户,然后执行命令: adduser zonkidd 2.执行以下命令,设置用户zonkidd的密码: pas ...

  4. 印象笔记·剪藏 Chrome插件

    印象笔记·剪藏 Chrome插件 链接:https://pan.baidu.com/s/10nzrSk_3sLkOI29MIEPEBw  密码:p8n8

  5. 最新 满帮java校招面经 (含整理过的面试题大全)

    从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿.满帮等10家互联网公司的校招Offer,因为某些自身原因最终选择了满帮.6.7月主要是做系统复习.项目复盘.LeetCode ...

  6. 给定一个字符串str,将str中连续两个字符为a的字符替换为b(一个或连续超过多个字符a则不替换)

    需求:给定一个字符串str,将str中连续两个字符为a的字符替换为b(一个或连续超过多个字符a则不替换) 如: a 不替换 b 不替换  ab 不替换 ba 不替换 aba 不替换  aab 替换为 ...

  7. canvas画箭头demo

    效果图: 代码: <!DOCTYPE html> <html> <title>canvas画箭头demo</title> <body> &l ...

  8. python 计算文件夹里所有内容的大小总和

    计算文件夹里所有内容的大小总和 递归方法 '''计算文件夹的大小''' import os def dir_file_size(path): if os.path.isdir(path): file_ ...

  9. jira邮箱配置

    系统-邮件-外发邮件

  10. Windows用Eclipse来开发hadoop的WordCount的helloworld

    [学习笔记] 2.Win7用Eclipse来开发hadoop的WordCount的helloworld网上下载hadoop-eclipse-plugin-2.7.4.jar,将该jar包拷贝到Ecli ...