您的位置:首页 > 其它

反射之动态创建对象

2015-07-27 23:18 519 查看

前言

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

讨论

定义一个Person类代码如下

public class Person
{

/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }

/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }

/// <summary>
/// 性别
/// </summary>
public bool Gender { get; set; }

/// <summary>
/// 求两个数的和
/// </summary>
/// <param name="num1"></param>
/// <param name="num2"></param>
/// <returns></returns>
public int Add(int num1,int num2)
{
return num1 + num2;
}
}


那么现在怎么动态获取该对象并打印该对象?啊,用反射动态获取呗,ok,实现如下!

Type person = typeof(Person);

Person t = (Person)Activator.CreateInstance(person) as Person;

Console.WriteLine(t.ToString());


完全没错,在黑框框中运行输出入下:



接下来小小改动一下,在Person类中添加一个构造函数

public Person(string age, string name, bool gender)
{
this.Age = age;
this.Name = name;
this.Gender = gender;
}


此时我们再来运行看看!!什么鬼,怎么出现错误了???



吓我一跳,平常来个未将对象设置到对象的实例那是见怪不怪了,出现这个容我想想,无参构造函数似乎暗示着什么,突然醒悟对象不都默认有个无参的构造函数吗,啊,shit,原来是因为我定义了一个有参数的构造函数,用Activator.CreateInstance动态创建对象调用的无参构造函数啊,紧接着我将鼠标放在该方法跟前,都告诉我了写着 使用指定类型的默认构造函数来创建该类型的实例 ,知道错误所在了,关键是怎么去解决,要是类中写了有参数的构造函数,现在想要反射来动态创建对象岂不是不能够了吗?继续想,记得在javascript中虽然不能够像C#实现重载,当然js也不存在重载,但是可以根据arguments.length来近似于实现所谓的重载,这里同样可不可以根据构造函数的个数来实现呢?有了这个想法就开干,当看到这个GetConstructors方法心底就舒坦起来了,经过反复查看其方法改造控制台后的代码如下:

var length = 0;
Person p = null;
Type person = typeof(Person);
var gc = person.GetConstructors();
foreach (var c in gc)
{
length = c.GetParameters().Length;
}


现在获取到了构造函数的长度即可以根据参数的个数来进行创建对象,离解决问题更进一步了,这时我想到如果我参数个数相同,怎么知道我是调用哪个构造函数呢?对,根据参数的类型,所以现在问题上升到怎么确定我要传递参数的类型呢?看看构造函数的属性 ConstructorInfo 中有没有什么方法可以定义参数类型,皇天不负有心人 GetConstructor 方法参数中 有个Type 这就是参数的类型了,然后利用 Invoke 委托对构造函数传入参数获取对象,如下:

ConstructorInfo Constructor = null;

switch (length)
{
case 0:
Constructor = person.GetConstructor(new Type[0]);
p = Constructor.Invoke(null) as Person;
break;
case 1:
Constructor = person.GetConstructor(new Type[1] { typeof(int) });
p = Constructor.Invoke(new object[1] { 1 }) as Person;
break;
case 2:
Constructor = person.GetConstructor(new Type[2] { typeof(int), typeof(string) });
p = Constructor.Invoke(new object[2] { 1, "嘿嘿" }) as Person;
break;
case 3:
Constructor = person.GetConstructor(new Type[3] { typeof(int), typeof(string), typeof(bool) });
p = Constructor.Invoke(new object[3] { 1, "嘿嘿", false }) as Person;
break;
default:
break;
}

//Person t = (Person)Activator.CreateInstance(person) as Person;
Console.WriteLine(p.ToString());


同样得到上述结果打印出:反射之动态创建对象.Person,ok,终于解决了,完美!

拓展

在上述过程中用到委托Invoke再传入参数到其中,鉴于此对于反射,参考代码改善建议利用dynamic关键字简化反射实现。下面用例子说明,利用反射计算Person类中方法计算两个数的和。利用反射立马能够写出

Person dy = new Person();
var p= typeof(Person).GetMethod("Add");
Convert.ToInt32(p.Invoke(dy, new object[] { 30, 40 });)


如果利用 dynamic 关键字能够更加精简而且更加优美

dynamic dy = new Person();
dy.Add(30, 40);


总结

  (1)利用反射动态创建对象两种方法

    【1】利用Activator.CreateInstance,前提是调用对象的默认无参构造函数

    【2】利用构造器来动态创建对象

  (2)利用dynamic关键字来简化反射实现

