When Cast<T> doesn’t Cast to T

A friend asked me about some issues he was having using Enumerable.Cast<T>(). In his mind, it just wasn’t working. Like so many problems, it was working correctly, just not the way he expected. It’s worth examining.

Examine this class:

          class MyType
             2: {
          public String StringMember { get; set; }
          operator String(MyType aString)
             6:     {
          return aString.StringMember;
             8:     }
          operator MyType(String aString)
            11:     {
          new MyType { StringMember = aString };
            13:     }
            14: }

Note:  I normally recommend against conversions, (See Item 28 in Effective C#), but that’s the key to this issue.

Consider this code (assume that GetSomeSttrings() returns a sequence of strings)

             1: var answer1 = GetSomeStrings().Cast<MyType>();
             3: {
          foreach (var v in answer1)
             5:         Console.WriteLine(v);
             6: }
          catch (InvalidCastException)
             8: {
             9:     Console.WriteLine("Cast Failed!");
            10: }

You’d expect that GetSomeStrings().Cast<MyType>() would correctly convert each string to a MyType usingthe implicit conversion operator defined in MyType. It doesn’t, it throws an InvalidCastException.

The above code is equivalent to this construct, using a query expression:

             1: var answer3 = from MyType v in GetSomeStrings()
             2:               select v;
             4: {
          foreach (var v in answer3)
             6:         Console.WriteLine(v);
             7: }
          catch (InvalidCastException)
             9: {
            10:     Console.WriteLine("Cast failed again");
            11: }

The type declaration on the range variable is converted to a call to Cast<MyType> by the compiler (See Item 36 in More Effective C#). Again, it throws an InvalidCastException.

Here’s one way to restructure the code so that it works:

             1: var answer2 = from v in GetSomeStrings()
             2:               select (MyType)v;
          foreach (var v in answer2)
             4:     Console.WriteLine(v);

What’s the difference? The two versions that don’t work use Cast<T>(), and the version that works includes the cast in the lambda used as the argument to Select().

And, that’s where the difference lies.

Cast<T>() Cannot access User Defined Conversions

When the compiler creates IL for Cast<T>, it can only assume the functionality in System.Object. System.Object does not contain any conversion methods, therefore, Cast<T>() does not generate any IL that might call any conversion operators.

Cast<T>() will only succeed if its argument is not derived from the target (or a type that implements the target if the target is an interface), Cast<T> fails.

On the other hand, placing the cast in the lambda for the Select clause enables the compiler to know about the conversion operators in the MyType class. That means in succeeds.

As I’ve pointed out before, I normally view Conversion operators as a code smell. On occasion, they are useful, but often they’ll cause more problems than they are worth. Here, without the conversion operators, no developer would be tempted to write the example code that didn’t work.

Of course, if I’m recommending not to use conversion operators, I should offer an alternative.  MyType already contains a read/write property to store the string property, so you can just remove the conversion operators and write either of these constructs:

             1: var answer4 = GetSomeStrings().Select(n => new MyType { StringMember = n });
             2: var answer5 = from v in GetSomeStrings()
             3:               select new MyType { StringMember = v };

Also, if you needed to, you could create a different constructor for MyType.

Created: 7/23/2009 3:04:04 AM

Current Projects

I create content for .NET Core. My work appears in the .NET Core documentation site. I'm primarily responsible for the section that will help you learn C#.

All of these projects are Open Source (using the Creative Commons license for content, and the MIT license for code). If you would like to contribute, visit our GitHub Repository. Or, if you have questions, comments, or ideas for improvement, please create an issue for us.

I'm also the president of Humanitarian Toolbox. We build Open Source software that supports Humanitarian Disaster Relief efforts. We'd appreciate any help you can give to our projects. Look at our GitHub home page to see a list of our current projects. See what interests you, and dive in.

Or, if you have a group of volunteers, talk to us about hosting a codeathon event.