Skip to main content

More C# generic extension methods - non-interface interface methods

(This blog post is copied from my old blog at www.lordzoltan.org, it was originally written 1st August 2008)

Somebody asked me the other day if there was a generic way of providing a property-based clone operation for classes.  The operation didn't have to recurse into complex objects (just copy the references), but the value types had to be copied.

The first part of the solution was a little bit of reflection:

  1. public static void Clone(object source, object dest)
  2. {
  3.     //(omitted) check that the source and destination types
  4.     //are equal with Type.IsAssignableFrom
  5.  
  6.     PropertyInfo[] props = source.GetType().GetProperties(
  7.         BindingFlags.Public |
  8.         BindingFlags.Instance |
  9.         BindingFlags.FlattenHierarchy);
  10.     MethodInfo getmethod;
  11.     MethodInfo setmethod;
  12.     foreach (var p in props)
  13.     {
  14.         //make sure the get and set accessors are available.
  15.         getmethod = p.GetGetMethod();
  16.         setmethod = p.GetSetMethod();
  17.         if (getmethod == null || setmethod == null)
  18.             continue;
  19.         //invoke the getmethod on the source, and use the result to pass
  20.         //to the set method on the destination
  21.         setmethod.Invoke(dest,
  22.             new object[] { getmethod.Invoke(source, new object[] { }) });
  23.     }
  24. }

All great, so now we have a static method we can call - but, however nice it is, it's not that gawk-able.

I wanted to have this method appear in the member list of objects that we wanted to be able to clone - which meant either a base or an interface.  Interfaces are less intrusive, because you can still apply them to classes with bases, of course, and you can have multiple interfaces - but having the method as an interface member didn't make any sense.  However, if you've read my previous post: http://lordzoltan.blogspot.com/2010/09/pseudo-template-meta-programming-in-c_10.html, which uses some generic extensions alongside static generics to provide meta info, the solution becomes easy.  However you have to do something that a lot of people would seriously moan about...  an empty interface.

  1. public interface IGenericCloneable
  2. {
  3. }
  4.  
  5. public static class IGenericCloneableExtensions
  6. {
  7.     public static void Clone<T>(this T source, T dest)
  8.         where T : IGenericCloneable
  9.     {
  10.         PropertyInfo[] props =
  11.             typeof(T).GetProperties(
  12.             BindingFlags.Public |
  13.             BindingFlags.Instance |
  14.             BindingFlags.FlattenHierarchy);
  15.         MethodInfo getmethod;
  16.         MethodInfo setmethod;
  17.         foreach (var p in props)
  18.         {
  19.             getmethod = p.GetGetMethod();
  20.             setmethod = p.GetSetMethod();
  21.             if (getmethod == null || setmethod == null)
  22.                 continue;
  23.             setmethod.Invoke(dest,
  24.                 new object[] { getmethod.Invoke(source, new object[] { }) });
  25.         }
  26.     }
  27. }

Notice that the reflection is performed on typeof(T) instead of source.GetType() - which should be faster at runtime.

There is a gotcha here, though, and it's to do with the T that is passed when this method called for base classes of a superclass, consider the following:

  1. public class Cloneable1 : IGenericCloneable
  2. {
  3.     public int ID { get; set; }
  4. }
  5.  
  6. public class Cloneable2 : Cloneable1
  7. {
  8.     public string Name { get; set; }
  9. }

Now some method declared in another static class, no attempt made to contrive a complicated scenario, instead we just have a rather pointless method, but it demonstrates the issue at hand:

  1. public static class Utility
  2. {
  3.     public static void GetClone(Cloneable1 source,
  4.         Cloneable1 dest)
  5.     {
  6.         source.Clone(dest);
  7.     }
  8. }

