简体   繁体   中英

C# reflection, cloning

Say I have this class Myclass that contains this method:

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

Then this class:

public class MyOtherClass 
{
    public int MyProperty777 { get; set; }
}

when I do this:

MyClass a = new MyClass { 
                        MyProperty = 1, 
                        MySecondProperty = 2, 
                        subClass = new MyOtherClass() { MyProperty777 = -1 } 
                        };
            MyClass b = a.clone(a, a) as MyClass;

how come on the second call to clone, T is of type object and not of type MyOtherClass

Your second (recursive) call to clone passes the result of GetValue as the second argument, which is of type object , and hence T is object .

ie

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

The result of GetValue on a FieldInfo is an object .

Given that you pass the same thing twice in all cases, the design of the clone method is possibly wrong. You probably don't need generics there. Just use obj.GetType() to get the type information of the second argument (if indeed you really need a second argument).

It would make more sense to constrain the return type using generics, so that the cast isn't necessary on the calling side. Also you could make Clone into an extension method so it could apply to anything.

On the other hand, the thing you're trying to do (an automatic deep clone) is unlikely to be generally useful. Most classes end up hold references to things that they don't own, so if you clone such an object, you end up accidentally cloning half of your application framework.

Try this:


    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;

First of all I agree that clone method should be static, but I don't think that

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

is a good idea. I think that better way is to use type of original and get rid of emptyObject parameter at all.

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

Also you have to GetFields on original not on this .

So my method would be

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

Note that I use original.GetType() anyway as inner call would have type T= Object anyway. Used generic type is determined at compilation time and it would be Object as return type of fi.GetValue .

You can move this static method to some static helper class.

As a side note I'd like to say that this implementation of "deep" clone will not work properly if there is some collection-type field (or any standard mutable composite field) in one of classes in your namespace.

Best way to clone an instance of a class is to create a delegate to do it. Indeed, delegate produced by linq expression can access private/internal/protected and public fields. Delegate can be created only once. Keep it in static field in generic class to take benefit of generic lookup resolution instead of dictionary

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

I tried to clone an entity framework object with the examples posted here but nothing worked.

I made an extension method with a different way and now I can clone EF objects:

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

I didn't check for the array cases but you can add some code for that too (like in this 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; 
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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