tags:

views:

2316

answers:

4

I've created two classes, with one of them having an implicit cast between them:

public class Class1
{
    public int Test1;
}

public class Class2
{
    public int Test2;

    public static implicit operator Class1(Class2 item)
    {
        return new Class1{Test1 = item.Test2};
    }
}

When I create a new list of one type and try to Cast<T> to the other, it fails with an InvalidCastException:

List<Class2> items = new List<Class2>{new Class2{Test2 = 9}};
foreach (Class1 item in items.Cast<Class1>())
{
    Console.WriteLine(item.Test1);
}

This, however, works fine:

foreach (Class1 item in items)
{
    Console.WriteLine(item.Test1);
}

Why is the implicit cast not called when using Cast<T>?

+5  A: 

Because, looking at the code via Reflector, Cast doesnt attempt to take any implicit cast operators (the LINQ Cast code is heavily optimised for special cases of all kinds, but nothing in that direction) into account (as many .NET languages won't).

Without getting into reflection and other things, generics doesnt offer any out of the box way to take such extra stuff into account in any case.

EDIT: In general, more complex facilities like implicit/explict, equality operators etc. are not generally handled by generic facilities like LINQ.

Ruben Bartelink
+1  A: 

Thanks for that I was about to use that exact case somewhere. You have saved me a pile of time. As a possible solution to your problem you could use ConvertAll<> instead, like so:

foreach (Class1 item in items.ConvertAll<Class1>((i) => (Class1)i))
{
     Console.WriteLine(item.Test1);
}

EDIT: or if you want to be more explicit that the cast is implicit then this works too:

foreach (Class1 item in items.ConvertAll<Class1>(i => i))
{
     Console.WriteLine(item.Test1);
}
David McEwing
Interesting, you can probaly remove the explicit cast in that case, which would make it clear that the casting is to be implicit? In not, you could use Select()
Ruben Bartelink
Your right... that works too.
David McEwing
Ok, you win... you can't remove it completely because the ConvertAll<> requires a delegate to be passed to it. However a default i=>i works. Though now the readability is starting to be lost and thus i prefer my first solution, or just (i => (Class1)i) where you are telling the reader a bit more about what is happening.
David McEwing
I wanted to keep the set I returned an IEnumerable<> without converting it to a list, so I ended up doing this: return items.Select(i => (Class1)i);I know I didn't mention that in the question, but I wanted it that way in my real code. By using this method I don't have to worry about it creating a separate list.
Ryan Versaw
Hi Ryan, Glad you're happy and have a result. I'd suggest using ConvertAll in preference to Select as David has done, as it more clearly communicates to the reader what your code is attempting to achieve. (IOW your method body would read "return items.ConvertAll<Class1>(i => i);")
Ruben Bartelink
Rather than starting with a List<>, it was starting with an IEnumerable<>. I would have to modify your suggestion to read: "return items.ToList().ConvertAll<Class1>(i => i);" It seems unnecessary in my situation, though I can see the benefit in others. The place I'm using it shouldn't cause any confusion either - converting DataSet generated classes to standalone classes which are used throughout the project. I do value other opinions though, so thanks for your input!
Ryan Versaw
Hi Ryan, The fact that ConvertAll was from List<T> had gone over my head, sorry (I generally prefer IEnumerable over List in the same way)! In that case, as you say, it is indeed far better not to force it to a list (especially as you potentially won't benefit from the result being a List<T> either).The reason I'm looking at the post is to suggest using "from item in items select (Class1)item" as a potentially cleaner looking way of putting it (the i=>i just jars a little!)
Ruben Bartelink
A: 

A solution could be to use a bit of linq'ing here if you really need this kind of conversion:

List items = new List{new Class2{Test2 = 9}};
foreach (Class1 item in (from x in items select (Class1)x))
{
    Console.WriteLine(item.Test1);
}
RoastedBattleSquirrel
A: 

You can also use this to do casting with conversions if needed:

public static IEnumerable<TDest> CastAll<TItem, TDest>(this IEnumerable<TItem> items)
{
 var p = Expression.Parameter(typeof(TItem), "i");
 var c = Expression.Convert(p, typeof(TDest));
 var ex = Expression.Lambda<Func<TItem, TDest>>(c, p).Compile();

 foreach (var item in items)
 {
    yield return ex(item);
 }
}

From http://adventuresdotnet.blogspot.com/2010/06/better-more-type-safe-alternative-to.html

Maslow