Now let's test the method for an instance of Cloneable1 and for Cloneable2:

  1. [TestMethod]
  2. public void TestClone1()
  3. {
  4.     Cloneable1 c1 = new Cloneable1() { ID = 10 };
  5.     Cloneable1 c2 = new Cloneable1();
  6.     Utility.GetClone(c1, c2);
  7.     Assert.AreEqual(c1.ID, c2.ID); //should be fine.
  8. }
  9.  
  10. [TestMethod]
  11. public void TestClone2()
  12. {
  13.     Cloneable2 c1 = new Cloneable2() { ID = 10, Name = "Fred" };
  14.     Cloneable2 c2 = new Cloneable2();
  15.     Utility.GetClone(c1, c2);
  16.     Assert.AreEqual(c1.ID, c2.ID); //should be fine.
  17.     Assert.AreEqual(c1.Name, c2.Name); //will fail
  18. }

The second test will fail, because the GetClone method implicitly passes Cloneable1 as the <T> parameter for the Clone<T> extension method, since T is taken from the left-hand side of the '.' operator which selects the method.

One way to get around this problem is to write another extension method for IGenericCloneable in a new IGenericCloneableExtensions2 class (simplifies the reflection), this time which takes IGenericCloneable and is not generic, we can give it the same name because the compiler will intelligently select the method that matches the MOST derived version of the input parameters when it is called:

  1. public static class IGenericCloneableExtensions2
  2. {
  3.     public static void Clone(this IGenericCloneable source, IGenericCloneable dest)
  4.     {
  5.         typeof(IGenericCloneableExtensions).
  6.             GetMethod("Clone", System.Reflection.BindingFlags.Static
  7.                 | System.Reflection.BindingFlags.Public).MakeGenericMethod(
  8.                     new Type[] { source.GetType() }).Invoke(
  9.                         null, new object[] { source, dest });
  10.     }
  11. }
Now we change the signature of our utility method to take instances of IGenericCloneable instead of the Cloneable1 class, and the compiler will redirect calls to the intermediate method instead of the generic.

This is a useful practise anyway, because it means you can make the IGenericCloneable(2) extensions available to code that only has instances of the interface (which, after all, is what most people will expect of an interface).  However, we can bypass the need to regress the Utility.GetClone method to using IGenericCloneable instance, by tightening up our original generic version with a little bit of reflection and dynamic generic compilation magic:

  1. //modify the original version to check the
  2. //actual instance types of source and dest
  3. public static void Clone<T>
  4.     (this T source, T dest)
  5.     where T : IGenericCloneable
  6. {
  7.     Type[] instancetypes = new Type[] {
  8.         source.GetType(), dest.GetType() };
  9.     
  10.     if (instancetypes[0].Equals(typeof(T)) == false)
  11.     {
  12.         //check ultimate
  13.         if (instancetypes[0].Equals(instancetypes[1])
  14.                 == false &&
  15.              instancetypes[0].IsSubclassOf(instancetypes[1])
  16.              == false)
  17.             throw new ArgumentException("If the two instance types are not equal, " +
  18.                              "the first instance type must be derived from the target type");
  19.         
  20.         ((MethodInfo)MethodInfo.GetCurrentMethod()).
  21.                 GetGenericMethodDefinition().
  22.                      MakeGenericMethod(new Type[] { instancetypes[0] }).
  23.                             Invoke(null, new object[] { source, dest });
  24.         return;
  25.     }
  26.  
  27.     //the rest is the same as before
  28.     PropertyInfo[] props =
  29.         typeof(T).GetProperties(
  30.         BindingFlags.Public |
  31.         BindingFlags.Instance |
  32.         BindingFlags.FlattenHierarchy);
  33.  
  34.     MethodInfo getmethod;
  35.     MethodInfo setmethod;
  36.     foreach (var p in props)
  37.     {
  38.         getmethod = p.GetGetMethod();
  39.         setmethod = p.GetSetMethod();
  40.         if (getmethod == null || setmethod == null)
  41.             continue;
  42.         setmethod.Invoke(dest,
  43.             new object[] { getmethod.Invoke(source, new object[] { }) });
  44.     }
  45. }

Now, we can return our original GetClone method to take instances of Cloneable1, and the two tests should pass.

Don't be worried about the MakeGenericMethod call - the type engine will only actually compile the method once - after that, it will simply go and obtain a handle to the compiled method.  [Note – with the advent of expression trees I now do things like this by dynamically compiling methods and then caching them – the result is far faster and much tidier :)]

There are other things we could do here, for example make another version of Clone which takes two type parameters:

  1. public static void Clone<T1, T2>(this T1 source, T2 dest)
  2.     where T1 : T2
  3.     where T2 : IGenericCloneable
  4. {
  5.     //same code as the original Generic one we wrote,
  6.     //using the property list of T2 instead of T1.
  7. }

This method automatically allows us to clone an instance of T1 to T2 because T1 is inherited from T2, and it'll still appear as an extension method to the instance of T1 because T2 is constrained to implement the IGenericCloneablee interface.

We could then modify the reflection code at the head of the modified generic Clone<T> method so that it picks up this generic method when the instance type of the destination object is a base class of the instance type of the source.

We could write a CloneNew<T> method, by specifying an additional constraint on <T>:

  1. public static T CloneNew<T>(this T source)
  2.     where T : IGenericCloneable, new()
  3. {
  4.     T result = new T();
  5.     source.Clone<T>(result);
  6.     return result;
  7. }

And if you started stacking these empty interfaces on top of others, you can start building whole libraries of useful generic extension methods that provide a whole host of common functionality to otherwise method-less types - and still be able to inherit those types from concrete classes (because the interface list is unbounded).

Have fun!

Comments

Popular posts from this blog

Asp.Net 2 and 4 default application pool generates CS0016 IIS7.5

Before I start – if you’ve found a bunch of other articles about this around the net, tried the fixes that are mentioned and still not getting any joy – then read on – you might find this solves your problem. Earlier today I discovered that when I run any ASP.Net 2 or 4 application through IIS7.5 using the default application pools (which use ApplicationPoolIdentity) on Windows 2008 R2 x64 I get an error message similar to this:   Server Error in '/MvcApplication31' Application. Compilation Error Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately. Compiler Error Message: CS0016: Could not write to output file 'c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\mvcapplication31\222b4fe6\4e80a86\App_global.asax.clb4bsnc.dll' -- 'The directory name is invalid. ' Source Error: [No relevant source

Shameless plug - Use the new JobServe Web API to search for jobs your way

As my signature states - I work for JobServe in the UK.  Over the past few months I have been working on a new REST API (using the ASP.Net Web API from pre-beta to RTM) and it is now in official public beta. Now of course, this isn't just so your good selves can start running your own job searches using whatever web-enabled client takes your fancy, but that is one of the cool benefits that has come out of it - and that's why we're calling it a public beta. At the time of writing, you can use the API to run almost any job search that you can on the website.  As you would expect, you can get the results in XML or JSON (also GET requests with search parameters in the query-string are supported as well).  Note that JSONP is not currently supported - but it's slated for a future release. Sounds great, how do I get in? In order to get cracking with this - you need to request an API token .  This is not an automated process but we should be able to get you set up in a

Serializing to attributes in WCF with DataContractSerializer

It’s a common problem – you want to return an object from a WCF service as XML, but you either want, or need, to deliver some or all of the property values as XML Attributes instead of XML Elements; but you can’t because the DataContractSerializer doesn’t support attributes (you’re most likely to have seen this StackOverflow QA if you’ve done a web search).  Most likely you’ve then migrated all your WCF service code to using the XmlSerializer (with all the XmlElement/XmlAttribute/XmlType attributes et al) – and you’ve cursed loudly. Well, I’m here to rescue you, because it is possible – and the answer to the problem is actually inferred from the MSDN article entitled ‘ Types supported by the Data Contract Serializer ’. The example I’m going to give is purely for illustration purposes only.  I don’t have a lot of time, so work with me! Create a new Asp.Net WCF service application, you can use Cassini as your web server (probably easier – otherwise you might have to enable Asp.Net