补充1

用构造器将其进行封装为如下,其中用时需要手动添加参数类型以及参数默认值

public static T GetEntity<T>() where T : class
{
T entity = null;
var length = 0;
Type t = typeof(T);
var gc = t.GetConstructors();

foreach (var c in gc)
{
length = c.GetParameters().Length;
}
ConstructorInfo Constructor = null;

switch (length)
{
case 0:
Constructor = t.GetConstructor(new Type[0]);
entity = Constructor.Invoke(null) as T;
break;
case 1:

Constructor = t.GetConstructor(new Type[1] { typeof(int) });
entity = Constructor.Invoke(new object[1] { 0 }) as T;
break;
case 2:
Constructor = t.GetConstructor(new Type[2] { typeof(int), typeof(string) });
entity = Constructor.Invoke(new object[2] { 0, null }) as T;
break;
case 3:
Constructor = t.GetConstructor(new Type[3] { typeof(int), typeof(string), typeof(bool) });
entity = Constructor.Invoke(new object[3] { 0, null, false }) as T;
break;
default:
break;
}

return entity;
}


补充2

上述提到用 dynamic 来简化反射的实现,对于园友提出 对于反射无法获取到class是什么 ,像 dynamic dy = new Person(); Person dy= new Person() ; 似乎是一样的,那还不如直接实例化调用其方法即可,一想确实是这样,经过再次研究觉得用dynamic只是更加便捷而且代码更加精简,就像用lamda简化而省去了用委托或者匿名方法一样!下面就以一个实例来说明不得不用反射来实现,还用上面的Person类,现在继续添加一个 OtherPerson 类:

public class OtherPerson
{
private int OtherAge { get; set; }
}


然后在Person类中添加一个返回值为OtherPerson的私有方法 GetOtherPerson

private OtherPerson GetOtherPerson()
{
OtherPerson op = new OtherPerson();
return op;
}


现在想调用 GetOtherPerson 方法获取 OtherPerson 类中的私有字段 OtherAge ,别告诉我直接实例化Person对象,再调用,因为是私有现在无法实现,所以马上想到的是通过反射来实现获取这个方法再同样实现获取私有字段

Person p1 = new Person();
var p = typeof(Person).InvokeMember("GetOtherPerson", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, p1, null);
var propInfo = p.GetType().GetProperty("OtherAge", BindingFlags.Instance | BindingFlags.NonPublic);
var age = (int)propInfo.GetValue(p, null);


一大片代码看起来是不是很恶心,接下来我们将代码进行改进,使其便捷化,上述提到用dynamic来实现,所以就来吧!

var age = ((dynamic)p1).GetOtherPerson().OtherAge; 就一行代码是不是很简单,再次说明了dynamic的优美和简洁,so perfect!那我们运行下看看吧,oh,往往在你最得意的时候结果就会给你当头一棒,出错了!如下



这保护的级别有点忒高,那必须攻破你的堡垒!弄了一下午最终还是google给出了一位前辈已经这么做过的解决方案!重写了dynamic的基类DynamicObject,接着就写了它的扩展方法,代码如下:

public class PrivateReflectionDynamicObject : DynamicObject
{

private static IDictionary<Type, IDictionary<string, IProperty>> _propertiesOnType = new ConcurrentDictionary<Type, IDictionary<string, IProperty>>();

// Simple abstraction to make field and property access consistent
interface IProperty
{
string Name { get; }
object GetValue(object obj, object[] index);
void SetValue(object obj, object val, object[] index);
}

// IProperty implementation over a PropertyInfo
class Property : IProperty
{
internal PropertyInfo PropertyInfo { get; set; }

string IProperty.Name
{
get
{
return PropertyInfo.Name;
}
}

object IProperty.GetValue(object obj, object[] index)
{
return PropertyInfo.GetValue(obj, index);
}

void IProperty.SetValue(object obj, object val, object[] index)
{
PropertyInfo.SetValue(obj, val, index);
}
}

// IProperty implementation over a FieldInfo
class Field : IProperty
{
internal FieldInfo FieldInfo { get; set; }

string IProperty.Name
{
get
{
return FieldInfo.Name;
}
}

object IProperty.GetValue(object obj, object[] index)
{
return FieldInfo.GetValue(obj);
}

void IProperty.SetValue(object obj, object val, object[] index)
{
FieldInfo.SetValue(obj, val);
}
}

private object RealObject { get; set; }
private const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

internal static object WrapObjectIfNeeded(object o)
{
// Don't wrap primitive types, which don't have many interesting internal APIs
if (o == null || o.GetType().IsPrimitive || o is string)
return o;

return new PrivateReflectionDynamicObject() { RealObject = o };
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
IProperty prop = GetProperty(binder.Name);

// Get the property value
result = prop.GetValue(RealObject, index: null);

// Wrap the sub object if necessary. This allows nested anonymous objects to work.
result = WrapObjectIfNeeded(result);

return true;
}

public override bool TrySetMember(SetMemberBinder binder, object value)
{
IProperty prop = GetProperty(binder.Name);

// Set the property value
prop.SetValue(RealObject, value, index: null);

return true;
}

public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
// The indexed property is always named "Item" in C#
IProperty prop = GetIndexProperty();
result = prop.GetValue(RealObject, indexes);

// Wrap the sub object if necessary. This allows nested anonymous objects to work.
result = WrapObjectIfNeeded(result);

return true;
}

