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