【问题标题】:System.StackOverflowException when cloning an object using reflection使用反射克隆对象时出现 System.StackOverflowException
【发布时间】:2016-01-27 18:44:23
【问题描述】:

我正在编写一个 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 代码不会尝试克隆该属性?如果是这样,有人可以帮我写代码吗?如果没有,我应该如何修改我的代码?

【问题讨论】:

  • stackoverflow 发生是因为您的属性是递归的,即对象 A 可能具有对对象 B 的引用,而对象 B 又具有对对象 A 的引用,因此它们不断尝试彼此序列化。您绝对可以使用属性执行此操作,请查看 MSDN 中的 GetCustomAttributes 文档
  • 标准解决方案是序列化/反序列化 - stackoverflow.com/questions/78536/deep-cloning-objects... 循环引用很难处理,您最好使用现有解决方案。

标签: c# reflection clone


【解决方案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 这样的单例的副本。我已经这样做了,但遇到了一些麻烦。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-08
    • 2013-06-28
    • 2017-07-12
    • 2021-07-21
    • 2019-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多