[英]C# reflection, cloning
假设我有这个包含此方法的类Myclass:
public class MyClass
{
public int MyProperty { get; set; }
public int MySecondProperty { get; set; }
public MyOtherClass subClass { get; set; }
public object clone<T>(object original, T emptyObj)
{
FieldInfo[] fis = this.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
object tempMyClass = Activator.CreateInstance(typeof(T));
foreach (FieldInfo fi in fis)
{
if (fi.FieldType.Namespace != original.GetType().Namespace)
fi.SetValue(tempMyClass, fi.GetValue(original));
else
fi.SetValue(tempMyClass, this.clone(fi.GetValue(original), fi.GetValue(original)));
}
return tempMyClass;
}
}
那么这堂课:
public class MyOtherClass
{
public int MyProperty777 { get; set; }
}
当我这样做:
MyClass a = new MyClass {
MyProperty = 1,
MySecondProperty = 2,
subClass = new MyOtherClass() { MyProperty777 = -1 }
};
MyClass b = a.clone(a, a) as MyClass;
如何进行第二次克隆调用,T是类型为object而不是MyOtherClass类型
您对clone
第二次(递归)调用将GetValue
的结果作为第二个参数传递,该参数类型为object
,因此T
是object
。
即
fi.SetValue(tempMyClass, this.clone(fi.GetValue(original), fi.GetValue(original)));
FieldInfo
上GetValue
的结果是一个object
。
鉴于你在所有情况下都传递了两次相同的东西, clone
方法的设计可能是错误的。 你可能不需要泛型。 只需使用obj.GetType()
来获取第二个参数的类型信息(如果确实你真的需要第二个参数)。
使用泛型约束返回类型会更有意义,因此在调用端不需要强制转换。 你也可以将Clone变成一个扩展方法,这样它就可以适用于任何东西。
另一方面,您尝试做的事情(自动深度克隆)通常不太有用。 大多数类最终会保留对它们不拥有的东西的引用,因此如果克隆这样的对象,最终会意外地克隆一半的应用程序框架。
尝试这个:
public static class Cloner
{
public static T clone(this T item)
{
FieldInfo[] fis = item.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
object tempMyClass = Activator.CreateInstance(item.GetType());
foreach (FieldInfo fi in fis)
{
if (fi.FieldType.Namespace != item.GetType().Namespace)
fi.SetValue(tempMyClass, fi.GetValue(item));
else
{
object obj = fi.GetValue(item);
fi.SetValue(tempMyClass, obj.clone());
}
}
return (T)tempMyClass;
}
}
MyClass b = a.clone() as MyClass;
首先,我同意克隆方法应该是静态的,但我不这么认为
object tempMyClass = Activator.CreateInstance(typeof(T));
是个好主意。 我认为更好的方法是使用原始类型并完全摆脱emptyObject参数。
object tempMyClass = Activator.CreateInstance(original.GetType());
你也需要GetFields
对original
不是this
。
所以我的方法是
public static T clone<T>(T original)
{
T tempMyClass = (T)Activator.CreateInstance(original.GetType());
FieldInfo[] fis = original.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo fi in fis)
{
object fieldValue = fi.GetValue(original);
if (fi.FieldType.Namespace != original.GetType().Namespace)
fi.SetValue(tempMyClass, fieldValue);
else
fi.SetValue(tempMyClass, clone(fieldValue));
}
return tempMyClass;
}
请注意,我仍然使用original.GetType()
,因为内部调用无论如何都将具有类型T = Object
。 使用的泛型类型在编译时确定,它将是Object
作为fi.GetValue
返回类型。
您可以将此静态方法移动到某个静态助手类。
作为旁注,我想说如果在命名空间中的某个类中有一些集合类型字段(或任何标准的可变复合字段),则“深度”克隆的实现将无法正常工作。
克隆类实例的最佳方法是创建一个委托来执行它。 实际上,linq表达式生成的委托可以访问私有/内部/受保护和公共字段。 委托只能创建一次。 将它保存在泛型类的静态字段中,以利用通用查找解析而不是字典
/// <summary>
/// Help to find metadata from expression instead of string declaration to improve reflection reliability.
/// </summary>
static public class Metadata
{
/// <summary>
/// Identify method from method call expression.
/// </summary>
/// <typeparam name="T">Type of return.</typeparam>
/// <param name="expression">Method call expression.</param>
/// <returns>Method.</returns>
static public MethodInfo Method<T>(Expression<Func<T>> expression)
{
return (expression.Body as MethodCallExpression).Method;
}
}
/// <summary>
/// Help to find metadata from expression instead of string declaration to improve reflection reliability.
/// </summary>
/// <typeparam name="T">Type to reflect.</typeparam>
static public class Metadata<T>
{
/// <summary>
/// Cache typeof(T) to avoid lock.
/// </summary>
static public readonly Type Type = typeof(T);
/// <summary>
/// Only used as token in metadata expression.
/// </summary>
static public T Value { get { throw new InvalidOperationException(); } }
}
/// <summary>
/// Used to clone instance of any class.
/// </summary>
static public class Cloner
{
/// <summary>
/// Define cloner implementation of a specific type.
/// </summary>
/// <typeparam name="T">Type to clone.</typeparam>
static private class Implementation<T>
where T : class
{
/// <summary>
/// Delegate create at runtime to clone.
/// </summary>
static public readonly Action<T, T> Clone = Cloner.Implementation<T>.Compile();
/// <summary>
/// Way to emit delegate without static constructor to avoid performance issue.
/// </summary>
/// <returns>Delegate used to clone.</returns>
static public Action<T, T> Compile()
{
//Define source and destination parameter used in expression.
var _source = Expression.Parameter(Metadata<T>.Type);
var _destination = Expression.Parameter(Metadata<T>.Type);
//Clone method maybe need more than one statement.
var _body = new List<Expression>();
//Clone all fields of entire hierarchy.
for (var _type = Metadata<T>.Type; _type != null; _type = _type.BaseType)
{
//Foreach declared fields in current type.
foreach (var _field in _type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
//Assign destination field using source field.
_body.Add(Expression.Assign(Expression.Field(_destination, _field), Expression.Field(_source, _field)));
}
}
//Compile expression to provide clone method.
return Expression.Lambda<Action<T, T>>(Expression.Block(_body), _source, _destination).Compile();
}
}
/// <summary>
/// Keep instance of generic definition of clone method to improve performance in reflection call case.
/// </summary>
static private readonly MethodInfo Method = Metadata.Method(() => Cloner.Clone(Metadata<object>.Value)).GetGenericMethodDefinition();
static public T Clone<T>(T instance)
where T : class
{
//Nothing to clone.
if (instance == null) { return null; }
//Identify instace type.
var _type = instance.GetType();
//if T is an interface, instance type might be a value type and it is not needed to clone value type.
if (_type.IsValueType) { return instance; }
//Instance type match with generic argument.
if (_type == Metadata<T>.Type)
{
//Instaitate clone without executing a constructor.
var _clone = FormatterServices.GetUninitializedObject(_type) as T;
//Call delegate emitted once by linq expreesion to clone fields.
Cloner.Implementation<T>.Clone(instance, _clone);
//Return clone.
return _clone;
}
//Reflection call case when T is not target Type (performance overhead).
return Cloner.Method.MakeGenericMethod(_type).Invoke(null, new object[] { instance }) as T;
}
}
我尝试使用这里发布的示例来克隆实体框架对象,但没有任何效果。
我用不同的方式创建了一个扩展方法,现在我可以克隆EF对象:
public static T CloneObject<T>(this T source)
{
if (source == null || source.GetType().IsSimple())
return source;
object clonedObj = Activator.CreateInstance(source.GetType());
var properties = source.GetType().GetProperties();
foreach (var property in properties)
{
try
{
property.SetValue(clonedObj, property.GetValue(source));
}
catch { }
}
return (T)clonedObj;
}
public static bool IsSimple(this Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
// nullable type, check if the nested type is simple.
return IsSimple(type.GetGenericArguments()[0]);
}
return !type.IsClass
|| type.IsPrimitive
|| type.IsEnum
|| type.Equals(typeof(string))
|| type.Equals(typeof(decimal));
}
我没有检查数组的情况,但你也可以为它添加一些代码(比如在这个链接中 ):
else if (type.IsArray)
{
Type typeElement = Type.GetType(type.FullName.Replace("[]", string.Empty));
var array = obj as Array;
Array copiedArray = Array.CreateInstance(typeElement, array.Length);
for (int i = 0; i < array.Length; i++)
{
// Get the deep clone of the element in the original array and assign the
// clone to the new array.
copiedArray.SetValue(CloneProcedure(array.GetValue(i)), i);
}
return copiedArray;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.