反射之动态创建对象

前言

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,对此也深信不疑!

 

上一篇:用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单


下一篇:入驻“云栖社区”