• 反射之动态创建对象


    前言

    C#有关反射的话题已经是个老生常谈的话题,也许园友一看这标题都不屑去看了,但是既然拿出来讲必有讲之道理,当然,不喜勿喷,高手请绕道!直入话题。

    讨论

     定义一个Person类代码如下

     1    public class Person
     2     {
     3 
     4         /// <summary>
     5         /// 年龄
     6         /// </summary>
     7         public int Age { get; set; }
     8 
     9         /// <summary>
    10         /// 姓名
    11         /// </summary>
    12         public string Name { get; set; }
    13 
    14         /// <summary>
    15         /// 性别
    16         /// </summary>
    17         public bool Gender { get; set; }
    18 
    19 
    20         /// <summary>
    21         /// 求两个数的和
    22         /// </summary>
    23         /// <param name="num1"></param>
    24         /// <param name="num2"></param>
    25         /// <returns></returns>
    26         public int Add(int num1,int num2)
    27         {
    28             return num1 + num2;
    29         }
    30     }

    那么现在怎么动态获取该对象并打印该对象?啊,用反射动态获取呗,ok,实现如下!

    1             Type person = typeof(Person);
    2 
    3             Person t = (Person)Activator.CreateInstance(person) as Person;
    4 
    5             Console.WriteLine(t.ToString());

    完全没错,在黑框框中运行输出入下:

    接下来小小改动一下,在Person类中添加一个构造函数

    1         public Person(string age, string name, bool gender)
    2         {
    3             this.Age = age;
    4             this.Name = name;
    5             this.Gender = gender;
    6         }

    此时我们再来运行看看!!什么鬼,怎么出现错误了???

    吓我一跳,平常来个未将对象设置到对象的实例那是见怪不怪了,出现这个容我想想,无参构造函数似乎暗示着什么,突然醒悟对象不都默认有个无参的构造函数吗,啊,shit,原来是因为我定义了一个有参数的构造函数,用Activator.CreateInstance动态创建对象调用的无参构造函数啊,紧接着我将鼠标放在该方法跟前,都告诉我了写着 使用指定类型的默认构造函数来创建该类型的实例 ,知道错误所在了,关键是怎么去解决,要是类中写了有参数的构造函数,现在想要反射来动态创建对象岂不是不能够了吗?继续想,记得在javascript中虽然不能够像C#实现重载,当然js也不存在重载,但是可以根据arguments.length来近似于实现所谓的重载,这里同样可不可以根据构造函数的个数来实现呢?有了这个想法就开干,当看到这个GetConstructors方法心底就舒坦起来了,经过反复查看其方法改造控制台后的代码如下:

    1             var length = 0;
    2             Person p = null;
    3             Type person = typeof(Person);
    4             var gc = person.GetConstructors();
    5             foreach (var c in gc)
    6             {
    7                 length = c.GetParameters().Length;
    8             }

     现在获取到了构造函数的长度即可以根据参数的个数来进行创建对象,离解决问题更进一步了,这时我想到如果我参数个数相同,怎么知道我是调用哪个构造函数呢?对,根据参数的类型,所以现在问题上升到怎么确定我要传递参数的类型呢?看看构造函数的属性  ConstructorInfo 中有没有什么方法可以定义参数类型,皇天不负有心人 GetConstructor 方法参数中 有个Type 这就是参数的类型了,然后利用 Invoke 委托对构造函数传入参数获取对象,如下:

     1             ConstructorInfo Constructor = null;
     2 
     3             switch (length)
     4             {
     5                 case 0:
     6                     Constructor = person.GetConstructor(new Type[0]);
     7                     p = Constructor.Invoke(null) as Person;
     8                     break;
     9                 case 1:
    10                     Constructor = person.GetConstructor(new Type[1] { typeof(int) });
    11                     p = Constructor.Invoke(new object[1] { 1 }) as Person;
    12                     break;
    13                 case 2:
    14                     Constructor = person.GetConstructor(new Type[2] { typeof(int), typeof(string) });
    15                     p = Constructor.Invoke(new object[2] { 1, "嘿嘿" }) as Person;
    16                     break;
    17                 case 3:     
    18                     Constructor = person.GetConstructor(new Type[3] { typeof(int), typeof(string), typeof(bool) });
    19                     p = Constructor.Invoke(new object[3] { 1, "嘿嘿", false }) as Person;
    20                     break;
    21                 default:
    22                     break;
    23             }
    24 
    25             //Person t = (Person)Activator.CreateInstance(person) as Person;
    26             Console.WriteLine(p.ToString());

    同样得到上述结果打印出:反射之动态创建对象.Person,ok,终于解决了,完美!

    拓展

    在上述过程中用到委托Invoke再传入参数到其中,鉴于此对于反射,参考代码改善建议利用dynamic关键字简化反射实现。下面用例子说明,利用反射计算Person类中方法计算两个数的和。利用反射立马能够写出

    1     Person dy = new Person();
    2     var p= typeof(Person).GetMethod("Add");
    3     Convert.ToInt32(p.Invoke(dy, new object[] { 30, 40 });)

    如果利用 dynamic 关键字能够更加精简而且更加优美 

    1      dynamic dy = new Person(); 
    2      dy.Add(30, 40); 

    总结 

      (1)利用反射动态创建对象两种方法

        【1】利用Activator.CreateInstance,前提是调用对象的默认无参构造函数

        【2】利用构造器来动态创建对象

      (2)利用dynamic关键字来简化反射实现

    补充1

    用构造器将其进行封装为如下,其中用时需要手动添加参数类型以及参数默认值

     1       public static T GetEntity<T>() where T : class
     2         {
     3             T entity = null;
     4             var length = 0;
     5             Type t = typeof(T);
     6             var gc = t.GetConstructors();
     7 
     8             foreach (var c in gc)
     9             {
    10                 length = c.GetParameters().Length;
    11             }
    12             ConstructorInfo Constructor = null;
    13            
    14             switch (length)
    15             {
    16                 case 0:
    17                     Constructor = t.GetConstructor(new Type[0]);
    18                     entity = Constructor.Invoke(null) as T;
    19                     break;
    20                 case 1:
    21 
    22                     Constructor = t.GetConstructor(new Type[1] { typeof(int) });
    23                     entity = Constructor.Invoke(new object[1] { 0 }) as T;
    24                     break;
    25                 case 2:          
    26                     Constructor = t.GetConstructor(new Type[2] { typeof(int), typeof(string) });
    27                     entity = Constructor.Invoke(new object[2] { 0, null }) as T;
    28                     break;
    29                 case 3:
    30                     Constructor = t.GetConstructor(new Type[3] { typeof(int), typeof(string), typeof(bool) });
    31                     entity = Constructor.Invoke(new object[3] { 0, null, false }) as T;
    32                     break;
    33                 default:
    34                     break;
    35             }
    36 
    37             return entity;
    38         }

     补充2

    上述提到用 dynamic 来简化反射的实现,对于园友提出 对于反射无法获取到class是什么 ,像 dynamic dy = new Person(); Person dy= new Person() ; 似乎是一样的,那还不如直接实例化调用其方法即可,一想确实是这样,经过再次研究觉得用dynamic只是更加便捷而且代码更加精简,就像用lamda简化而省去了用委托或者匿名方法一样!下面就以一个实例来说明不得不用反射来实现,还用上面的Person类,现在继续添加一个 OtherPerson 类:

    1     public class OtherPerson
    2     {
    3         private int OtherAge { get; set; }
    4     }

    然后在Person类中添加一个返回值为OtherPerson的私有方法 GetOtherPerson 

    1        private OtherPerson GetOtherPerson()
    2         {
    3             OtherPerson op = new OtherPerson();
    4             return op;
    5         }

    现在想调用 GetOtherPerson 方法获取 OtherPerson 类中的私有字段 OtherAge  ,别告诉我直接实例化Person对象,再调用,因为是私有现在无法实现,所以马上想到的是通过反射来实现获取这个方法再同样实现获取私有字段

    1             Person p1 = new Person();
    2             var p = typeof(Person).InvokeMember("GetOtherPerson", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, p1, null);
    3             var propInfo = p.GetType().GetProperty("OtherAge", BindingFlags.Instance | BindingFlags.NonPublic);
    4             var age = (int)propInfo.GetValue(p, null);

    一大片代码看起来是不是很恶心,接下来我们将代码进行改进,使其便捷化,上述提到用dynamic来实现,所以就来吧!

      var age = ((dynamic)p1).GetOtherPerson().OtherAge; 就一行代码是不是很简单,再次说明了dynamic的优美和简洁,so  perfect!那我们运行下看看吧,oh,往往在你最得意的时候结果就会给你当头一棒,出错了!如下

    这保护的级别有点忒高,那必须攻破你的堡垒!弄了一下午最终还是google给出了一位前辈已经这么做过的解决方案!重写了dynamic的基类DynamicObject,接着就写了它的扩展方法,代码如下:

      1    public class PrivateReflectionDynamicObject : DynamicObject
      2     {
      3 
      4         private static IDictionary<Type, IDictionary<string, IProperty>> _propertiesOnType = new ConcurrentDictionary<Type, IDictionary<string, IProperty>>();
      5 
      6         // Simple abstraction to make field and property access consistent
      7         interface IProperty
      8         {
      9             string Name { get; }
     10             object GetValue(object obj, object[] index);
     11             void SetValue(object obj, object val, object[] index);
     12         }
     13 
     14         // IProperty implementation over a PropertyInfo
     15         class Property : IProperty
     16         {
     17             internal PropertyInfo PropertyInfo { get; set; }
     18 
     19             string IProperty.Name
     20             {
     21                 get
     22                 {
     23                     return PropertyInfo.Name;
     24                 }
     25             }
     26 
     27             object IProperty.GetValue(object obj, object[] index)
     28             {
     29                 return PropertyInfo.GetValue(obj, index);
     30             }
     31 
     32             void IProperty.SetValue(object obj, object val, object[] index)
     33             {
     34                 PropertyInfo.SetValue(obj, val, index);
     35             }
     36         }
     37 
     38         // IProperty implementation over a FieldInfo
     39         class Field : IProperty
     40         {
     41             internal FieldInfo FieldInfo { get; set; }
     42 
     43             string IProperty.Name
     44             {
     45                 get
     46                 {
     47                     return FieldInfo.Name;
     48                 }
     49             }
     50 
     51 
     52             object IProperty.GetValue(object obj, object[] index)
     53             {
     54                 return FieldInfo.GetValue(obj);
     55             }
     56 
     57             void IProperty.SetValue(object obj, object val, object[] index)
     58             {
     59                 FieldInfo.SetValue(obj, val);
     60             }
     61         }
     62 
     63 
     64         private object RealObject { get; set; }
     65         private const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
     66 
     67         internal static object WrapObjectIfNeeded(object o)
     68         {
     69             // Don't wrap primitive types, which don't have many interesting internal APIs
     70             if (o == null || o.GetType().IsPrimitive || o is string)
     71                 return o;
     72 
     73             return new PrivateReflectionDynamicObject() { RealObject = o };
     74         }
     75 
     76         public override bool TryGetMember(GetMemberBinder binder, out object result)
     77         {
     78             IProperty prop = GetProperty(binder.Name);
     79 
     80             // Get the property value
     81             result = prop.GetValue(RealObject, index: null);
     82 
     83             // Wrap the sub object if necessary. This allows nested anonymous objects to work.
     84             result = WrapObjectIfNeeded(result);
     85 
     86             return true;
     87         }
     88 
     89         public override bool TrySetMember(SetMemberBinder binder, object value)
     90         {
     91             IProperty prop = GetProperty(binder.Name);
     92 
     93             // Set the property value
     94             prop.SetValue(RealObject, value, index: null);
     95 
     96             return true;
     97         }
     98 
     99         public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    100         {
    101             // The indexed property is always named "Item" in C#
    102             IProperty prop = GetIndexProperty();
    103             result = prop.GetValue(RealObject, indexes);
    104 
    105             // Wrap the sub object if necessary. This allows nested anonymous objects to work.
    106             result = WrapObjectIfNeeded(result);
    107 
    108             return true;
    109         }
    110 
    111         public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
    112         {
    113             // The indexed property is always named "Item" in C#
    114             IProperty prop = GetIndexProperty();
    115             prop.SetValue(RealObject, value, indexes);
    116             return true;
    117         }
    118 
    119         // Called when a method is called
    120         public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    121         {
    122             result = InvokeMemberOnType(RealObject.GetType(), RealObject, binder.Name, args);
    123 
    124             // Wrap the sub object if necessary. This allows nested anonymous objects to work.
    125             result = WrapObjectIfNeeded(result);
    126 
    127             return true;
    128         }
    129 
    130         public override bool TryConvert(ConvertBinder binder, out object result)
    131         {
    132             result = Convert.ChangeType(RealObject, binder.Type);
    133             return true;
    134         }
    135 
    136         public override string ToString()
    137         {
    138             return RealObject.ToString();
    139         }
    140 
    141         private IProperty GetIndexProperty()
    142         {
    143             // The index property is always named "Item" in C#
    144             return GetProperty("Item");
    145         }
    146 
    147         private IProperty GetProperty(string propertyName)
    148         {
    149 
    150             // Get the list of properties and fields for this type
    151             IDictionary<string, IProperty> typeProperties = GetTypeProperties(RealObject.GetType());
    152 
    153             // Look for the one we want
    154             IProperty property;
    155             if (typeProperties.TryGetValue(propertyName, out property))
    156             {
    157                 return property;
    158             }
    159 
    160             // The property doesn't exist
    161 
    162             // Get a list of supported properties and fields and show them as part of the exception message
    163             // For fields, skip the auto property backing fields (which name start with <)
    164             var propNames = typeProperties.Keys.Where(name => name[0] != '<').OrderBy(name => name);
    165             throw new ArgumentException(
    166                 String.Format(
    167                 "The property {0} doesn't exist on type {1}. Supported properties are: {2}",
    168                 propertyName, RealObject.GetType(), String.Join(", ", propNames)));
    169         }
    170 
    171         private static IDictionary<string, IProperty> GetTypeProperties(Type type)
    172         {
    173             // First, check if we already have it cached
    174             IDictionary<string, IProperty> typeProperties;
    175             if (_propertiesOnType.TryGetValue(type, out typeProperties))
    176             {
    177                 return typeProperties;
    178             }
    179 
    180             // Not cache, so we need to build it
    181 
    182             typeProperties = new ConcurrentDictionary<string, IProperty>();
    183 
    184             // First, add all the properties
    185             foreach (PropertyInfo prop in type.GetProperties(bindingFlags).Where(p => p.DeclaringType == type))
    186             {
    187                 typeProperties[prop.Name] = new Property() { PropertyInfo = prop };
    188             }
    189 
    190             // Now, add all the fields
    191             foreach (FieldInfo field in type.GetFields(bindingFlags).Where(p => p.DeclaringType == type))
    192             {
    193                 typeProperties[field.Name] = new Field() { FieldInfo = field };
    194             }
    195 
    196             // Finally, recurse on the base class to add its fields
    197             if (type.BaseType != null)
    198             {
    199                 foreach (IProperty prop in GetTypeProperties(type.BaseType).Values)
    200                 {
    201                     typeProperties[prop.Name] = prop;
    202                 }
    203             }
    204 
    205             // Cache it for next time
    206             _propertiesOnType[type] = typeProperties;
    207 
    208             return typeProperties;
    209         }
    210 
    211         private static object InvokeMemberOnType(Type type, object target, string name, object[] args)
    212         {
    213             try
    214             {
    215                 // Try to incoke the method
    216                 return type.InvokeMember(
    217                     name,
    218                     BindingFlags.InvokeMethod | bindingFlags,
    219                     null,
    220                     target,
    221                     args);
    222             }
    223             catch (MissingMethodException)
    224             {
    225                 // If we couldn't find the method, try on the base class
    226                 if (type.BaseType != null)
    227                 {
    228                     return InvokeMemberOnType(type.BaseType, target, name, args);
    229                 }
    230 
    231                 throw;
    232             }
    233         }
    234     }

    扩展方法如下

    1    public static class PrivateReflectionDynamicObjectExtensions
    2     {
    3         public static dynamic AsDynamic(this object o)
    4         {
    5             return PrivateReflectionDynamicObject.WrapObjectIfNeeded(o);
    6         }
    7     }

    最后调用拓展方法  var age = p1.AsDynamic().GetOtherPerson().OtherAge; 成功!所以有时候使用dynamic使得代码变得更加优美而用反射代码繁多而且显得非常臃肿,通过再一次学习dynamic,对此也深信不疑!

  • 相关阅读:
    P2048 [NOI2010]超级钢琴 (rmq +堆+贪心)
    题解 P4799 【[CEOI2015 Day2]世界冰球锦标赛】
    洛谷 P1360 [USACO07MAR]Gold Balanced Lineup G (前缀和+思维)
    洛谷 P4880 抓住czx
    洛谷 P2471 [SCOI2007]降雨量
    洛谷 P4688 [Ynoi2016]掉进兔子洞 (看到题目背景,galgame玩家狂喜)
    洛谷 P2101 命运石之门的选择 (分治)
    留言版
    【游记】OI 2020(在更)
    类欧几里得算法
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/4680229.html
Copyright © 2020-2023  润新知