C#反思,克隆

时间:2010-04-14 17:31:24

标签: c# reflection clone

假设我有这个包含此方法的类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;

如何第二次调用clone,T是object类型而不是MyOtherClass类型

5 个答案:

答案 0 :(得分:3)

您对clone的第二次(递归)调用将GetValue的结果作为第二个参数传递,其类型为object,因此T为{{1} }}

object

fi.SetValue(tempMyClass, this.clone(fi.GetValue(original), fi.GetValue(original))); GetValue的结果是FieldInfo

鉴于你在所有情况下都传递了两次相同的东西,object方法的设计可能是错误的。你可能不需要泛型。只需使用clone来获取第二个参数的类型信息(如果确实需要第二个参数)。

使用泛型约束返回类型会更有意义,因此在调用端不需要强制转换。您也可以将Clone变为扩展方法,以便它可以应用于任何内容。

另一方面,您尝试做的事情(自动深度克隆)通常不太可能有用。大多数类最终都会保存对他们不拥有的东西的引用,因此如果您克隆这样的对象,最终会意外地克隆一半的应用程序框架。

答案 1 :(得分:1)

试试这个:


    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;

答案 2 :(得分:0)

首先,我同意克隆方法应该是静态的,但我不认为

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类型。使用的泛型类型在编译时确定,并且Objectfi.GetValue的返回类型。

您可以将此静态方法移动到某个静态助手类。

作为旁注,我想说如果你的命名空间中的某个类中有一些集合类型字段(或任何标准的可变复合字段),那么“深度”克隆的实现将无法正常工作。 / p>

答案 3 :(得分:0)

克隆类实例的最佳方法是创建一个委托来完成它。实际上,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;
    }
}

答案 4 :(得分:0)

我尝试使用此处发布的示例克隆实体框架对象,但无济于事。

我用另一种方法制作了扩展方法,现在可以克隆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));
}

我没有检查数组的大小写,但是您也可以为此添加一些代码(例如在link中):

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; 
}