反射之動態創建對象


前言

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM