10 January, 2008

Extension methods for copying or cloning objects

C# 3.0 includes a new feature known as extension methods, and fiddling with it triggered the idea of creating a mechanism for copying or cloning (virtually) any .NET object or graph of objects. The manifestation of that idea has become a rather decent little framework for copying objects. It performs a deep copy as automatically as it possibly can, and provides mechanisms to easily solve many of the cases which cannot be covered automatically. It is great for copying your custom object hierarchies, and saves you the pain of a solution like implementing ICloneable for an entire hierarchy of objects.

Let's start off with a few words on extension methods. They are best explained through an example. Let's say we want to be able to calculate area given size. Wouldn't it be nice to be able to add GetArea to the already existing Size class? Well, let's do so!

As you can see, the new syntax simply allows you to tell the compiler that the this of this method is a Size. This means that the method is an extension of the Size class.

As mentioned, I had the idea of extending the very base of the C# class hierarchy (System.Object) with a method for copying or cloning "any" object. Obviously, the method cannot automatically copy _any_ object, since it cannot possibly know how to construct an object from an arbitrary class. Hence, a small framework needed to be created. The goals were to:
  • Enable copying of many objects automatically.
  • Enable copying of virtually any object with very little effort.
  • Automate and hide away as much as possible (The KISS Principle).
The result is Copyable (pun intended).

The Copyable framework

Copyable is a small framework for copying (or cloning, if you will) objects. The straightforward way of using it is to just reference the assembly it's in from your project, and start copying!

The instance copy is now a deep copy of instance, no matter how complex the object graph for instance is. The relations in the copy graph is the same as in instance, but all objects in the copy object graph are copies of those in instance.

For the automated copy to work, though, one of the following statements must hold for instance:

  • Its type must have a parameterless constructor, or
  • It must be a Copyable, or
  • It must have an IInstanceProvider registered for its type.

Besides the Copy method, The Copyable class and IInstanceProvider interface are the two major building blocks of the Copyable framework. Each of these blocks enable copying of objects that cannot automatically be copied.

The Copyable base class

Copyable is an abstract base class for objects that can be copied. To create a copyable class, you simply subclass Copyable and call its constructor with the arguments of your constructor.

This code above makes MyClass a copyable class. Note that if MyClass had had a parameterless constructor, subclassing Copyable would not be necessary.

MyClass can now be copied just like the previous example, e.g. MyClass b = new MyClass(1, 2.0, "3").Copy().

The introduction of the Copyable base class solves many problems, but not all. Let's say you wanted to copy a System.Drawing.SolidBrush. This class does not have a parameterless constructor, which means it cannot be copied "automatically" by the framework. Also, you cannot alter it so that it subclasses Copyable. So, what do you do? You create an instance provider.

The IInstanceProvider interface

An instance provider is defined by the interface IInstanceProvider. As the name clearly states, the implementation is a provider of instances. One instance provider can provide instances of one given type. The Copyable framework automatically detects IInstanceProvider implementations in all assembies in its application domain, so all you need to do to create a working instance provider is to define it. No registration or other additional operations are required. To simplify the implementation of instance providers and the IInstanceProvider interface, an abstract class InstanceProvider is included in the framework.

This implementation will be used automatically by the Copyable framework. NOTE: To be usable, the instance provider MUST have a parameterless constructor.

The instance provider pattern does not solve the case where you want different initial states for your SolidBrush instances depending on which context you use them for copying. For those cases, an overload of Copy() exists which takes an already created instance as an argument. This argument will become the copy.

Limitations and pitfalls

Although this solution works in most cases, it's not a silver bullet. Be aware when you copy classes that hold unmanaged resources such as handles. If these classes are designed on the premise that their resources are exclusive to them, they will manage them as they see fit. Imagine if you copied a class which holds a handle, disposed one of the instances, and continued using the copy. The handle will (probably) be freed by the original instance, and the copy will generate an access violation by attempting reading or writing freed memory.

That's it! The Copyable framework can be downloaded from Github. For those interested in reading more on extension methods, MSDN provides an excellent explanation in the C# Programming Guide, and Scott Guthrie has an introduction article here.

Enjoy Copyable, and please let me know if you find it useful or come across any problems with it.

UPDATE 2009-12-11: Due to popular demand, I have made the source code for Copyable available under the MIT license.

