In my previous blog post, I discussed how to build a .NET component that works with different versions of a particular library. In that post, I demonstrated a technique for accessing instance methods that get added in a later version of a library. In this post, I’ll modify the technique some to make it work reasonably well for static methods that get added in a later version of a library.

Let’s starts by looking at Version 1.0 of some library class:

public sealed class VersioningType {    // v1.0
   // Other methods not shown
}

And, let’s say that version 2.0 of the same library class looks like this:

public sealed class VersioningType {     // v2.0
public static String NewStaticMethod(String arg) {// New STATIC method introduced in v2.0
      return "Real static method: &quo t; + arg;
   }
   // Other methods not shown
}

Now, we want to write some code in a component that calls VersioningType’s NewStaticMethod if it exists at runtime. To do this, I would add the following VersioningTypeExtensions class into my component code:

internal static class VersioningTypeExtensions {

   private static dynamic s_NewStaticMethodFound =

      new StaticMemberDynamicWrapper(typeof(VersioningType));   // Assume method exists

   /// <summary>The stub for VersioningType’s static NewStaticMethod method</summary>

   public static String NewStaticMethod(String arg) {

      if (s_NewStaticMethodFound != null) {

         try {

            // We think the method exists, try to invoke it

            return s_NewStaticMethodFound.NewStaticMethod(arg);

         }

         catch (RuntimeBinderException) {

            // The method doesn’t exist; in the future, avoid the dynamic/exception perf hit

            s_NewStaticMethodFound = null;

         }

      }

      // This is the default implementation we want if the method doesn’t exist

      return "Stub static   method: " + arg;

   }

}

This code introduces a static method called NewStaticMethod that matches the same signature as the NewStaticMethod introduced in v2.0 of the library. Now, in my component’s code, I can call this static stub method and I get compile-time type safety at all the call sites. This code uses a helper class that I wrote, called StaticMemberDynamicObject. I show the code for this class at the bottom of this blog post. You construct an instance of my StaticMemberDynamicObject class by passing a type to its constructor. In short, by casting a reference to a StaticMemberDynamicObject object to C#’s dynamic type, you can invocate a type’s static methods, properties, or fields dynamically at runtime. It makes the type look like an object and this class for use with dynamic types. The StaticMemberDynamicObject class is useful in many programming scenarios.

Inside VersioningTypeExtension’s NewStaticMethod stub method, I use the s_NewStaticMethodFound field (which is of the dynamic type) to try to call VersioninType’s static NewStaticMethod. If our component is running against v2.0 of the library, then its NewStaticeMethod will be invoked. But, if our component is running against v1.0 of the library, then attempting to call NewStaticMethod will throw a RuntimeBinderException. My stub method catches this exception, sets the static field to null and then my code that simulates the default behavior of this method. In this case, I return a string indicating that my stub method handled the call.

The static s_NewStaticMethodFound field exists to improve performance. Once the stub determines that VersioningType doesn’t offer the desired method, this field is set to null and future invocations of the stub method avoid the performance hit of entering/leaving a try block, attempting to dynamically invoke the NewStaticMethod and the performance hit of processing the thrown RuntimeBinderException. In addition, the StaticMemberDynamicObject object can be garbage collected now.

If in the future, we decide that our component no longer needs to work with v1.0 of the library, we can build our component code against v2.0 of the library and we can leave all our code alone and it will just suffer a slight performance penalty whenever calling NewStaticMethod. Or, we can delete our stub method and then modify all of the call sites to call VersioningType’s static NewStaticMethod method directly.

The instance method technique shown in my previous blog posting used C#’s extension methods to implement the stub. This had two significant benefits. First, the code at the call site looked just like the code you would have to call the instance method meaning that no source code would have to change in order to call the V2.0 library. Second, if we build the component against v2.0 of the library, then the compiler would bind to the instance method instead of the extension method resulting in a performance improvement by not calling the stub method. Unfortunately, we don’t get these benefits with static methods because extension methods don’t allow you to extend a type with a static method. Extension methods can only extend objects with instance methods.

Below is the code for my useful StaticMemberDynamicObject class that allows you to dynamically invoke static members of a type. This class is derived from the .NET Framework’s System.Dynamic.DynamicObject class which allows instance of the class to control how its members are invoked when invoked using C#’s dynamic type.

/// <summary>Construct an instance of this class and cast the reference to ‘dynamic’ 
/// to dynamically invoke a type’s static members</summary>

<br>internal sealed class StaticMemberDynamicWrapper : DynamicObject {

<br>   private const BindingFlags c_bf = BindingFlags.Public | BindingFlags.Static;

<br>   private readonly Type m_type;

<br>   public StaticMemberDynamicWrapper(Type type) { m_type = type; }

<br>

<br>   public override IEnumerable&lt;String&gt; GetDynamicMemberNames() {

<br>      return m_type.GetMembers(c_bf).Select(mi =&gt; mi.Name);

<br>   }

<br>

<br>   public override bool TryGetMember(GetMemberBinder binder, out object result) {

<br>      var members = m_type.GetMember(binder.Name, MemberTypes.Field | MemberTypes.Property, c_bf);

<br>      if (members.Length != 1) { result = null; return false; }

<br>      result = (members[0].MemberType == MemberTypes.Field)  <br>         ? ((FieldInfo)members[0]).GetValue(null)  <br>         : ((PropertyInfo)members[0]).GetValue(null, null);

<br>      return true;

<br>   }

<br>

<br>   public override bool TrySetMember(SetMemberBinder binder, object value) {

<br>      var members = m_type.GetMember(binder.Name, MemberTypes.Field | MemberTypes.Property, c_bf);

<br>      if (members.Length != 1) return false;

<br>      if (members[0].MemberType == MemberTypes.Field)

<br>         ((FieldInfo)members[0]).SetValue(null, value);

<br>      else  <br>         ((PropertyInfo)members[0]).SetValue(null, value, null);

<br>      return true;

<br>   }

<br>

<br>   public override Boolean TryInvokeMember(InvokeMemberBinder binder,  <br>      Object[] args, out Object result) {

<br>

<br>      MethodInfo method = m_type.GetMethod(binder.Name, c_bf);

<br>      if (method == null) { result = null; return false; }

<br>      result = method.Invoke(null, args);

<br>      return true;

<br>   }

<br></font><font size="2" face="Courier New">}</font>