0

I am coding a C# forms application, and I am using the following code to clone on object:

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

Some of my objects have parent objects and these parent objects have collections. As such, I am getting the following exception:

An unhandled exception of type 'System.StackOverflowException' occurred in CustomWebpageObjectCreator.exe

How can I prevent this from happening?

Is it possible to decorate some specific properties in an object with an attribute, such that the CloneObject code will not try and clone the property? If so, can someone please help me with the code to do this? If not, how should I modify my code?

Simon
  • 7,991
  • 21
  • 83
  • 163
  • the stackoverflow happens because your properties are recursive, ie, an object A may have a reference to object B, which has a reference to object A, so they keep trying to serialize one another. You definitely can do this with an attribute, check the GetCustomAttributes doc from MSDN – edeboursetty Oct 28 '15 at 05:36
  • Standard solution is to serialize/deserialize - http://stackoverflow.com/questions/78536/deep-cloning-objects... circular references are hard to deal with and you may be better off using existing solutions. – Alexei Levenkov Oct 28 '15 at 05:49

1 Answers1

1

You can use a dictionary that memorizes all original instances to the clone of it:

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

Before we create a new instance we check if we already have a clone. The key is to add this new instace to the dictionary before we copy all properties.

Please see that I changed your code to use MemberwiseClone to create an instance of classes without parameterless constructor.

It uses this comparer:

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

You have to keep in mind that you need a separate case handling for copying arrays.

Use this feature with care. If you copy a system object like a stream you'll create copies of singletons like Encodings. I've done this already and got a little trouble.

Sebastian Schumann
  • 3,204
  • 19
  • 37