Wednesday, October 22, 2008

A type-safe pattern to implement ICloneable and similar interfaces

The ICloneable interface contains a single method Clone() that returns a clone of the object.

However the return value is defined as type ‘System.Object’, which means that the user still has to cast this to the actual type:

   1: Test t = new Test();
   2: Test t2 = t.Clone() as Test;    // cast needed

This is cumbersome and error-prone because the cast may fail at runtime.

Use the following pattern to provide a type-safe implementation of this interface:

   1: class Test : ICloneable
   2: {
   3:     int m_Amount;  // just some example data...
   4:     Font m_Font;
   5:     string m_Name;
   6:  
   7:     #region ICloneable Members
   8:  
   9:     // Provide a type-safe implementation
  10:     public Test Clone()
  11:     {
  12:         Test clone = new Test();
  13:         clone.m_Amount = m_Amount;
  14:         clone.m_Font = m_Font.Clone() as Font;
  15:         clone.m_Name = m_Name;
  16:         return clone;
  17:     }
  18:  
  19:     // Provide an "Explicit Interface Method Implementation"
  20:     object ICloneable.Clone()
  21:     {
  22:         return Clone();
  23:     }
  24:  
  25:     #endregion
  26: }

This example implements a type-safe Clone() method, so the user does not have to cast the result. But we still have to implement the interface-method that returns a System.Object. This is done through explicit interface method implementation.

This pattern is easier for the user of the class, and it has the benefit that the compiler will now guarantee that the returned object is always of the correct type.

5 comments:

Steven said...

I think this is a good and easy pattern. However, you chose the wrong example :-).

You shouldn't use the ICloneable interface, because the interface isn't very helpful. I've never ever seen anyone ever cast an object of a certain type to an ICloneable and call clone on it. The problem with ICloneable is that the interface doesn't specify how the object is cloned (deep or shallow). For this reason the interface is unusable and should have been depricated. It's because of this that the .NET Framework Design Guidelines state that you shouldn't use it.

Of course it’s completely fine to have a Clone() method on your type (like in your example) and return a strongly typed object, as long as your comments state whether it’s doing a deep or a shallow copy.

Kristof said...

steven: you're absolutely right. I must admit that I hadn't thought about this until now. Thanks for the comment!

I wanted to give an example that uses an interface that everybody knows. Of course you can use this pattern on other interfaces as well (you might want to make an IShallowCloneable interface for instance).

When implementing an interface such as IEnumerable<T>, the code that is generated by Visual Studio actually uses this pattern: it will implement a 'normal' GetEnumerator() skeleton that returns a type-safe IEnumerator<T>. But it also generates the skeleton for a GetEnumerator() method that returns an IEnumerator that is not type-safe. When you implement this second method, you typically just call the type-safe version (as in my example).

Ryan Melville said...

Thanks. This helped point me in a good direction.

Here's a C++/CLI version:

http://msdn.microsoft.com/en-us/library/ms235235.aspx

Anonymous said...

Your test code is below


Test t = new Test();

Test t2 = t.Clone() as Test; // cast needed

Why would a cast be needed? Your code compile fine with out a cast. The reason is that above test code fragment will not call method Object ICloneable.Clone ( ). Rather it calls pubic Test Clone( ). And if it calls that then why a cast is needed? I ran your code through a debugger to confirm that.

Kristof said...

@Anonymous: when no precautions are taken, then you defenitely need to cast the result because it will be of type System.Object.

The pattern that I described will make sure that no cast is needed, and it will behave in the way that you say...