C# 反射
1. 概述反射
在程序运行的过程中动态获取类型信息和调用类型成员的功能
例如:我们定义好一个类后,在VS开发工具中编写代码,会有智能提示此类有哪些成员,就是使用反射技术来获取类型的成员
通过反射技术,我们不需要在代码中写固化的代码,而是在运行时根据需求动态执行代码
创建对象 注意: assembly.GetName().Name用于获取程序集的名称
//创建对象方 Assembly assembly = Assembly.GetExecutingAssembly();//加载当前程序集 object o1 = assembly.CreateInstance(assembly.GetName().Name + ".Info"); //创建对象方式2 object o2 = Activator.CreateInstance(typeof(Info));
反射核心类:Type
使用Type类的对象来表示一个类,通过Type类对象能够获取类型的成员和调用成员
static void Main(string[] args) { //方法一 Type t1 = typeof(Info); //方法二 Assembly assembly = Assembly.GetExecutingAssembly(); object o1 = assembly.CreateInstance(assembly.GetName().Name + ".Info"); Type t2 = o1.GetType(); Console.WriteLine(t2.Name);//类型名称 PropertyInfo[] pros = t2.GetProperties();//获取所有公共属性 //遍历所有公共属性,并输出属性名称 foreach (PropertyInfo p in pros) { Console.WriteLine(p.Name + "-" + p.PropertyType.Name); } PropertyInfo pro = t2.GetProperty("Age");//根据指定的属性名获取属性 MethodInfo[] methods = t2.GetMethods();//获取所有公共方法 foreach (MethodInfo m in methods) { Console.WriteLine("方法名:" + m.Name + ",方法返回的类型:" + m.ReturnType.Name); } MethodInfo method = t2.GetMethod("Greet"); ParameterInfo[] pis = method.GetParameters();//获取方法的所有参数 foreach (ParameterInfo pi in pis) { Console.WriteLine("参数名:"+pi.Name+",参数类型:"+pi.ParameterType.Name); } }
调用方法
PropertyInfo p = t1.GetProperty("Name");//获取指定的属性 p.SetValue(对象,"abc");//设置对象的属性值 object value = p.GetValue(对象,null);//获取对象的属性值 MethodInfo m = t1.GetMethod("Greet");//获取指定的方法 m.Invoke(对象, new object[] { 值, 值 });//调用对象的方法
- 通过反射可以提供类型信息,从而使得我们开发人员在运行时能够利用这些信息构造和使用对象。
- 反射机制允许程序在执行过程中动态地添加各种功能。
-
都知道反射伤性能,但不得不反射的时候又怎么办呢?当真的被问题逼迫的时候还是能找到解决办法的。
为反射得到的方法创建一个委托,此后调用此委托将能够提高近乎直接调用方法本身的性能。(当然 Emit 也能够帮助我们显著提升性能,不过直接得到可以调用的委托不是更加方便吗?)
-
using System; using System.Diagnostics; using System.Reflection; namespace Walterlv.Demo { public class Program { static void Main(string[] args) { // 调用的目标实例。 var instance = new StubClass(); // 使用反射找到的方法。 var method = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) }); Assert.IsNotNull(method); // 将反射找到的方法创建一个委托。 var func = InstanceMethodBuilder<int, int>.CreateInstanceMethod(instance, method); // 跟被测方法功能一样的纯委托。 Func<int, int> pureFunc = value => value; // 测试次数。 var count = 10000000; // 直接调用。 var watch = new Stopwatch(); watch.Start(); for (var i = 0; i < count; i++) { var result = instance.Test(5); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接调用"); // 使用同样功能的 Func 调用。 watch.Restart(); for (var i = 0; i < count; i++) { var result = pureFunc(5); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用同样功能的 Func 调用"); // 使用反射创建出来的委托调用。 watch.Restart(); for (var i = 0; i < count; i++) { var result = func(5); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射创建出来的委托调用"); // 使用反射得到的方法缓存调用。 watch.Restart(); for (var i = 0; i < count; i++) { var result = method.Invoke(instance, new object[] { 5 }); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射得到的方法缓存调用"); // 直接使用反射调用。 watch.Restart(); for (var i = 0; i < count; i++) { var result = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) }) ?.Invoke(instance, new object[] { 5 }); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接使用反射调用"); } private class StubClass { public int Test(int i) { return i; } } } } namespace Walterlv.Demo { public static class InstanceMethodBuilder<T, TReturnValue> { /// <summary> /// 调用时就像 var result = func(t)。 /// </summary> [Pure] public static Func<T, TReturnValue> CreateInstanceMethod<TInstanceType>(TInstanceType instance, MethodInfo method) { if (instance == null) throw new ArgumentNullException(nameof(instance)); if (method == null) throw new ArgumentNullException(nameof(method)); return (Func<T, TReturnValue>) method.CreateDelegate(typeof(Func<T, TReturnValue>), instance); } /// <summary> /// 调用时就像 var result = func(this, t)。 /// </summary> [Pure] public static Func<TInstanceType, T, TReturnValue> CreateMethod<TInstanceType>(MethodInfo method) { if (method == null) throw new ArgumentNullException(nameof(method)); return (Func<TInstanceType, T, TReturnValue>) method.CreateDelegate(typeof(Func<TInstanceType, T, TReturnValue>)); } } }
2. Type类的介绍
- 是BCL(基底类别库)声明的一个抽象类,所有它不能被实例化
- 对于程序中用到的每一个类型,CLR(公共语言运行时)都会创建一个包含这个类型信息的Type类型的对象
- 程序中用到的每一个类型都会关联到独立的Type类型的对象
- 不管创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例
2.1 Type类的部分常见成员
成员 | 成员类型 | 描述 |
Name | 属性 | 返回类型的名字 |
FullName | 属性 | 返回数据类型的完全限定名(包括命名空间名) |
NameSpace | 属性 | 返回包含数据类型声明的命名空间 |
Assembly | 属性 | 返回声明类型的程序集。如果类型是泛型的,返回定义这个类型的程序集 |
GetConstructor(), GetConstructors() | 方法 | 返回ConstructorInfo类型,用于取得该类的构造函数的信息 |
GetEvent(), GetEvents() | 方法 | 返回EventInfo类型,用于取得该类的事件的信息 |
GetField(), GetFields() | 方法 | 返回FieldInfo类型,用于取得该类的字段(成员变量)的信息 |
GetInterface(), GetInterfaces() | 方法 | 返回InterfaceInfo类型,用于取得该类实现的接口的信息 |
GetMember(), GetMembers() | 方法 | 返回MemberInfo类型,用于取得该类的所有成员的信息 |
GetMethod(), GetMethods() | 方法 | 返回MethodInfo类型,用于取得该类的方法的信息 |
GetProperty(), GetProperties() | 方法 | 返回PropertyInfo类型,用于取得该类的属性的信息 |
3. 如何获取Type类型
3.1GetType()和typeof() 方法 两者都是返回Syetem.Type的引用。(private和protected修饰的成员也可以访问到)
3.1.1 GetType()
1.GetType()是从Syetem.object中基础的方法。
2.GetType()必须要通过类型的实例点出这个方法。
3.1.2 typeof()
1.typeof(xx)是公开的运算符。
2.typeof(xx)中xx只能是int,string 等类型及自定义类型,不能是实例。
3.2 不同点
1.GetType()返回的是Type(类型)
2.typeof(xx) 返回的是xx Class(类)的类型
//实例一个用户类 User user = new User(); //GetType()方法 Type getType = user.GetType(); //typeof(xx) 方法 Type typeOf = typeof(User); //判断是否相等 if (getType == typeOf) { //这里相等 Console.WriteLine("我在这"); }
4.Type类方法
1: 一旦有了Type对象就可以使用GetMethodInfo()方法获取此类型支持的方法列表。该方法返回一个MethodInfo 对象数组,MethodInfo对象描述了主调类型所支持的方法,他位于System.Reflection命名空间中
2: MethodInfo类派生于MethodBase抽象类,而MethodBase类继承了MemberInfo类。因此我们能够使用这三个类定义的属性和方法。例如,使用Name属性得到方法名称。这里有两个重要的成员:
3: ReturnType属性 :为Type类型的对象,能够提供方法的返回类型信息 GetParameters()方法 :返回参数列表,参数信息以数组形式保存在PatameterInfo对象中。PatameterInfo类定义了大量描述参数信息的属性和方法。这里也列出两个常用的属性 :Name(包含参数名称信息的字符串),ParameterType(参数类型的信息)。
//创建实例 Sublevel sublevel = new Sublevel(); //获取类型 Type sublevelType = sublevel.GetType(); //获取类型的方法列表 //BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public 这个有一个注意点 //实际上至少要有Instance(或Static)与Public(或NonPublic)标记。否则将不会获取任何方法。 MethodInfo[] obj = sublevelType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); //遍历所有的方法 foreach (MethodInfo item in obj) { //返回方法的返回类型 Console.Write(item.ReturnType.Name); //返回方法的名称 Console.Write(" "+item.Name+"("); //获取方法的返回参数列表 ParameterInfo[] parameterss = item.GetParameters(); foreach (var parameters in parameterss) { //参数类型名称 Console.Write(parameters.ParameterType.Name); //参数名称 Console.Write(" "+parameters.Name+","); } Console.WriteLine(")"); }
5.方法的使用
前面我们讲了放的显示,但是只能看到不能用就不好了呀!!!
5.1:GetMethods()方法的另一种形式
这种形式可以制定各种标记,已筛选想要获取的方法。他的通用形式为:MethodInfo[] GetMethods(BindingFlags bindingAttr)BindingFlags是一个枚举,枚举值有(很多只列出4个吧)
- Instance:获取实例方法
- NonPublic: 获取非公有方法
- Public: 获取共有方法
- Static:获取静态方法
GetMethods(BindingFlags bindingAttr)这个方法,参数可以使用or把两个或更多标记连接在一起,实际上至少要有Instance(或Static)与Public(或NonPublic)标记。否则将不会获取任何方法。del.GetType();
public static void Method<T>(T model) { //获取泛性的Type类型 Type objType = model.GetType(); //获取泛性的方法列表 MethodInfo[] mthodInfos = objType.GetMethods(); //循环方法 foreach (var item in mthodInfos) { //获取方法的所有参数列表 var parameters = item.GetParameters(); //过滤没用方法 //1:查看是不是有参数的方法 //2:查看这个方法的返回类型是不是我们想要的 //3:查看这个方法的返回类型是不是我们想要的 if (parameters.Any() && parameters[0].ParameterType == typeof(int) && item.ReturnType != typeof(void)) { //调用方法 object[] parametersObj = new object[] { 5 }; //调用实例方法 //第一个参数是我们的实体,后面是我们的参数(参数是一个数组,多个参数按照顺序来传递,没有参数可以为null) //如果我们的方法是一个静态方法 ,这个参数可以为null (不是静态的就会报错) Console.WriteLine(item.Invoke(model, parametersObj)); } } }
6.DataTable转Model(List)
在刚刚学.net 的时候,我们从数据库查询出一个DataTable的时候想要转成Model或者LIst的时候我们需要手动的写遍历,超级麻烦(在没有接触MVC的时候我就是)
/// <summary> /// DataTable转换 /// </summary> public class TransitionDataTable { /// <summary> /// DataTable转换模型 /// </summary> /// <typeparam name="T">模型类型</typeparam> /// <param name="obj">模型</param> /// <param name="data">数据行</param> /// <returns></returns> public T DataSetBindModel<T>(T obj, DataTable data) where T : class, new() { T result = new T(); foreach (DataRow item in data.Rows) { result = assignmentClass(obj, item); } return result; } /// <summary> /// DataTable转换List /// </summary> /// <typeparam name="T">模型类型</typeparam> /// <param name="obj">模型</param> /// <param name="data">数据行</param> /// <returns></returns> public List<T> DataSetBindList<T>(T obj, DataTable data) where T : class, new() { List<T> result = new List<T>(); foreach (DataRow item in data.Rows) { result.Add(assignmentClass(obj, item)); } return result; } /// <summary> /// DataRow 转换成模型 /// </summary> /// <typeparam name="T">模型类型</typeparam> /// <param name="obj">模型</param> /// <param name="row">数据行</param> /// <returns></returns> private T assignmentClass<T>(T obj, DataRow row) where T : class, new() { if (obj == null) { obj = new T(); } Type type = obj.GetType(); //得到类型的所有属性,也就是表对应的实体模型的所有属性 //嗮选一下只要公开的 PropertyInfo[] properts = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); if (properts.Any()) { foreach (PropertyInfo item in properts) { if (row.Table.Columns.IndexOf(item.Name) != -1) { if (row[item.Name] != null && row[item.Name] != DBNull.Value) { item.SetValue(obj, row[item.Name]); } } } } return obj; } }
调用
static void Show() { DataTable data = new BDHelper().GetData("select * from Jack_News_TNews"); News news = new News(); var list = new TransitionDataTable().DataSetBindList(news, data); }
在做RESTful api模式开发Api时,Patch更新时,频繁的遇到不同类型但属性相同的Dto到Model赋值的情况,
网上通用的类库是AutoMapper,但遇到了问题,查了Git也提出了问题,并未能解决。
Post、Get等覆盖式传值时都能满足需求,唯独当Dto的值类型字段设为Nullable(如int?、bool?)时会被覆盖为默认值
利用反射查找类的字段名称与字段类型,并将具备相同属性名的源class属性值赋值到目标class,如遇到源class属为Null(引用空 或 Nullable泛型类型Null)跳过该属赋值。
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Zhiqing.Helpers.Lambdas { /// <summary> /// 快速复制,并忽略NULL值 /// </summary> public static class FastCopy { static Action<S, T> CreateCopier<S, T>() { // 源 var source = Expression.Parameter(typeof(S)); // 目标 var target = Expression.Parameter(typeof(T)); // 源 所有属性 var props1 = typeof(S).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead).ToList(); // 目标 所有属性 var props2 = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanWrite).ToList(); // 共有属性 var props = props1.Where(x => props2.Any(y => y.Name == x.Name)); IEnumerable<Expression> assets = new List<Expression>(); foreach (var item in props) { // 目标 var tItem = Expression.Property(target, item.Name); var tType = tItem.Type; var tIsNullable = tType.IsGenericType && tType.GetGenericTypeDefinition() == typeof(Nullable<>); // 源 var sItem = Expression.Property(source, item.Name); var sType = sItem.Type; var sIsNullable = sType.IsGenericType && sType.GetGenericTypeDefinition() == typeof(Nullable<>); Debug.WriteLine(sIsNullable); // =================================== // 注释:Nullable实际是个泛型,赋值是需要转为实际类型才可赋值,否咋泛型给实际类型赋值引发异常 // 案例:int? s = 1;int t = s; 会引发异常 // 解决:int? s = 1;int t = Convert.ToInt32(s); 转换后解决 // 另外:Lamnda表达式应使用 Expression.Convert(); 转换 // 源是可为空类型 if (sIsNullable) { // 目标可为空 if (tIsNullable) { // 赋值表达式 var asset = Expression.Assign(tItem, sItem); // 当源不为空的时候赋值 var notNull = Expression.IfThen(Expression.NotEqual(sItem, Expression.Constant(null)), asset); // 加入表达式树 assets = assets.Append(notNull); } // 目标不可为空 else { // 转换源为实际类型 var sItemConverted = Expression.Convert(sItem, sType.GetGenericArguments().First()); // 赋值表达式 var asset = Expression.Assign(tItem, sItemConverted); // 当源不为空的时候赋值 var notNull = Expression.IfThen(Expression.NotEqual(sItem, Expression.Constant(null)), asset); // 加入表达式树 assets = assets.Append(notNull); } } // 源不是可为空类型 else { // 源是否值类型 var sIsValueType = sType.IsValueType; if (sIsValueType) { // 赋值表达式 var asset = Expression.Assign(tItem, sItem); // 加入表达式树 assets = assets.Append(asset); } // 不是值类型 else { // 赋值表达式 var asset = Expression.Assign(tItem, sItem); // 当源不为空的时候赋值 var notNull = Expression.IfThen(Expression.NotEqual(sItem, Expression.Constant(null)), asset); // 加入表达式树 assets = assets.Append(notNull); } } } // 赋值 var tempBlock = Expression.Block(assets); return Expression.Lambda<Action<S, T>>(tempBlock, source, target).Compile(); } static ConcurrentDictionary<string, object> actions = new ConcurrentDictionary<string, object>(); /// <summary> /// 快速的拷贝同名公共属性。忽略差异的字段。 /// </summary> /// <typeparam name="S"></typeparam> /// <typeparam name="T"></typeparam> /// <param name="from"></param> /// <param name="to"></param> public static void Copy<S, T>(S from, T to) { string name = string.Format("{0}_{1}", typeof(S), typeof(T)); if (!actions.TryGetValue(name, out object obj)) { var ff = CreateCopier<S, T>(); actions.TryAdd(name, ff); obj = ff; } var act = (Action<S, T>)obj; act(from, to); } } }
使用
FastCopy.Copy<Card_UpdateDto, Commons.Entities.Card>(updateDto, modelCard);
其他反射项目中使用过
/// <summary> /// 设置公共属性值 /// </summary> /// <param name="srouce"></param> /// <param name="login"></param> public static void SetPublic(this object srouce,string login) { Type st = srouce.GetType(); var ps = st.GetProperties(); ps.FirstOrDefault(q => q.Name == "CreateMan").SetValue(srouce, login); ps.FirstOrDefault(q => q.Name == "CreateDate").SetValue(srouce, DateTime.Now); } public static void Clone(this object srouce ,object target) { //if (srouce.GetType() != target.GetType()) // return; Type st = srouce.GetType(); Type tt = target.GetType(); var sps = st.GetProperties(); var tps = tt.GetProperties(); foreach(PropertyInfo sp in sps.ToList()) { foreach(PropertyInfo tp in tps) { if(sp.Name == tp.Name) { if(!(sp.GetValue(srouce)==null) && sp.Name != "DocmentGUID" && sp.Name != "ContactGUID" ) sp.SetValue(target, sp.GetValue(srouce)); break; } } } } public static T ConvertTo<T>(this object srouce) where T :new() { Type tt = typeof(T); T re = Activator.CreateInstance<T>(); Type rt = srouce.GetType(); foreach (PropertyInfo rp in rt.GetProperties()) { foreach (PropertyInfo tp in tt.GetProperties()) { if (rp.Name == tp.Name) { if(tp.GetValue(srouce)!=null) rp.SetValue(re, tp.GetValue(srouce)); break; } } } return re; }