UPDATE 2010-01-31: The requirement of parameterless constructors has been removed in the latest version of Copyable available on Github. A new release will follow soon.

50 comments:

  1. Leonardo Cooper06 May, 2008 00:42

    you are my hero, thanks a lot... just of problem though, declaring an event in the instance to be copied throws an exception because seens delegates dont have parameterless ctors.

    ReplyDelete
  2. Leonardo: Create an IInstanceProvider implementation for it, to provide the Copyable framework with instances.

    ReplyDelete
  3. [...] Stranden created a custom cloning framework using extension methods.  The framework creates a deep copy of an object and all referenced [...]

    ReplyDelete
  4. After numerous requests, I have decided to make the source code available. It is released under the MIT license, and can be downloaded from here.

    ReplyDelete
  5. How about this:

    public interface IInstanceProvider : IInstanceProvider where T: new()

    Thanks

    ReplyDelete
  6. Exactly what I was looking for. Thanks!

    ReplyDelete
  7. I have a class with a generic event handler (EventHandler).

    I created a class the implements InstanceProvider for EventHandler but I still get "object of type {0} cannot be cloned..." when trying to clone the System.Reflection.RuntimeMethodInfo from the event handler.

    Has anyone gotten this to work?

    Thanks.

    ReplyDelete
  8. Nice!
    Please take a look at your code snippet in the "The IInstanceProvider interface" section, I think you should use override keyword there.

    ReplyDelete
  9. Ron: You are indeed right, thanks for spotting! Example updated now.

    ReplyDelete
  10. Would using FormatterServices.GetUninitializedObject() allow automatic cloning of objects without a default constructor?

    ReplyDelete
  11. Mark: It would. I am considering patching Copyable so that it uses FormatterServices.GetUninitializedObject instead of Activator.CreateInstance, but haven't gotten around to it yet. Copyable needs a revamp, polish, and new release anyway, so I'll hopefully get it done soon. Feel free to submit a patch!

    ReplyDelete
  12. I replaced

    line 135 of ObjectExtensions.cs:
    visited.Add(instance, copy);
    With these two lines:

    if(visited.ContainsKey(instance))
    return visited[instance];
    as You said and got a bug in
    TestCyclicObjectHierarchyClone test.

    ReplyDelete
  13. Hi Håvard,

    I just stumbled over this blog post and intended to use your extensions.
    The only problem I ran into was that I mainly use auto-properties and, from what I see in the source, you only read out the fields, so my properties are not cloned unfortunately.

    Is there a particular reason you haven't implemented that?
    Can you give me a nudge to what I could to to extend the extension to support properties as well?

    Greetings Alex

    PS: And totally OT... Håvard... I just looked it up on Wiki only getting nowegian or icelandish results. And as my swedish is limited (and I don't know much about the icelandish characters) I don't understand where it comes from. I'm just interested ;) Thanks!

    ReplyDelete
  14. As we say in germany "Alle guten Dinge sind 3" (All good things are three).
    Here my 3rd Post :)
    And actually the most important.

    Thank you for the work you have done with this, Håvard!
    It is actually very useful for me! :)

    ReplyDelete
  15. Hi Håvard,

    first of all, I know about the å. :) As I said, I know a little swedish brag (At least I understand quite a bit).
    The problem in itself was that Wiki said this: Hávarðr. I know it's icelandish, but that's all ;).

    And another quite off topic question. Yesterday, while researching your name, I had a discussion with my girlfriend as to what is actually the spoken language in norway, as there are (as much as I understand) two versions: bokmål and nynorsk. Though my bet was on nynorsk.

    About Topic. Hey, that's great that my change gets into your code. I'm honored. Actually, the first contribution I could to so far xD. And what do you think, I use that merely to achieve a Copy & Paste effect. But hey, it works like a charm :)

    Greetings
    Alex

    ReplyDelete
  16. More off-topic replies:

    Icelandish and old Norwegian are very similar languages, so you've probably hit a wiki with information on old Norwegian/icelandic names. Icelandic and old Norwegian were the same language a long time ago, and Iceland was actually part of Norway until the beginning of the 20th century.

    Bokmål and nynorsk are the two written languages in Norway. Bokmål is the most common, and all Norwegians must learn both written languages. Ironically, nobody in Norway actually speaks neither bokmål nor nynorsk. Norway has a ton of very different dialects, of which many are close too (but not quite the same as) bokmål or nynorsk, and some are quite different from both.

    Again great to hear that you find Copyable useful. Many do, and so I think it's time to establish a "project" for it and allow people to contribute their changes. :)

    ReplyDelete
  17. Do you mean making it open source?

    ReplyDelete
  18. Ahh. Sorry, I expressed myself incorrecly.
    Open Source it is already. I meant like creating a Project at.. say SourceForge or whatever.

    ReplyDelete
  19. How is the performance of this compared to other techniquest (manually copying, serialize/deserialize, etc)? I'm looking for a way to quickly clone a very large and complex object and performance is critical.

    ReplyDelete
  20. Håvard,
    It seems I don't have the write permission to your code. But I was able to get the latest code and try my change on it. My patch passed all tests except the one that tests provider usage:
    Assert.IsTrue(SolidBrushProvider.NumCalls > 0);
    This failure is expected, since my patch does not require or use any provider. Isn’t this another advantage! Let me know if I can add my patch, or you may add it for me.

    ReplyDelete
  21. John, that's right, you don't have write access. You need to prepare and send a "pull request" through GitHub (see the GitHub docs).

    Patches with failing tests will not be accepted. :) The instance provider mechanism is still useful for objects with unmanaged resources, so it will stay alive in spite of a new way of cloning.

    ReplyDelete
  22. Thanks very helpful, liked the code!!

    ReplyDelete
  23. Ben: I am going to need a source code copy that reproduces this problem. You can send me a copy at havard DOT stranden ATT gmail DOT com.

    ReplyDelete
  24. Boris: Thanks for sharing! The issue with inherited properties has been reported earlier, and is fixed in the latest version of Copyable on GitHub . You can get a copy of the source there.

    As for the issue of exceptions, I'll see if I can find time to lessen the exception throwing for added debugging convenience. Until then, rest assured that they are handled properly. :)

    ReplyDelete
  25. Tried the suggestion (yes, the files were linked into a new SL class lib proj) for making this work in Silverlight 3 and still throws the same error when trying to compile the wrapper project that I created. Has anyone besides Boris been able to make this work for Silverlight 3?

    ReplyDelete
  26. Rachel: Sadly, OX.Copyable won't easily work with Silverlight. There are several issues that need to be adressed before a Silverlight version can be built. I hope to be able to research the possibilities for Silverlight support in the near future. Keep watching!

    ReplyDelete
  27. Hi, Håvard, thanks for making the code available. I'd really like to use it; however, I'm getting a StackOverflowException in this line:
    return FormatterServices.GetUninitializedObject(instanceType);
    of method DeduceInstance(object instance) in class ObjectExtensions. My objects have circular dependencies, but I see you keep a record of the visited nodes in the dependency graph, so this shouldn't be a problem, should it?
    Thanks,
    Alix

    ReplyDelete
  28. Hi Alix, are you using the latest version from GitHub? If not, please try with that. Also, could you provide a small sample that will reproduce the problem?

    ReplyDelete
  29. Hello,
    Thanks for creating this, it looks very useful...

    I am working on a Silverlight 4 project, and tried to build Copyable for SL 4.
    - Create new SL 4 class lib project
    - Copy source files in to new project
    When I build, I get this error:
    C:\Src32\OX.Copyable-SL\OX.Copyable-SL\ObjectExtensions.cs(23,59): error CS0122: 'System.AssemblyLoadEventArgs' is inaccessible due to its protection level

    Does anyone know of a workaround to get this to build with SL 4?

    Thanks again...

    ReplyDelete
  30. CB: Sadly, Copyable won't easily work with Silverlight. Copyable uses reflection on non-public members, which isn't available on the Silverlight platform, so to get Copyable working on Silverlight, someone will have to implement a new way of copying objects.

    ReplyDelete
  31. I recommend you do not use this code, author states it will copy any object, it's false, it didn't copy my objects properly and like many people stated it's flawed.

    ReplyDelete
  32. Does not work with WPF either, templates aren't copied.

    ReplyDelete
  33. After second check it doesn't even copy the string member variables :/

    ReplyDelete
  34. @Ilya Interesting, I'll hopefully be able to reproduce and patch it. Thanks for reporting!

    ReplyDelete
  35. @Yuri I can assure you that Copyable does what it says "on the box". :) Copyable is in production at several major companies. If you have issues, please send me a test project that reproduces the problem, and I'll look into it.

    ReplyDelete
  36. Travis L. Riffle20 August, 2010 19:42

    Hi -- I was getting the constructor error and decided to back out of this project I had already spent a bit of time understanding it. I would suggest you test this with inheritence, that may have been causing my problem. My base class inherited from this class. Derived class inherited from base class. Derived class and base class both have parameters in their constructors... I'm wondering if there's something going on there... Let me know your thoughts.

    ReplyDelete
  37. Alexhander Alekhine14 September, 2010 21:21

    Hello,

    When I try to get a copy I get the following exception at runtime,
    the more interesting is I could not reproduce the same problem on my local machine.

    I get an exception:
    System.TypeInitializationException: The type initializer for 'OX.Copyable.ObjectExtensions' threw an exception. ---> System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information. at System.Reflection.Module.GetTypesInternal(StackCrawlMark& stackMark) at System.Reflection.Assembly.GetTypes() at OX.Copyable.ObjectExtensions.d_0.MoveNext() at System.Collections.Generic.List1.InsertRange(Int32 index, IEnumerable1 collection) at OX.Copyable.ObjectExtensions.IntializeInstanceProviders() at OX.Copyable.ObjectExtensions..cctor()

    ReplyDelete
  38. Ok. I think I solved the problem. The class initializer of ObjectExtensions throws when it hits the Microsoft.VisualStudio.Enterprise.ASPNetHelper assembly and calls GetTypes(). So another try catch around that and everything is fine.


    private static IEnumerable GetInstanceProviders(Assembly assembly)
    {
    System.Type[] types = null;
    try
    {
    types = assembly.GetTypes();
    }
    catch { }

    if (types != null)
    {
    foreach (Type t in types)
    {
    if (typeof(IInstanceProvider).IsAssignableFrom(t))
    {
    IInstanceProvider instance = null;
    try
    {
    instance = (IInstanceProvider)Activator.CreateInstance(t);
    }
    catch { } // Ignore provider if it cannot be created
    if (instance != null)
    yield return instance;
    }
    }
    }
    }


    ReplyDelete
  39. André,

    thanks a lot for noticing the issue, and sharing a solution. It would be great if you could fork Copyable on Github, implement your changes, and send me a pull request so we can get it into the master branch (preferably also with a unit test that reproduces the specific problem you had).

    ReplyDelete
  40. Great code, thanks! I'd like to use it in Silverlight (4), where it would be especially useful as deep-cloning objects using serialization is excluded by the lack of SerializableAttribute. Anyway, I tried to compile it under SL 4 and I get an error about AssemblyLoadEventArgs, which seems not public under this platform. Do you plan a SL compatible version? Or can you suggest any workaround for this?

    ReplyDelete
  41. Sorry guys, I had not seen another post like mine in comments, they were shown all only after I posted. So it seems there is no support for SL. Thanks anyway.

    ReplyDelete
  42. Is it possible to write custom type converters, as described by AutoMapper here:
    http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters&referringTitle=Home

    ReplyDelete
  43. Gravitas: No, type converters are not supported. The concept of type conversion is different from the concept of copying, so there really is no obvious way of supporting conversion or "mapping". AutoMapper and similar are a better match for conversion than Copyable is.

    Support for custom "copyers" could be desirable. I haven't seen any real use for it in my applications, but I would welcome any suggestions. I would expect a suggestion to include an interface that can be implemented to support custom copying, as well as modification of the Copyable base class to support custom copy behavior.

    Just fork on GitHub and get on with it! :)

    ReplyDelete
  44. [...] Stranden created a custom cloning framework using extension methods. The framework creates a deep copy of an object and all referenced [...]

    ReplyDelete
  45. […] Extension Methods, such as this custom clone framework by Havard Straden […]

    ReplyDelete
  46. […] Extension Methods, such as this custom clone framework by Havard Straden […]

    ReplyDelete
  47. […] Extension Methods, such as this custom clone framework by Havard Straden […]

    ReplyDelete
  48. […] Extension Methods, such as this custom clone framework by Havard Straden […]

    ReplyDelete
  49. […] if implementing ICloneable is too much like hard work a reflective one using Håvard Stranden’s Copyable library to do the heavy lifting […]

    ReplyDelete
  50. […] Extension Methods, such as this custom clone framework by Havard Straden […]

    ReplyDelete