C#: Object cloning is a bit confusing now


Some developers believe that object cloning (creating new object which are copies of the original) is easier in C# than in other languages like C++. However, I do not agree with this. Somewhere I had read that a good language is one that places more hardship on framework (class) designers and less on client developers. This is where we find the principle getting violated.


C++


Lets consider a class that uses dynamically allocated memory and needs deep-copy when it is cloned. In the C++ world the developer of the class had to know things like copy-constructor and = operator overloading (and when they get called) to ensure that this happens correctly. A well-behaved class would look like

class MyClass
{
public:
MyClass()
// ctor

{

numListLen = 10;
numList =
new int[numListLen];
}
MyClass(
const MyClass& mc) // copy ctor
{
numListLen = mc.numListLen;
numList =
new int[numListLen];
for(int i = 0; i < numListLen; i++)
numList[i] = mc.numList[i];
}
MyClass&
operator=(const MyClass& mc)
{
if(this != &mc) // take care of mc = mc assignement
{
delete[] numList;
numList =
new int[numListLen];
for(int i = 0; i < numListLen; i++)
numList[i] = mc.numList[i];
}
return *this; // take care of mc1 = mc2 = mc3
}
virtual ~MyClass()
{
delete []numList;
}
int* numList;
int numListLen;
};


All this code just to ensure that copy happens as planned. So people call it complex. However, the client uses this class as.

MyClass mc1, mc2; // class Ctor
mc2 = mc1; // calls = operator overload
MyClass mc3 = mc1; // calls copy-ctor

mc1.numList[2] = 2;// mc2, mc3 are left untouched


So the client programmer simply need not care about shallow/deep copy. He assigns and creates object and all of the deep-copy stuff are handled under the hood.


C#


On C# you cannot overload the assignment operator and copy-ctor is not supported. The object cloning is supported using the ICloneable interface. Consider the following code that implements this interface

class MyClass : ICloneable
{
public MyClass()
{
deepRef =
new AnotherClass();
}
public object Clone() // ICloneable implementation
{
MyClass mc = this.MemberwiseClone() as MyClass;
mc.deepRef =
this.deepRef.Clone() as AnotherClass;
return mc;
}
public AnotherClass deepRef;
};


The code looks much simpler. However the catch is that the client writer needs to ensure that he never creates an object copy by direct assignment. So if he does the following there goes a bug into his code

MyClass mc1 = new MyClass();
MyClass mc2 = mc1; // Bug – shallow copy made

mc1.deepRef.x = 10;// and mc2.deepRef gets modified


So the client needs to call MyClass mc2 = mc1.Clone() as MyClass; to ensure deep copy. Since humans do forget bugs like this creep in.


Another issue is that ICloneable came in the pre-generic (read pre-historic) era. So its not type safe and you need ugly type-casting all over the code.


Ruby


ruby which was once a upon a time my favorite language sucks big time in deep-copy. You need to do something as below to get a deep copy

class Object
def deepcopy
Marshal::load(Marshal.dump(self))
end
end

