Coding

Implementing a TypeConverter

The .Net Extensions library on CodePlex has a great extension method: ConvertTo<T>

There are several overloads available.  Here is its core implementation:

public static T ConvertTo<T>(this object value, T defaultValue)
{
  if (value != null)
  {
    var targetType = typeof(T);

    if (value.GetType() == targetType) return (T)value;

    var converter = TypeDescriptor.GetConverter(value);
    if (converter != null)
    {
      if (converter.CanConvertTo(targetType))
        return (T)converter.ConvertTo(value, targetType);
    }

    converter = TypeDescriptor.GetConverter(targetType);
    if (converter != null)
    {
       if (converter.CanConvertFrom(value.GetType()))
         return (T)converter.ConvertFrom(value);
    }
  }

  return defaultValue;
}

I only just discovered this project last weekend, but I have a similar method in my utility library called ConvertSimpleType<T> which I adapted from the ASP.NET MVC source.  What’s great about these methods is that the let you move the logic for implementing casts and conversions out of the consuming classes and into a TypeConverter.  If appropriate, the TypeConverter can delegate this logic back to the class itself, keeping all the logic about the class in one place, the class itself.

Example

Say you are using a stream and wanted to decouple from System.IO.Stream for testing purposes.  You’ve created a an interface and a wrapper class like so:

public interface IStreamWrapper
{
    void Close();

    // Other methods omitted...
}

public class StreamWrapper : IStreamWrapper
{
    private readonly Stream stream;

    public StreamWrapper(Stream stream)
    {
        this.stream = stream;
    }

    public void Close()
    {
        stream.Close();
    }
}

You are happy now because you can easily create mocks against IStreamWrapper for all your tests that consume streams.  However you may find that you run into a problem when interacting with other classes.  Say you want to save your XML data to your wrapped stream.

public void Respond(
    IStreamWrapper responseStream,
    XDocument responseDataXml)
{
    responseDataXml.Save(responseStream); // Does not compile
}

You can make the compiler happy with a cast, but that just moves the problem to runtime:

public void Respond(
    IStreamWrapper responseStream,
    XDocument responseDataXml)
{
    // Now throws NullReferenceException at runtime...
    responseDataXml.Save(responseStream as Stream);
}

You can add a conversion operator to the wrapper, but the runtime will not use it because that implementation is part of the class, but Respond only knows about the interface, so you would have to cast back to StreamWrapper first before you can take advantage of the “implicit” conversion operator.

public class StreamWrapper : IStreamWrapper
{
    // New method
    public static implicit operator Stream(StreamWrapper source)
    {
       return source.stream;
    }
}
public void Respond(
     IStreamWrapper responseStream,
     XDocument responseDataXml)
{
  // Works if responseStream is a StreamWrapper, but not with other
  // IStreamWrapper implementations
  responseDataXml.Save(responseStream as StreamWrapper);
}

But the real problem is that you have taken an explicit dependency on StreamWrapper. The method interface advertises that it expects any IStreamWrapper, but it is lying.  Say you have a FakeStreamWrapper implementation for testing.  This fake class should be convertible to stream, and indeed the conversion operator is exactly the same as StreamWrapper’s, except for the input:

public class FakeStreamWrapper : IStreamWrapper
{
  private readonly Stream stream;

  public FakeStreamWrapper(Stream stream)
  {
         this.stream = stream;
  }

  public static implicit operator Stream(FakeStreamWrapper source)
  {
         return source.stream;
  }

  public void Close()
  {
         Console.WriteLine("Close was called.");
  }
}

If you submit this fake to the Respond method, you will get a NullReferenceException in the StreamWrapper conversion operator and FakeStreamWrapper’s conversion operator is never called.   Respond asks the runtime to cast responseStream to StreamWrapper, but the runtime does not know how to do that for FakeStreamWrapper, so it supplies (StreamWrapper)null as the value instead.  Then the runtime tries to implicitly convert the null value into a Stream to satisfy the requirements of the XDocument.Save(Stream) method.  This is when StreamWrapper’s conversion operator is called with a null input value, resulting in the exception.

