Dynamic Cast with Generic Collection

Greetings to my blog readers!

In this post I will share with you a trick of achieving something that, quite frankly, I do not recommend doing – dynamically casting types in generic collections.
In my previous post here I showed you that generic custom collections are great, type-safe and can be easily created by deriving from System.Collections.ObjectModel.Collection which provides a wealth of ready-made functionality. So, suppose you have a need for a custom generic collection like this, but you need to be able to treat it as a polymorphic type. For example, if you create a collection of integers, you want to be able to convert it to a collection of doubles when there is a need to do so. Or the other way around.

To begin with, you may try doing something like this, where NumericalList class is initialised with an int type, but then attempting to cast to a list of doubles to save into doubleList:

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;


namespace TypeSafety
{
    sealed class NumericalList<T> : System.Collections.ObjectModel.Collection<T>
    {
        
    }
    
    class EntryPoint
    {
        static void Main(string[] args)
        {
            NumericalList<int> intList = new NumericalList<int> {1,2,3,4,5};

            var doubleList = from double n in intList select n;
            
            foreach (var d in doubleList)
                Console.WriteLine(d);

            Console.ReadLine();
        }

    }
}

The above code will compile, but it will not run. The reason for this is not very intuitive because one should be able to cast an int to a double implicitly. The runtime exception is System.InvalidCastException, originating from the line where doubleList is initialised and later unwound in the foreach block. So, what is going on here?

Examining the IL for the above code we can find this statement corresponding to the instructions where we attempt the implicit cast: System.Linq.Enumerable::Cast(class[mscorlib]System.Collection.IEnumerable)
The Enumerable.Cast is called by the foreach iterator, and the cast fails with the exception. The method Cast is an extension to Enumerable class. You can examine the source code for the whole class here. Below I am providing the snippets of code from the referencesource.microsoft.com that are relevant to our problem:

public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) {
            IEnumerable<TResult> typedSource = source as IEnumerable<TResult>;
            if (typedSource != null) return typedSource;
            if (source == null) throw Error.ArgumentNull("source");
            return CastIterator<TResult>(source);
        }
 
        static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source) {
            foreach (object obj in source) yield return (TResult)obj;
        }

Since as never throws an exception, the exception is thrown from the CastIterator extension where the cast takes place. The explicit cast is from object to, in our case, double, which is invalid.

Ok, now that we know where and why the code fails, let’s look at how we can fix it. A simple fix is to move the cast to where it will work, i.e. to where the selected list element (an integer) can be cast to a double one at a time:

var doubleList = from n in intList select (double)n;

This does the job. But not at a class level. To be able to perform the kind of conversion we tried originally, we need to modify the default Cast extension. In particular, we need to remove the ‘casting object to a double’ part. To do this, we can utilise the dynamic keyword, which allows us to determine the type at runtime. I am not going to go into the details about this keyword here. Take a look at this comprehensive article on MSDN magazine if you are interested to learn more about dynamic. Below is the modified solution adapted for integers and doubles:

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;

namespace TypeSafety
{
    sealed class NumericalList<T> : System.Collections.ObjectModel.Collection<T>
    {
        public IEnumerable<TRes>CustomCast<TRes>()
        {
            if (typeof(TRes).Equals(typeof(double)) || typeof(TRes).Equals(typeof(int)))
            {
                dynamic runtime;
                foreach (var tr in this)
                {
                    runtime = tr;
                    yield return (TRes)runtime;
                }
            }
        }
    }
    
    class EntryPoint
    {
        static void Main(string[] args)
        {
            NumericalList<int> intList = new NumericalList<int> {1,2,3,4,5 };
            
            var doubleList = from double n in intList.CustomCast<double>() select n;

            foreach (var d in doubleList)
                Console.WriteLine(d);

            Console.ReadLine();
        }

    }
}


The above works for doubles and integers.

Advertisements
This entry was posted in C# Programming and tagged . Bookmark the permalink.