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

by Lord Zoltan 1. August 2008 09:26

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:

public static void Clone(object source, object dest)
{
   //(omitted) check that the source and destination types are equal with Type.Equals

   //get only public instance properties, from all types in T's hierarchy.
   PropertyInfo[] props = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
   MethodInfo getmethod;
   MethodInfo setmethod;
   foreach (var p in props)
   {
    //make sure the get and set accessors are available.
    getmethod = p.GetGetMethod();
    setmethod = p.GetSetMethod();
    if (getmethod == null || setmethod == null)
      continue;
    //invoke the getmethod on the source, and use the result to pass to the set method on the destination
    setmethod.Invoke(dest, new object[] { getmethod.Invoke(source, new object[] { }) });
   }
}

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://www.lordzoltan.org/blog/post/Pseudo-Template-Meta-Programming-in-C-Sharp-Part-2.aspx, 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.

//the empty interface.  Applying the interface to a type means that a developer expects to be able to
//clone it using our clone method.
public interface IGenericCloneable
{
}

public static class IGenericCloneableExtensions
{
   //generic extension method that will only appear in the member list for types
   //that implement the IGenericCloneable interface.  Could have simply made the
   //first parameter IGenericCloneable, and removed the <T> - but since we're
   //doing reflection, having the <T> helps a great deal - not to mention meaning that
   //we no longer have to check for type equality between source and dest.
   public static void Clone<T>(this T source, T dest) where T:IGenericCloneable
   {
      PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance | 
                                                     
BindingFlags.FlattenHierarchy);
      MethodInfo getmethod;
      MethodInfo setmethod;
      foreach (var p in props)
      {
        getmethod = p.GetGetMethod();
        setmethod = p.GetSetMethod();
        if (getmethod == null || setmethod == null)
           continue;
        setmethod.Invoke(dest, new object[] { getmethod.Invoke(source, new object[] { }) });
      }
   }
}

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:

public class Cloneable1 : IGenericCloneable
{
   public int ID {get; set;}
}

public class Cloneable2 : Cloneable1
{
   public string Name {get; set;}
}

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:

public static class Utility
{
   public GetClone(Cloneable1 source, Cloneable1 dest)
   {
       source.Clone(dest);
   }
}

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

[TestMethod]
public void TestClone1()
{
   Cloneable1 c1 = new Cloneable1() { ID = 10 };
   Cloneable1 c2 = new Cloneable1();
   Utility.GetClone(c1, c2);
   Assert.AreEqual(c1.ID, c2.ID); //should be fine.
}

[TestMethod]
public void TestClone2()
{
   Cloneable2 c1 = new Cloneable2() { ID = 10, Name = "Fred" };
   Cloneable2 c2 = new Cloneable2();
   Utility.GetClone(c1, c2);
   Assert.AreEqual(c1.ID, c2.ID); //should be fine.
   Assert.AreEqual(c1.Name, c2.Name); //will fail
}

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 the IGenericCloneableExtensions class, 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:

public static void Clone(this IGenericCloneable source, IGenericCloneable dest)
{
   //this line of looks nasty, but all it does is to get a reference to the base generic method
   //declaration of Clone{T}, and make a new version for the supertype of the passed instance
   //of IGenericCloneable (string notation for all generics is '[Name]`[num type params]' - where
   //the ` character is the one you find just under the [Esc] key, to the left of '1')
   //Then it invokes it - note that no instance reference is passed to .Invoke - because once
   //extension methods are actually compiled into an assembly, they are simply treated as static
   //methods - all the wizardry is done by the compiler when an extension is actually used.
  
typeof(IGenericCloneableExtensions).
       
GetMethod("Clone`1", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).
            MakeGenericMethod(new Type[] { instance.GetType() }).
                 Invoke(null, new object[] { source, dest });

}

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 extensions available to code that only has instances of the interface (which, after all, is what most people will expect of an interface).  However, 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:

//modify the original version to check the actual instance types of source and dest
public static void Clone<T>(this T source, T dest) where T:IGenericCloneable
{
   Type[] instancetypes = new Type[] {source.GetType(), dest.GetType()};
   //if the source's instance type passed to the method is not equal to the <T> it
   //was called with, we have to look at dynamically promoting T to the superclass.
   //however we can only safely do that if the instance type of the destination is
   //either equal to, or a base of the source's instance type - promotion of to an
   //instance type that is derived from <T> is not safe, because it can't be
   //guaranteed that all properties would be cloned - demotion, however, is safe.
   if(instancetypes[0].Equals(typeof(T)) == false)
   {
      //check ultimate
      if(instancetypes[0].Equals(instancetypes[1])==false &&
         instancetypes[0].IsSubClassOf(instancetypes[1]) == false)
            throw new ArgumentException("If the two instance types are not equal, " +
                     "the first instance type must be derived from the target type");
      //now recurse into a dynamically generated version of this same method
      //this time we can use MethodInfo.GetCurrentMethod to get this method,
      //move back to the original generic (without an explicit <T> set),
      //compile it on the fly, then invoke it
      ((
MethodInfo)MethodInfo.GetCurrentMethod()).
          GetGenericMethodDefinition().
             MakeGenericMethod(new Type[] {instancetypes[0]}).
                Invoke(null, new object[]{source, dest});
      return;

   }


   //the rest is the same as before
   PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance | 
                                                  
BindingFlags.FlattenHierarchy);
   MethodInfo getmethod;
   MethodInfo setmethod;
   foreach (var p in props)
   {
     getmethod = p.GetGetMethod();
     setmethod = p.GetSetMethod();
     if (getmethod == null || setmethod == null)
        continue;
     setmethod.Invoke(dest, new object[] { getmethod.Invoke(source, new object[] { }) });
   }
}

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.

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

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

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>:

public static T CloneNew<T>(this T source) where T:IGenericCloneable, new()
{
   T result = new T();
   source.Clone(result);
   return result;
}

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! Wink

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Technical

Comments

Comments are closed

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen