Search code examples
c#.netdeep-copy

DeepCloner not actually copying the properties?


So I've been trying to play around little bit with a NuGet package called DeepCloner.

I have a simple class called IdInfo with one property and constructor

public class IdInfo
{
    public int IdNumber;
    public IdInfo(int idNumber)
    {
        IdNumber = idNumber;
    }
}

Then, I have a class called Person, with couple of properties and constructors

public class Person
{
    public int Age;
    public DateTime BirthDate;
    public string Name;
    public IdInfo IdInfo;

    public Person(int age, DateTime birthDate, string name, IdInfo idInfo)
    {
        Age = age;
        BirthDate = birthDate;
        Name = name;
        IdInfo = idInfo;
    }
    public Person()
    { }
}

In my main class, I would like to achieve Deep cloning by using DeepCloner as mentioned above. This is what I have tried

internal class Program
{
    static void Main(string[] args)
    {
        //create a dummy Person to get cloned
        Person p1 = new Person();
        p1.Age = 42;
        p1.BirthDate = Convert.ToDateTime("1977-01-05");
        p1.Name = "Aleksandar Petrovic";
        p1.IdInfo = new IdInfo(123);

        //create a dummy Person to append those values to
        Person clonedPerson = new Person();

        //call a method for DeepCloning (down in the code)
        PerformDeepCloning(p1, clonedPerson);
        //after finishing with the method, "clonedPerson" value stay null, why?
        

        Console.WriteLine("Displaying values of both persons (1. p1, 2. Cloned person)\n");
        DisplayValues(p1);

        //System.NullReferenceException: 'Object reference not set to an instance of an object.'
        DisplayValues(clonedPerson);
    }

    public static void DisplayValues(Person p)
    {
        Console.WriteLine("    Name: {0:s}, Age: {1:d}, BirthDate: {2:MM/dd/yy}", p.Name, p.Age, p.BirthDate);
        Console.WriteLine("    ID#: {0:d}\n", p.IdInfo.IdNumber);
    }

    //method gets 2 elements, first has values that should be copied to the second
    public static void PerformDeepCloning(Person source, Person destination)
    {
        //call a method from the package
        destination = source.DeepClone();
        //this works fine
        Console.WriteLine("DEEP CLONED NAME = " + destination.Name);
    }
}

I understand why the values are shown in the SC down below

enter image description here

But why are the values not applied to the variable "clonedPerson" afterwards? enter image description here


Solution

  • As your code is currently written, you are assigning a complete new object to your destination parameter. But this does not change the object you passed into that parameter. To overcome this issue there are several possibilites (in order of my personal preference)

    1. You can just define PerformDeepCloning to return a Person

       public static Person PerformDeepCloning(Person source)
       {
         //call a method from the package
         Person destination = source.DeepClone();
         //this works fine
         Console.WriteLine("DEEP CLONED NAME = " + destination.Name);
         return destination;
       }
      

      and call it like this

       Person otherPerson = PerformDeepCloning(person);
      

      This would seem the most natural way for me to do it, instead of populating out or ref parameters of a method returning void, why not just return the result via return keyword?

    2. Use the out keyword which was specifically designed for such situations (ie creating a new object inside the called method and pass it to the caller if return isn't possible for whatever resons)

       public static void PerformDeepCloning(Person source, out Person destination)
       {
         //call a method from the package
         destination = source.DeepClone();
         //this works fine
         Console.WriteLine("DEEP CLONED NAME = " + destination.Name);
       }
      

      and then call it like this

       //you must not assign a value here, when using the out keyword        
       Person otherPerson;
       PerformDeepCloning(person, out otherPerson);
      

      or starting with C#7 you can also use an inline declaration

       //no need to delare Person otherPerson; prior to the call
       PerformDeepCloning(person, out Person otherPerson);
      
    3. Use the ref keyword which is capable of updating the reference which was passed in.

       public static void PerformDeepCloning(Person source, ref Person destination)
       {
         //call a method from the package
         destination = source.DeepClone();
         //this works fine
         Console.WriteLine("DEEP CLONED NAME = " + destination.Name);
       }
      

      and then call it like this

       Person otherPerson = new Person(); 
       PerformDeepCloning(person, ref otherPerson);
      

      But as you are creating a new instance of a Person anyways, you don't need to create a new object before the call (because this will be thrown away immediately) but initialize the otherPerson with null. But then you of course have to sure, you don't access otherPerson's properties, before you created an instance.

       Person otherPerson = null; 
       PerformDeepCloning(person, ref otherPerson);