Comments (18)

  1. Anonymous says:

    It seems to me the C# world is simpler for both audicences.

    Class designers need to write two methods in C++ to do effectively the same thing and neither of those methods have helpful names.  In C# you have one method and it’s called Clone.

    "However the catch is that the client writer needs to ensure that he never creates an object copy by direct assignment. "

    This may be a problem if you’re used to C++ but the fact is ALL reference types in C# exhibit this behaviour so it’s not a mistake easily made.

    With C++ you have to know what the class designer did and that involves heading off to the source or documentation.

    In C# you can relax safe in the knowledge that = will copy the reference for reference types (class) or the value for value types (struct).

    If you ever actually want another copy then .Clone is clearly named.

    Some of the simpler types are Immutable anyway so worying about somebody modifying the original by mistake is a non-issue.  

    This behaviour also makes perfect sense in the context of function parameters – the parameter you get in the function is just an "=" of the one you were passed unless the behaviour is modified with the "ref" keyword.

    [)amien

  2. Maybe I am coming from C++ background in stating that people may do a = b instead of a = (Foo)b.Clone() But I think a lot of people move to C# from C++, so there’ll be people doing this mistake.

    The other thing is that I do not like the fact that this is not type-safe and you need to do casting all the time.

    Moreover in code you’ll have to things like

    if (foo is ICloneable)

      bing = foo.clone();

    In case you do not want to do this the client writer need to check class-code or doc to find out if the class does implement ICloneable

  3. Anonymous says:

    You can implement a copy constructor in .NET as well. I find it a bit more easy for my C++ eye than using ICloneable.

    class MyClass

    {

      private string name;

      // Regular constructor.

      public MyClass(string name)

      {

         this.name = name;

      }

      // Copy constructor.

      public MyClass(MyClass mc)

      {

         this.name = mc.name;

      }

      public string Name

      {

         get { return name; }

         set { name = value; }

      }

    }

    void Test()

    {

      MyClass mc1 = new MyClass("mc1");

      MyClass mc2 = new MyClass(mc1); // Call copy constructor.

      mc2.Name = "mc2";

      System.Console.WriteLine(mc1.Name);

    }

  4. -Anders-

    You cannot write copy-ctor in .NET because .NET wouldn’t support it. What I mean is that when you write say MyClass mc2 = mc1;

    the ctor will not be called automatically. What you coded is just another ctor which explicitely needs to be called. So that can as well be done using any method like mc1.DeepCopy().

    So the requirement of the client writer not needing to care is not solved and he needs to remember to put in that call.

  5. Anonymous says:

    You are correct, the solution that I provided does not automatically call the "copying" constructor. You’ll have to create a new object using the "copying" constructor yourself, as I presented in the example.

    How are value types handling this? Can you provide your own copy constructor for those?

  6. Anonymous says:

    One question, in the code:

    MyClass mc2 = mc1; // Bug – shallow copy made

    Why would you consider this a "shallow copy"?  I would think this statement would just copy the reference.  I think of a "shallow copy" as one that copies the contents of the top-level object, but no deeper, i.e., it just makes copies of references without duplicating sub-objects.

    BTW, excellent post, as always!

  7. Tom, appologies. It is a reference copy not a shallow-copy. My bad.

  8. Maxim Moiseev says:

    off the topic:

    You say: ‘ruby which was once a upon a time my favorite language’.

    What’s your favourite programming language now (C#3 maybe) and why Ruby is not?

  9. Right now C# is my favorite language. Ruby is a close 2nd. The reason is multiple, most of which is not the language’s fault.

    1. I am not as comfortable with dynamic languages. I feel (and I might be wrong) that they do not scale well for very large projects. So I use Ruby for fun/scripting tasks and not for real life projects

    2. Lack of library support. If you have worked with any .NET languages, you get so much used to the super-rich platform support that all other languages just slip out of your sight. However, I am eagerly waiting to try our Ruby.NET. That misgt just get me back into Ruby.

  10. Maxim Moiseev says:

    AFAIK scripting languages are positioned as ‘general purpose’ languages. As for me I’d not mark any large project ‘general purpose’. Scripting languages are best suited to be embedded. And of course I agree with you about the Framework that makes life much easier. But! most scripting languages have a great number of enthusiasts, creating libraries (though separately) that can span the major Framework’s functionality.

    And, finally, about <somescript>.Net projects. I’m very impressed by the IronPython, and looking forward to the early releases of Ruby.Net too.

    Thanks for an answer.

  11. Anonymous says:

    if (foo is ICloneable)

    >  bing = foo.clone();

    if (foo is ICloneable)

     bing = ((ICloneable)foo).Clone();

    -ab.

  12. Anonymous says:

    here is a general purpose clone method in C# for all interested, not very efficient but accomplishes the objective.

    public Object Clone(Object Original)

    {

    System.IO.MemoryStream _Stream = null;

    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter _Formatter =null;

    Byte[] _Bytes = null;

    Object _Clone = null;

    try

    {

    _Stream = new MemoryStream();

    _Formatter = new BinaryFormatter();

    _Formatter.Serialize(_Stream, Original);

    _Bytes = _Stream.ToArray();

    _Stream.Close();

    _Stream = new MemoryStream(_Bytes);

    _Clone = _Formatter.Deserialize(_Stream);

    _Stream.Close();

    return _Clone;

    }

    finally

    {

    if(_Stream != null)

    _Stream.Close();

    _Stream = null;

    }

    }

  13. Anonymous says:

    A comment about the above,

    remember to decorate object(to clone) as [Serializable]

  14. Anonymous says:

    C# supports copy constructor that is automatically called and = operator overloading, but in a "different" kind of way.

    class MyClass

    {

     private string name;

     // Regular constructor.

     public MyClass(string name)

     {

        this.name = name;

     }

     // Copy constructor.

     // Makes support for MyClass c = "hello"; (copy constructor)

     // and  MyClass c = null;

     // c = "hellow"; ( = operator overloading)

     public static implicit MyClass(string name)

     {

        return new MyClass(name);

     }

     public string Name

     {

        get { return name; }

        set { name = value; }

     }

    }

  15. Anonymous says:

    string is a "special" value type in .Net. Can you change the Type of Name from string to object and conclude that you are doing a deep copy?

  16. Jack says:

    And if you make sure that you will not change the object, using shadow copy will save lots of resource and time.

  17. Kobi B says:

    ROFL, likes C# over ruby

    this is like funniest thing I heard this year