使用反射克隆对象时的System.StackOverflowException

时间:2015-10-28 05:26:17

标签: c# reflection clone

我正在编写C#表单应用程序,我正在使用以下代码克隆对象:

public static class ObjectExtension
{
    public static object CloneObject(this object objSource)
    {
        //Get the type of source object and create a new instance of that type
        Type typeSource = objSource.GetType();
        object objTarget = Activator.CreateInstance(typeSource);

        //Get all the properties of source object type
        PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

        //Assign all source property to taget object 's properties
        foreach (PropertyInfo property in propertyInfo)
        {
            //Check whether property can be written to
            if (property.CanWrite)
            {
                //check whether property type is value type, enum or string type
                if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(typeof(System.String)))
                {
                    property.SetValue(objTarget, property.GetValue(objSource, null), null);
                }
                //else property type is object/complex types, so need to recursively call this method until the end of the tree is reached
                else
                {
                    object objPropertyValue = property.GetValue(objSource, null);
                    if (objPropertyValue == null)
                    {
                        property.SetValue(objTarget, null, null);
                    }
                    else
                    {
                        property.SetValue(objTarget, objPropertyValue.CloneObject(), null);
                    }
                }
            }
        }
        return objTarget;
    }
}

我的一些对象有父对象,这些父对象有集合。因此,我得到以下例外:

  

未处理的类型' System.StackOverflowException'   发生在CustomWebpageObjectCreator.exe

如何防止这种情况发生?

是否可以使用属性修饰对象中的某些特定属性,以便CloneObject代码不会尝试克隆该属性?如果是这样,有人可以帮我提供代码吗?如果没有,我应该如何修改我的代码?

1 个答案:

答案 0 :(得分:1)

您可以使用字典来记忆所有原始实例以克隆它:

public static class ObjectExtension
{
    private static readonly MethodInfo MemberwiseCloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);

    public static object CloneObject(this object objSource)
    {
        return InnerCloneObject(objSource, new Dictionary<object, object>(ObjectReferenceEqualityComparer<object>.Default)));
    }


    private static object InnerCloneObject(this object objSource, IDictionary<object, object> alreadyVisitedInstances)
    {
        if (objSource == null)
        {
            return null;
        }

        var instanceType = objSource.GetType();
        if (IsPrimitive(instanceType) || instanceType.IsMarshalByRef)
        {
            return objSource;
        }

        object clonedInstance;
        if (alreadyVisitedInstances.TryGetValue(objSource, out clonedInstance))
        {
            // We already have a clone...
            return clonedInstance;
        }

        if (typeof(Delegate).IsAssignableFrom(instanceType))
        {
            // Copying delegates is very hard...
            return null;
        }

        // Don't use activator because it needs a parameterless constructor.
        objTarget = MemberwiseCloneMethod.Invoke(objSource, null);
        alreadyVisitedInstances.Add(objSource, objTarget);

        //Get all the properties of source object type
        PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

        //Assign all source property to taget object 's properties
        foreach (PropertyInfo property in propertyInfo.Where(x => x.CanWrite)
        {
           property.SetValue(objTarget, property.GetValue(objSource, null).InnerCloneObject(alreadyVisitedInstances));
        }


        return objTarget;
    }

    private static bool IsPrimitive(Type type)
    {
        return type == typeof(string) || type.IsPointer || (type.IsValueType && type.IsPrimitive);
    }
}

在我们创建新实例之前,我们会检查是否已有克隆。关键是在复制所有属性之前将这个新的实例添加到字典中。

请注意我更改了您的代码以使用MemberwiseClone创建没有无参数构造函数的类实例。

它使用这个比较器:

public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T>, IEqualityComparer
    where T : class
{
    private static ObjectReferenceEqualityComparer<T> _defaultComparer;

    public static ObjectReferenceEqualityComparer<T> Default
    {
        get { return _defaultComparer ?? (_defaultComparer = new ObjectReferenceEqualityComparer<T>()); }
    }

    public bool Equals(T x, T y)
    {
        return object.ReferenceEquals(x, y);
    }

    public int GetHashCode(T obj)
    {
        return RuntimeHelpers.GetHashCode(obj);
    }

    bool IEqualityComparer.Equals(object x, object y)
    {
        return this.Equals((T)x, (T)y);
    }

    int IEqualityComparer.GetHashCode(object obj)
    {
        return this.GetHashCode((T)obj);
    }
}

您必须记住,复制数组需要单独的案例处理。

小心使用此功能。如果你像流一样复制一个系统对象,你就会创建像Encodings这样的单例副本。我已经完成了这个并且遇到了一些麻烦。