So what we need is a dynamic way to convert from any implementation of IStreamWrapper into stream.  Then our consuming class will not need to know about every wrapper implementation we might create.  This is where the TypeConverters come in, but using them involves some ceremony (see the implementation at the beginning of this post).  So using a method like ConvertTo<T> makes using TypeConverters very handy because all that ceremony is hidden inside the extension method.

Here is what we want our new Respond method to look like:

public void Respond(
     IStreamWrapper responseStream,
     XDocument responseDataXml)
{
     responseDataXml.Save(responseStream.ConvertTo<Stream>());
}

Inside ConvertTo<T>, the TypeDescriptor class takes care of finding out which implementation of IStreamWrapper was passed in, then looking for an appropriate conversion mechanism to Stream. The logic is simple:

  • Can we find a converter that knows how to convert a StreamWrapper to a Stream?
  • Can we find a converter that knows how to build a Stream from a StreamWrapper?
  • Return default if we can’t figure it out.

In our case, we should be able to take the first path, because StreamWrapper and FakeStreamWrapper each have an implicit operator to Stream.  But our classes are not quite ready to work with this new way of converting types.  TypeDescriptor does not look for implicit operators, it looks for a TypeConverter implementation associated with one of the types involved in the conversion, so we will need to create a TypeConverter for the TypeDescriptor to find.  Also, we will need some way of telling the TypeDescriptor to use the TypeConverter we create.  This last piece sounds like metadata, and it is, so if you are familiar with metadata in .NET you probably expect that this metadata will be passed with an attribute, and you would be correct.

Implementing TypeConverter

Without a custom TypeConverter and some way of telling the TypeDescriptor class to use it, we still get a NullReferenceException when using ConvertTo<Stream>() in our example.  When ConvertTo<T> cant find a way to convert to the target type, it returns the default, which is null for reference classes like Stream, causing XDocument.Save(Stream) to throw the exception.  So lets get our classes working with ConvertTo<T>!

Add Metadata

This part certainly involves less typing.  The attribute we want to use is in System.ComponentModel, so import that namespace:

using System.ComponentModel;

Now add the attribute to your class declaration:

[TypeConverter(typeof(StreamWrapperConverter))]
public class StreamWrapper : IStreamWrapper
{

That’s it for metadata!  Of course, StreamWrapperConverter doesn’t exist, so we need to implement it before trying to compile.

Implement TypeConverter

In this minimal TypeConverter implementation, we will just make sure that we can convert from StreamWrapper to Stream.  We wont worry about converting from Stream to StreamWrapper, although that code is simple, I’ll leave it out for brevity:

using System;
using System.ComponentModel;
using System.Globalization;

namespace ImplementingTypeConverter
{   

  public class StreamWrapperConverter : TypeConverter
  {    

    public override bool CanConvertTo(    

      ITypeDescriptorContext context,
      Type destinationType)
    {    

      return destinationType == typeof(global::System.IO.Stream) ||

     base.CanConvertTo(context, destinationType);
    }

