Solving Generic Casting Issues with Actions

Leave a comment

August 25, 2011 by Alistair Deneys

Generics in .net are a great way to avoid having to repeat your code when all you need to do is alter a few of the types involved in the code. Recently I was retrieving some objects from a data store in several different places in my code, and just altering the return types at each call. This felt like an ideal candidate for generics.

I didn’t want to have to cast the array of objects to the appropriate type when I called the method. Instead I wanted to have the method return the appropriate type which is passed by the calling code.

The complicating factor around what I was trying to do was to do with the fact that I was passing a particular type into my generic method, but casting from a different type which the generic type didn’t inherit from. Even though I had implicit operators defined to go between my base type and wrapper, the compiler for some reason couldn’t see the conversion and failed. If I had the luxury of being able to make my wrapper inherit from the base class then everything would have been alright, but the base class I was dealing with came out of another system (a Sitecore.Data.Items.Item).

Let’s explore some code.

Below is a custom class and a wrapper. Notice how the wrapper class contains the implicit operators to allow implicit conversion to and from my custom class.

public class RawClass 
{ 
  public int ID { get; set; }

  public RawClass(int id) 
  {
    ID = id; 
  } 
}

public class Wrapper 
{ 
  private RawClass inner = null;

  public static implicit operator Wrapper(RawClass instance) 
  {
    return new Wrapper(instance); 
  }

  public static implicit operator RawClass(Wrapper instance) 
  { 
    return instance.inner; 
  }

  public Wrapper(RawClass instance) 
  {
    inner = instance;
  }

  public int GetId()
  {
    return inner.ID;
  }
}

The implicit operators let me treat an instance of Wrapper like an instance of RawClass.

Wrapper wrap = new RawClass(3);
RawClass raw = wrap;

I can also pass instances of Wrapper to methods expecting RawClass without an explicit cast.

public string GetId(RawClass raw) 
{
  return raw.ID.ToString();
}

var raw = new RawClass(5); 
Wrapper wrap = raw; 
var id = GetId(wrap);

Note above how I didn’t cast wrap to RawClass when I passed it to the GetId method.

OK, so all of the above compiles and works. Onto my problem.

I was working on a method to fetch several objects of one type but I wanted to define through generics the type to return from the method. In the above example code, RawClass is the type of object from the data store, but I want to cast it to the generic defined type for the return. The reason I wanted to use generics was because this method was going to be called from several different places in my code, all with different return types. I wanted to be able to call the method as such:

var items = GetItems<Wrapper>();
var items2 = GetItems<DifferentWrapper>();

For demonstration purposes I’ll use a dummy method to return me some data. In implementation this method would be replaced with one that actually communicated with the data store.

private IEnumerable<RawClass> LoadFromDataStore()
{
  // Load data from data store 
  for(int i = 0; i < 4; i++) 
    yield return new RawClass(i); 
}

And now we can write the GetItems method which is the generic method that loads the data and casts it to the appropriate type.

private T[] GetItems<T>() where T : class
{
  var list = new List<T>(); 
  var objects = LoadFromDataStore();

  foreach(var ob in objects)
  {
    list.Add((T)ob); 
  }

  return list.ToArray(); 
}

Not quite. The above code won’t compile. The compiler will give an error about converting from RawClass to T.

Error    Cannot convert type 'GenericCasting.RawClass' to 'T'

I could replace the cast above with a safe cast using the as operator, but it doesn’t really give us what we want. The safe cast will return null instead of throwing an exception when it can’t cast from one type to another. Even though the above code should work because we’ve defined the implicit conversion operators, it doesn’t.

I struggled with this code for quite some time, even getting it to a point where I got errors stating there is no conversion from RawClass to Wrapper. Are you serious? I’m even using the implicit operators in other parts of the code!

I knew I could cast the objects if I wasn’t dealing with a generic type, and the calling code knew what type it wanted to deal with, so I needed to pass control momentarily back to the calling code and have it do the casting. I’ve shown previously how to pass control back to the calling code, and this approach is basically the same.

Enter the Action<> class. Action is simply a generic delegate and an efficient way to use delegates when you don’t want to define a new delegate type. I simply pass as the type parameters the types I want my delegate to require. Let’s have a look at how I can update the previous method to use an action which would be called to pass control back to the calling code.

private T[] GetItems<T>(Action<List<T>, RawClass> act) where T : class 
{ 
  var list = new List<T>(); 
  var objects = LoadFromDataStore();

  foreach(var ob in objects) 
  { 
    act(list, ob); 
  }

  return list.ToArray(); 
}

In my Action I’ve define the parameters as being of type List<T> and RawClass. The calling code will need to cast the RawClass and add it to the list. To call this method:

var list = GetItems<Wrapper>((l, ob) => { l.Add(ob); });

Mmmm. Syntactic sugar.

Rather than using a traditional delegate I’m using a lambda expression. Note how I don’t even need to cast, because the implicit conversion is now working when the compiler can see what the types are.

And that’s it. Using this technique I’ve worked around the limitations of the compiler not seeing the conversions between classes when using generics.

Advertisements

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

Categories

The views expressed on this blog are solely my own and do not necessarily reflect the views of my employer.
%d bloggers like this: