【问题标题】:C# reflection, cloningC# 反射、克隆
【发布时间】:2011-02-08 00:52:43
【问题描述】:

假设我有这个类 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 类型

【问题讨论】:

  • 你指的是哪个clone的电话?

标签: c# reflection clone


【解决方案1】:

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

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

GetValueFieldInfo 上的结果是object

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

使用泛型约束返回类型会更有意义,这样调用端就不需要强制转换了。你也可以将 Clone 变成一个扩展方法,这样它就可以应用于任何东西。

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

【讨论】:

    【解决方案2】:

    试试这个:

    
        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】:

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

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

      是个好主意。我认为更好的方法是使用原始类型并完全摆脱 emptyObject 参数。

      object tempMyClass = Activator.CreateInstance(original.GetType());
      

      您还必须在originalGetFields 而不是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 的返回类型。

      您可以将此静态方法移至某个静态帮助程序类。

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

      【讨论】:

        【解决方案4】:

        克隆类实例的最佳方法是创建一个委托来执行此操作。实际上,由 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;
            }
        }
        

        【讨论】:

        • 简要说明你在做什么将有助于提问者更好地理解
        • 克隆类实例的最佳方法是创建一个委托来执行此操作。实际上,由 linq 表达式生成的委托可以访问私有/内部/受保护和公共字段。代表只能创建一次。将其保存在泛型类的静态字段中,以利用泛型查找解析而不是字典。
        • 请编辑您的答案并添加您的解释。
        【解决方案5】:

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

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

        【讨论】:

          猜你喜欢
          • 2013-06-28
          • 2016-01-27
          • 1970-01-01
          • 2018-08-31
          • 2017-07-12
          • 2015-03-27
          • 1970-01-01
          • 2023-03-27
          • 1970-01-01
          相关资源
          最近更新 更多