    public override object ConvertTo(
      ITypeDescriptorContext context,
      CultureInfo culture,
      object value,
      Type destinationType)
    {
      var proxy = value as global::ImplementingTypeConverter.StreamWrapper;
      if (destinationType == typeof(global::System.IO.Stream) &&
        proxy != null)
      {
        return (global::System.IO.Stream)proxy;
      }

      return base.ConvertTo(context, culture, value, destinationType);
    }
  }
}

Again, we see a lot of ceremony, but at its heart, the code is very simple.  TypeDescriptor will read the TypeConverterAttribute on StreamWrapper which points to this TypeConverter.  When CovnertTo<T> asks for the converter, TypeDescriptor will provide an instance of this class.  TypeDescriptor doesn’t know what conversions this converter can handle, it just knows that this TypeConverter handles conversions for StreamWrapper.  Since its possible that this TypeConverter can’t handle the requested conversion, ConvertTo<T> will ask this class if it can convert to Stream using the CanConvertTo method.  CanConvertTo will check the destination type, and return true, or revert to the base implementation if the destination type is not Stream.

Once ConvertTo<T> has received confirmation that the StreamWrapperConverter can handle the conversion,  it calls ConvertTo on StreamWrapperConverter.  The ConvertTo method makes sure that it can cast the input object to StreamWrapper, double checks that the destination is Stream, then casts the wrapper object to Stream and returns it.  If the source or destination types are incorrect, it sends the data down to the base class.  In most cases this will result in an exception, just as if StreamWrapperConverter didn’t exist.  We double check the destination type because we don’t know who is calling the StreamWrapperConverterConvertTo<T> is well behaved, and it calls CanConvertTo before trying to convert, but less well behaved callers might not do so.

Unfortunately, TypeConverter returns the result as System.Object, so ConvertTo<T> will cast the result one more time before sending it back to the consuming class.  Working together, TypeConverter, TypeDescriptor and ConvertTo<T> accomplish a lot, and by implementing another TypeConverter for FakeStreamWrapper, we will be able to use either class with our example implementation and the Respond method would never need to know about either wrapper class.  The TypeConverter knows about what types its associated class can be converted to or from, and how to do that.  In most cases, I prefer to push that knowledge into a static method on the class itself.  This keeps the TypeConverter a little more ignorant, and puts the knowledge about the converting the class in the class.  It also makes unwrapping objects really easy, because the static method has access to the wrapped private field.

Improving the Implementation

In a real implementation you will probably want to implement the opposite conversion as well, which would allow ConvertTo<T> to take a Stream and wrap it for us.  This is easy to accomplish, since all the TypeConverter would need to do is call the wrapper’s constructor and supply the Stream instance.

You might also notice some warnings with this implementation if you use FxCop.  FxCop will issue warning CA2225 if you implemented StreamWrapper (or FakeStreamWrapper) as described above.  This warning points out that not all languages support operator overloading.  The recommendation is to create a named method that provides access to the overloaded functionality in those languages.  So we would add a static method to StreamWrapper:

public static Stream ToStream(StreamWrapper source)
{
    return source.stream;
}

Since the body of this method is the same as the implicit operator, you could have the operator call the method, or the method call the operator.  Either way works if you would like to be completely DRY.  The TypeConverter could also call ToStream instead of using a cast, I prefer calling ToStream in the TypeConverter.  For symmetry, I would also implement a FromStream method and an implicit operator from Stream.

The consuming class could still throw a null reference exception if left as is.  The wonderful thing about ConvertTo<T> is that it dynamically finds TypeConverters for IStreamWrapper implementations that comply with the implementation requirements, but nothing guarantees that IStreamWrapper implementations must comply.   For example, a Moq.Mock<IStreamWrapper> will not have the metadata needed by TypeDescriptor to find a converter, and so ConvertTo<T> will end up returning null.  So, the consuming class will need to be refactored to check for null before attempting to use the value returned by ConvertTo<T>.

When dealing with a wrapper class, the operators, conversion methods, and TypeConverter are usually boilerplate.  While you may want to implement more complicated conversions by hand, wrapper conversions are perfect targets for code generation.  I have code snippets that will implement the wrapper class and the TypeConverter.  These are great when I know I need a wrapper, but not as useful when working on legacy code that I want to convert into wrappers.  In that case, I end up doing a lot of cut and paste after generating the code with my snippets.  In the future I hope have a CodeRush plugin that will add the necessaries to the existing class and generate the TypeConverter for me.  I’ll either implement this myself or hope that @MillerMark is reading this, and finds it sufficiently interesting, we might see this idea appear in a #CodeRush feature workshop webinar🙂.

Whichever is quicker Winking smile

Code

Here is the completed example code, and code snippets.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s