19

To convert between some LINQ to SQL objects and DTOs we have created explicit cast operators on the DTOs. That way we can do the following:

DTOType MyDTO = (LinqToSQLType)MyLinq2SQLObj;

This works well.

However when you try to cast using the LINQ .Cast() extension method it trows an invalid cast exception saying cannot cast type Linq2SQLType to type DTOType. i.e. the below does not work

List<DTO.Name> Names = dbContact.tNames.Cast<DTO.Name>()
                                               .ToList();

But the below works fine:

DAL.tName MyDalName = new DAL.tName();
DTO.Name MyDTOName = (DTO.Name)MyDalName;

and the below also works fine

List<DTO.Name> Names = dbContact.tNames.Select(name => (DTO.Name)name)
                                               .ToList();

Why does the .Cast() extension method throw an invalid cast exception? I have used the .Cast() extension method in this way many times in the past and when you are casting something like a base type to a derived type it works fine, but falls over when the object has an explicit cast operator.

Ben Robinson
  • 21,601
  • 5
  • 62
  • 79
  • possible duplicate of [Puzzling Enumerable.Cast InvalidCastException](http://stackoverflow.com/questions/445471/puzzling-enumerable-cast-invalidcastexception) – Thomas Levesque May 12 '10 at 14:15

3 Answers3

26

The Cast<> extension method does not apply user-defined conversions. It can only cast to interfaces or within the class heirarchy of the supplied type.

User defined conversions are identified at compile time based on the static types involved in the expression. They cannot be applied as runtime conversions, so the following is illegal:

public class SomeType
{
  public static implicit operator OtherType(SomeType s) 
  { 
    return new OtherType(); 
  }
}

public class OtherType { }

object x = new SomeType();
OtherType y = (OtherType)x; // will fail at runtime

It doesn't matter whether a UDC exists from SomeType to OtherType - it cannot be applied through a reference of type object. Trying to run the above code would fail at runtime, reporting something like:

System.InvalidCastException: 
    Unable to cast object of type 'SomeType' to type 'OtherType'

Cast<>() can only perform representation preserving conversions ... that's why you can't use it to apply user-defined conversions.

Eric Lippert has a great article about the behavior of the cast operator in C# - always a worthwhile read.

LBushkin
  • 129,300
  • 32
  • 216
  • 265
  • I do say in my question that i have observed the behaviour you descibe through experience, what i am asking is WHY does it not work for user-defined conversions. What is it doing differently that does not work with explicit cast operators, how is it casting? – Ben Robinson May 12 '10 at 14:10
  • Ben: I was working on elaborating my answer as you were writing your comment :) Let me know if this provides clarity behind why `Cast<>()` works the way it does. – LBushkin May 12 '10 at 14:15
  • OK yes that makes sense thanks, i did a bit of digging with reflector and it was nearly indecipherable but it did hint toward your explanation, thanks. – Ben Robinson May 12 '10 at 14:26
  • 2
    @Ben: Imagine that you are building a runtime like the CLR. Do you really want to embed in that runtime the rules of the C# programming language for binding conversions? What if the VB/F#/JScript/Perl/Ruby/C/C++/J#/Python/... rules are different? Why should the C# rules get special treatment in the runtime? Therefore the rules you get at runtime are the simplest possible "lowest common denominator"; just a type test. If you want compile-time behaviour, either use "dynamic" in C# 4 to get it at runtime, or write your code so that the resolution of the conversion is done at compile time. – Eric Lippert May 12 '10 at 14:46
2

If you decompile the Linq assembly you get code resembling the following. The previous answer is correct, ultimately the cast is from 'object' to target-type which will always fail for custom types.

private static IEnumerable<TResult> CastIterator<TResult>( IEnumerable source )
{
    foreach(object current in source)
    {
        yield return (TResult)( (object)current );
    }
    yield break;
}

public static IEnumerable<TResult> DCast<TResult>( this IEnumerable source )
{
    IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
    if(enumerable != null)
    {
        return enumerable;
    }
    if(source == null)
    {
        throw new ArgumentNullException( "source" );
    }
    return CastIterator<TResult>( source );
}

TFish

tfish
  • 21
  • 1
0

For those that hit this question looking for a workaround...

Dim res = arrayOfStrings.Select(Function(__) CType( __, YourType ))

Not sure the exact semantics with C#, but i'm sure it's pretty easy.

iQueue
  • 191
  • 1
  • 6