博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
反射之动态创建对象
阅读量:5825 次
发布时间:2019-06-18

本文共 14974 字,大约阅读时间需要 49 分钟。

前言

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

讨论

 定义一个Person类代码如下

1    public class Person 2     { 3  4         ///  5         /// 年龄 6         ///  7         public int Age { get; set; } 8  9         /// 10         /// 姓名11         /// 12         public string Name { get; set; }13 14         /// 15         /// 性别16         /// 17         public bool Gender { get; set; }18 19 20         /// 21         /// 求两个数的和22         /// 23         /// 24         /// 25         /// 
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
() 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 = newPerson(); Person dynew Person() ; 似乎是一样的,那还不如直接实例化调用其方法即可,一想确实是这样,经过再次研究觉得用dynamic只是更加便捷而且代码更加精简,就像用lamda简化而省去了用委托或者匿名方法一样!下面就以一个实例来说明不得不用反射来实现,还用上面的Person类,现在继续添加一个 OtherPerson 类:

1     public class OtherPerson2     {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
> _propertiesOnType = new ConcurrentDictionary
>(); 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 called120 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 type151 IDictionary
typeProperties = GetTypeProperties(RealObject.GetType());152 153 // Look for the one we want154 IProperty property;155 if (typeProperties.TryGetValue(propertyName, out property))156 {157 return property;158 }159 160 // The property doesn't exist161 162 // Get a list of supported properties and fields and show them as part of the exception message163 // 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
GetTypeProperties(Type type)172 {173 // First, check if we already have it cached174 IDictionary
typeProperties;175 if (_propertiesOnType.TryGetValue(type, out typeProperties))176 {177 return typeProperties;178 }179 180 // Not cache, so we need to build it181 182 typeProperties = new ConcurrentDictionary
();183 184 // First, add all the properties185 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 fields191 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 fields197 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 time206 _propertiesOnType[type] = typeProperties;207 208 return typeProperties;209 }210 211 private static object InvokeMemberOnType(Type type, object target, string name, object[] args)212 {213 try214 {215 // Try to incoke the method216 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 class226 if (type.BaseType != null)227 {228 return InvokeMemberOnType(type.BaseType, target, name, args);229 }230 231 throw;232 }233 }234 }

扩展方法如下

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

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

本文转自Jeffcky博客园博客,原文链接:http://www.cnblogs.com/CreateMyself/p/4680229.html,如需转载请自行联系原作者

你可能感兴趣的文章
Java I/O操作
查看>>
Tomcat性能调优
查看>>
Android自学--一篇文章基本掌握所有的常用View组件
查看>>
灰度图像和彩色图像
查看>>
FreeMarker-Built-ins for strings
查看>>
argparse - 命令行选项与参数解析(转)
查看>>
修改上一篇文章的node.js代码,支持默认页及支持中文
查看>>
spring-boot支持websocket
查看>>
菜鸟笔记(一) - Java常见的乱码问题
查看>>
我理想中的前端工作流
查看>>
记一次Git异常操作:将多个repository合并到同一repository的同一分支
查看>>
Chrome 广告屏蔽功能不影响浏览器性能
查看>>
Android状态栏实现沉浸式模式
查看>>
使用Openfiler搭建ISCSI网络存储
查看>>
学生名单
查看>>
(转) 多模态机器翻译
查看>>
【官方文档】Nginx负载均衡学习笔记(三) TCP和UDP负载平衡官方参考文档
查看>>
矩阵常用归一化
查看>>
Oracle常用函数总结
查看>>
【聚能聊有奖话题】Boring隧道掘进机完成首段挖掘,离未来交通还有多远?
查看>>