繁体   English   中英

C#反思,克隆

[英]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 ,因此Tobject

fi.SetValue(tempMyClass, this.clone(fi.GetValue(original), fi.GetValue(original)));

FieldInfoGetValue的结果是一个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());

你也需要GetFieldsoriginal不是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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM