Clone class objects using ICloneable

6

I'm trying to use the Interface ICloneable to copy an object, but I'm having a problem when it is owned by a class because ICloneable does not create a new memory address for these objects, but keeps pointing to the original memory address of the copy.

public class Pessoa : ICloneable
{
    public string Nome { get; set; }
    public int Idade { get; set; }
    public Endereco Endereco { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }

    public Pessoa(string nome, int idade, Endereco endereco)
    {
        Nome = nome;
        Idade = idade;
        Endereco = endereco;
    }

    public string Info()
    {
        return String.Concat(this.Nome, ", ", this.Idade, " anos, Endereço: ", Endereco.Rua, " - ", Endereco.Cidade, ", CEP: ", Endereco.Cep);
    }
}

public class Endereco
{
    public string Rua { get; set; }
    public string Cidade { get; set; }
    public string Cep { get; set; }

    public Endereco(string rua, string cidade, string cep)
    {
        Rua = rua;
        Cidade = cidade;
        Cep = cep;
    }
}

private static void Main(string[] args)
{
    Pessoa Joao = new Pessoa("João", 19, new Endereco("Rua Augusta", "São Paulo", "123456789"));
    Pessoa Pedro = (Pessoa)Joao.Clone(); // Clona os valores dos atributos
    Pedro.Nome = "Pedro";

    Console.WriteLine(Joao.Info());
    Console.WriteLine(Pedro.Info());
    Console.WriteLine();

    // Alterando a cidade do Pedro...
    Pedro.Endereco.Cidade = "Rio de Janeiro";

    Console.WriteLine(Joao.Info()); // Alterou a cidade do João também
    Console.WriteLine(Pedro.Info());

    Console.ReadKey();
}
    
asked by anonymous 07.04.2014 / 13:37

3 answers

8

My dear, unfortunately .Net does not have the ability to clone an object in a controlled way. The only default form given by .Net is the MemberwiseClone method itself, which does what is called non-deep cloning.

The way to clone also internal objects, requires intervention on your part, like this:

public object Clone()
{
    var clone = (Pessoa)this.MemberwiseClone();
    clone.Endereco = (Endereco)clone.Endereco.Clone();
    return clone;
}

Alternative: Serialize / Deserialize

Another alternative would be to serialize and then deserialize the object, but this can have a number of drawbacks if objects exist in non-serializable objects.

Example:

IFormatter formatter = new BinaryFormatter();
using (Stream stream = new MemoryStream())
{
    formatter.Serialize(stream, source);
    return formatter.Deserialize(stream);
}

Alternative: Reflection

There is an alternative that gives more work, and in the case would be use reflection to mount the method of cloning. In this case, you would have to evaluate if this is worth it.

I use the following solution to do deep cloning of an object, using reflection to be able to construct the methods of cloning so that get good performance:

public static class CloneHelper
{
    public static T Clone<T>(T objToClone) where T : class
    {
        return CloneHelper<T>.Clone(objToClone);
    }
}

public static class CloneHelper<T> where T : class
{
    private static readonly Lazy<PropertyHelper.Accessor<T>[]> _LazyCloneableAccessors =
        new Lazy<PropertyHelper.Accessor<T>[]>(CloneableProperties, isThreadSafe: true);

    private static readonly Func<object, object> MemberwiseCloneFunc;

    static CloneHelper()
    {
        var flags = BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic;
        MemberwiseCloneFunc = (Func<object, object>)Delegate.CreateDelegate(
            typeof(Func<object, object>),
            typeof(T).GetMethod("MemberwiseClone", flags));
    }

    [ReflectionPermission(SecurityAction.Assert, Unrestricted = true)]
    private static PropertyHelper.Accessor<T>[] CloneableProperties()
    {
        var bindingFlags = BindingFlags.Instance
                           | BindingFlags.FlattenHierarchy
                           | BindingFlags.Public
                           | BindingFlags.NonPublic;

        var result = typeof(T)
            .GetProperties(bindingFlags)
            .Where(p => p.PropertyType != typeof(string) && !p.PropertyType.IsValueType)
            .Where(p => p.PropertyType.GetMethods(bindingFlags).Any(x => x.Name == "Clone"))
            .Select(PropertyHelper.CreateAccessor<T>)
            .Where(a => a != null)
            .ToArray();

        return result;
    }

