C # отражение, клонирование - PullRequest
1 голос
/ 14 апреля 2010

Скажите, у меня есть этот класс 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

Ответы [ 5 ]

3 голосов
/ 14 апреля 2010

Ваш второй (рекурсивный) вызов clone передает результат GetValue в качестве второго аргумента, который имеет тип object, и, следовательно, T равен object.

т.е.

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

Результат GetValue на FieldInfo равен object.

Учитывая, что во всех случаях вы передаете одно и то же дважды, возможно, метод clone ошибочен. Вам, вероятно, там не нужны дженерики. Просто используйте obj.GetType(), чтобы получить информацию о типе второго аргумента (если вам действительно нужен второй аргумент).

Было бы более разумно ограничить тип возвращаемого значения с помощью обобщений, чтобы приведение не было необходимости на вызывающей стороне. Также вы можете превратить Clone в метод расширения, чтобы он мог применяться ко всему.

С другой стороны, то, что вы пытаетесь сделать (автоматический глубокий клон), вряд ли будет вообще полезным. Большинство классов в конечном итоге содержат ссылки на вещи, которыми они не владеют, поэтому, если вы клонируете такой объект, вы в конечном итоге случайно клонируете половину своей прикладной инфраструктуры.

1 голос
/ 14 апреля 2010

Попробуйте это:


    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;
0 голосов
/ 13 сентября 2018

Я пытался клонировать объект каркаса сущности с примерами, размещенными здесь, но ничего не получалось.

Я создал метод расширения другим способом, и теперь я могу клонировать объекты 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; 
} 
0 голосов
/ 10 июня 2015

Лучший способ клонировать экземпляр класса - создать для этого делегата. Действительно, делегат, созданный выражением 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;
    }
}
0 голосов
/ 14 апреля 2010

Прежде всего, я согласен, что метод клонирования должен быть статическим, но я не думаю, что

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.

Вы можете переместить этот статический метод в некоторый статический вспомогательный класс.

В качестве примечания я хотел бы сказать, что эта реализация "глубокого" клона не будет работать должным образом, если в одном из классов в вашем пространстве имен есть какое-либо поле типа коллекции (или любое стандартное изменяемое составное поле).

...