代码之家  ›  专栏  ›  技术社区  ›  Enriquev

C反射,克隆

  •  1
  • Enriquev  · 技术社区  · 15 年前

    假设我有一个包含此方法的类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 回复  |  直到 6 年前
        1
  •  3
  •   Daniel Earwicker    15 年前

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

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

    结果 方法 在一 FieldInfo 是一个 对象 .

    考虑到你在所有情况下都会两次通过同一件事, 克隆 方法可能错误。你可能不需要仿制药。只是使用 obj.GetType() 获取第二个参数的类型信息(如果确实需要第二个参数)。

    使用泛型约束返回类型会更有意义,这样调用端就不需要强制转换。此外,还可以将克隆转换为扩展方法,以便它可以应用于任何内容。

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

        2
  •  1
  •   CDSO1    15 年前

    试试这个:

    
        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;
    
        3
  •  0
  •   SergGr    15 年前

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

    object tempMyClass = Activator.CreateInstance(typeof(T));
    

    是个好主意。我认为更好的方法是使用Original类型并完全去掉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 不管怎样。使用的泛型类型在编译时确定,它将 对象 作为返回类型 fi.GetValue .

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

    作为附带说明,我想说的是,如果您的命名空间中的某个类中存在一些集合类型字段(或任何标准可变复合字段),则“深度”克隆的这种实现将无法正常工作。

        4
  •  0
  •   Biswajit_86    10 年前

    克隆类实例的最佳方法是创建一个委托来执行该操作。实际上,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;
        }
    }
    
        5
  •  0
  •   Ninita    6 年前

    我尝试用这里发布的示例克隆实体框架对象,但没有任何效果。

    我用不同的方法创建了一个扩展方法,现在我可以克隆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; 
    }