public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
// The indexed property is always named "Item" in C#
IProperty prop = GetIndexProperty();
prop.SetValue(RealObject, value, indexes);
return true;
}

// Called when a method is called
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
result = InvokeMemberOnType(RealObject.GetType(), RealObject, binder.Name, args);

// Wrap the sub object if necessary. This allows nested anonymous objects to work.
result = WrapObjectIfNeeded(result);

return true;
}

public override bool TryConvert(ConvertBinder binder, out object result)
{
result = Convert.ChangeType(RealObject, binder.Type);
return true;
}

public override string ToString()
{
return RealObject.ToString();
}

private IProperty GetIndexProperty()
{
// The index property is always named "Item" in C#
return GetProperty("Item");
}

private IProperty GetProperty(string propertyName)
{

// Get the list of properties and fields for this type
IDictionary<string, IProperty> typeProperties = GetTypeProperties(RealObject.GetType());

// Look for the one we want
IProperty property;
if (typeProperties.TryGetValue(propertyName, out property))
{
return property;
}

// The property doesn't exist

// Get a list of supported properties and fields and show them as part of the exception message
// For fields, skip the auto property backing fields (which name start with <)
var propNames = typeProperties.Keys.Where(name => name[0] != '<').OrderBy(name => name);
throw new ArgumentException(
String.Format(
"The property {0} doesn't exist on type {1}. Supported properties are: {2}",
propertyName, RealObject.GetType(), String.Join(", ", propNames)));
}

private static IDictionary<string, IProperty> GetTypeProperties(Type type)
{
// First, check if we already have it cached
IDictionary<string, IProperty> typeProperties;
if (_propertiesOnType.TryGetValue(type, out typeProperties))
{
return typeProperties;
}

// Not cache, so we need to build it

typeProperties = new ConcurrentDictionary<string, IProperty>();

// First, add all the properties
foreach (PropertyInfo prop in type.GetProperties(bindingFlags).Where(p => p.DeclaringType == type))
{
typeProperties[prop.Name] = new Property() { PropertyInfo = prop };
}

// Now, add all the fields
foreach (FieldInfo field in type.GetFields(bindingFlags).Where(p => p.DeclaringType == type))
{
typeProperties[field.Name] = new Field() { FieldInfo = field };
}

// Finally, recurse on the base class to add its fields
if (type.BaseType != null)
{
foreach (IProperty prop in GetTypeProperties(type.BaseType).Values)
{
typeProperties[prop.Name] = prop;
}
}

// Cache it for next time
_propertiesOnType[type] = typeProperties;

return typeProperties;
}

private static object InvokeMemberOnType(Type type, object target, string name, object[] args)
{
try
{
// Try to incoke the method
return type.InvokeMember(
name,
BindingFlags.InvokeMethod | bindingFlags,
null,
target,
args);
}
catch (MissingMethodException)
{
// If we couldn't find the method, try on the base class
if (type.BaseType != null)
{
return InvokeMemberOnType(type.BaseType, target, name, args);
}

throw;
}
}
}


扩展方法如下

public static class PrivateReflectionDynamicObjectExtensions
{
public static dynamic AsDynamic(this object o)
{
return PrivateReflectionDynamicObject.WrapObjectIfNeeded(o);
}
}


最后调用拓展方法 var age = p1.AsDynamic().GetOtherPerson().OtherAge; 成功!所以有时候使用dynamic使得代码变得更加优美而用反射代码繁多而且显得非常臃肿,通过再一次学习dynamic,对此也深信不疑!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: