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个吧)


  1. Instance:获取实例方法
  2. NonPublic: 获取非公有方法
  3. Public: 获取共有方法
  4. 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;
        }

 

posted @ 2020-08-23 20:57  netlock  阅读(604)  评论(0编辑  收藏  举报