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