Clone class objects using Icloneable

Asked

Viewed 5,346 times

6

I’m trying to use the Interface Icloneable to copy an object, but I’m having a problem when it has a class property because the 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();
}
  • In the logic of Clone, Voce needs to clone at the addresses that exist within the Person object. This way the addresses also need to be IClonable

3 answers

9


My dear, unfortunately the . Net does not have the feature of cloning an object in a controlled way. The only standard form given by the . Net is the method itself MemberwiseClone, that does what is called deep cloning.

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

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 several drawbacks, if in the structure of objects there are 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 this case would use reflection to assemble the cloning method. In this case, you would have to assess whether this is worth.

I use the following solution to make deep cloning of an object, using reflection to build cloning methods so that perform well:

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

Way to use:

public object Clone()
{
    return CloneHelper.Clone(this);
}
  • I tried using the method using Reflection but it finds an error saying that Reflection.Propertyinfo does not have the definition of Setmethod nor the Getmethod

  • I fixed the error... I was recovering the Get method instead of Set to check the amount of parameters, and then I tested the number of parameters with != 1, but it should be != 0.

  • I’m sorry, but it’s still giving the same error, did I do something wrong? I use VS2010

  • The error that is happening is when compiling, or when already running?

  • Compile: http://i.imgur.com/1FpuJdE.png

  • It’s the version of .Net. Which version are you using?

  • I made an adjustment... you can check if it now works?

  • Now compiled, the method using Reflection I need to put in the class objects as well? Because it copied the data correctly only the class objects continue referencing pro first. Thank you and sorry for the delay in the reply. Version . NET: 4.0.30319 Sp1rel

  • 1

    This implementation I made, will only clone the properties that also have a method Clone, for example, if the class A has a class property B, then A and B must have a method Clone... but if that doesn’t suit you, I can show you another way.

  • Miguel does not need, really very cool the way you did, I’m sorry I’m learning so I had this difficulty to understand, but I will study your code!

  • Dispose... we’re all learning! = D

  • @Miguelangelo which means the single vertical bar on the line var flags = BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic;?

  • 1

    This is a bitwise operator, which merges the bits of the operands. Example: if A in binary for 1001, and B in binary for 0010, and I do C = A | B, the value of C in torque shall be 1011.

Show 8 more comments

2

The concept of cloning objects can have two implementations:

  1. Shallow (rasa): Clones the object but copies only the references to the objects it references, except for the types by value (value types) the value of which is, by definition, always copied.
  2. Deep* (deep): Clones the object and all objects referenced by it.

As cloning depends on implementation it can happen that cloning is mixed. That is, some internal objects being cloned to obtain a deep cloning can perform only a shallow cloning.

When one controls the definition of classes, some prefer to define their own definition of ICloneable in which the method Clone receives a parameter indicating whether 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 own cloning.

1

Address also need to implement the 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 to stay that way:

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

Browser other questions tagged

You are not signed in. Login or sign up in order to post.