    public static T Clone(T objToClone)
    {
        var clone = MemberwiseCloneFunc(objToClone) as T;

        // clonando todas as propriedades que possuem um método Clone
        foreach (var accessor in _LazyCloneableAccessors.Value)
        {
            var propToClone = accessor.GetValueObj(objToClone);
            var clonedProp = propToClone == null ? null : ((dynamic)propToClone).Clone() as object;
            accessor.SetValueObj(objToClone, clonedProp);
        }

        return clone;
    }

}

public static class PropertyHelper
{
    // solução baseada em: http://stackoverflow.com/questions/4085798/creating-an-performant-open-delegate-for-an-property-setter-or-getter

    public abstract class Accessor<T>
    {
        public abstract void SetValueObj(T obj, object value);
        public abstract object GetValueObj(T obj);
    }

    public class Accessor<TTarget, TValue> : Accessor<TTarget>
    {
        private readonly PropertyInfo _property;
        public Accessor(PropertyInfo property)
        {
            _property = property;

            if (property.GetSetMethod(true) != null)
                this.Setter = (Action<TTarget, TValue>)
                    Delegate.CreateDelegate(typeof(Action<TTarget, TValue>), property..GetSetMethod(true));

            if (property.GetGetMethod(true) != null)
                this.Getter = (Func<TTarget, TValue>)
                Delegate.CreateDelegate(typeof(Func<TTarget, TValue>), property.GetGetMethod(true));
        }

        public Action<TTarget, TValue> Setter { get; private set; }
        public Func<TTarget, TValue> Getter { get; private set; }

        public override void SetValueObj(TTarget obj, object value) { Setter(obj, (TValue)value); }
        public override object GetValueObj(TTarget obj) { return Getter(obj); }
        public override string ToString() { return _property.ToString(); }
    }

    public static Accessor<T> CreateAccessor<T>(PropertyInfo property)
    {
        var getMethod = property.GetGetMethod();
        if (getMethod == null || getMethod.GetParameters().Length != 0)
            return null;
        var accessor = (Accessor<T>)Activator.CreateInstance(
            typeof(Accessor<,>).MakeGenericType(typeof(T),
                property.PropertyType), property);
        return accessor;
    }

    public static Accessor<TIn, TOut> CreateAccessor<TIn, TOut>(PropertyInfo property)
    {
        return (Accessor<TIn, TOut>)CreateAccessor<TIn>(property);
    }
}

How to use:

public object Clone()
{
    return CloneHelper.Clone(this);
}
    
07.04.2014 / 15:02
1

Address also needs to implement IClonable.

public class Endereco : ICloneable
    {
        public string Rua { get; set; }
        public string Cidade { get; set; }
        public string Cep { get; set; }

        public Endereco(string rua, string cidade, string cep)
        {
            Rua = rua;
            Cidade = cidade;
            Cep = cep;
        }

        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }

Then you change the person clone so it looks like this:

public object Clone()
        {
            Pessoa pessoa = (Pessoa)this.MemberwiseClone();
            pessoa.Endereco = (Endereco)this.Endereco.Clone();
            return pessoa;
        }
    
07.04.2014 / 14:59
1

The concept of object cloning can have two implementations:

  • Shallow (shallow): Clone the object but only copies the references to the objects it references, exception made to the types by value ( value types ) whose value is by definition , always copied.
  • Deep * (deep): Clone the object and all objects referenced by it.
  • Because cloning depends on implementation, it may happen that cloning is mixed. That is, some internal objects when cloned to obtain deep cloning can only perform a shallow cloning.

    When controlling the definition of classes, some people prefer to define their own definition of ICloneable where the Clone method receives a parameter indicating whether the cloning is shallow or deep:

    public interface ICloneable
    {
        object Clone(bool deep);
    }
    
    public interface ICloneable<T> : ICloneable
    {
        T Clone(bool deep);
    }
    

    But in the end, everyone will have to implement their cloning.

        
    09.04.2014 / 04:03