20

Please read to the end before deciding of voting as duplicate...

I have a type that implements an implicit cast operator to another type:

class A
{
    private B b;
    public static implicit operator B(A a) { return a.b; }
}
class B
{
}

Now, implicit and explicit casting work just fine:

B b = a;
B b2 = (B)a;

...so how come Linq's .Cast<> doesn't?

A[] aa = new A[]{...};
var bb = aa.Cast<B>();  //throws InvalidCastException

Looking at the source code for .Cast<>, there's not much magic going on: a few special cases if the parameter really is a IEnumerable<B>, and then:

foreach (object obj in source) 
    yield return (T)obj; 
    //            ^^ this looks quite similar to the above B b2 = (B)a;

So why does my explicit cast work, but not the one inside .Cast<>?

Does the compiler sugar-up my explicit cast ?

PS. I saw this question but I don't think its answers really explain what's going on.

Community
  • 1
  • 1
Cristian Diaconescu
  • 34,633
  • 32
  • 143
  • 233
  • Even if _"its answers wouldn't really explain what's going on"_ you should not ask duplicate questions ;) – Tim Schmelter Jan 25 '13 at 14:21
  • 2
    @Tim and how would you suggest I improve the answers of a topic (that I don't know the answer to) except for asking a better question? – Cristian Diaconescu Jan 25 '13 at 14:25
  • @TimSchmelter, if this question gets better answers, maybe the other one should be closed as a dup of this? – Jon Egerton Jan 25 '13 at 15:09
  • I don't know what's the correct way to handle this. I often see questions getting closed where the answers are better than in the proposed duplicate. – Tim Schmelter Jan 25 '13 at 15:16
  • @tim Apparently the consensus on meta is to merge the questions: http://meta.stackexchange.com/q/1375/136203 - I've flagged this question, let's see what happens :) – Cristian Diaconescu Jan 25 '13 at 17:24
  • @CristiDiaconescu: More this: http://meta.stackexchange.com/questions/55251/opinions-on-closing-an-older-question-as-a-duplicate-of-a-newer-question – Tim Schmelter Jan 25 '13 at 19:42
  • `var bb = aa.Select(x => (B)x);` ftw, maybe with a `.ToArray()` – ruffin Jan 04 '19 at 20:43

3 Answers3

16

So why my explicit cast work, and the one inside .Cast<> doesn't?

Your explicit cast knows at compile time what the source and destination types are. The compiler can spot the explicit conversion, and emit code to invoke it.

That isn't the case with generic types. Note that this isn't specific to Cast or LINQ in general - you'd see the same thing if you tried a simple Convert method:

public static TTarget Convert<TSource, TTarget>(TSource value)
{
    return (TTarget) value;
}

That will not invoke any user-defined conversions - or even conversions from (say) int to long. It will only perform reference conversions and boxing/unboxing conversions. It's just part of how generics work.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • So should an explicit operator always be defined to complement an implicit operator? Is there a reason that implicit operators don't "implicitly" include explicit conversion? – Will Vousden Jan 25 '13 at 14:17
  • Is there a way to make it work? It would be pretty useful in certain scenarios - like when working with Oracles stupid types that don't even implement `IConvertible`... – Daniel Hilgarth Jan 25 '13 at 14:17
  • @DanielHilgarth: The closest you could come would be to use `dynamic`, I suspect. – Jon Skeet Jan 25 '13 at 14:18
  • @WillVousden: implicit operators can be called explicitly. Adding an explicit operator to the sample code above wouldn't change the observed behavior. – Daniel Hilgarth Jan 25 '13 at 14:18
  • 1
    @WillVousden: It has nothing to do with whether the conversion is explicit or implicit - it's *only* because your conversion is a user-defined one. That simply won't be used by a generic cast operation. – Jon Skeet Jan 25 '13 at 14:18
  • @JonSkeet: Ha, good idea. Never thought about using `dynamic` in this scenario. – Daniel Hilgarth Jan 25 '13 at 14:19
12

The short answer would be simply: the Cast<T> method doesn't support custom conversion operators.

In the first example:

B b = a;
B b2 = (B)a;

the compiler can see this B(A a) operator during static analysis; the compiler interprets this as a static call to your custom operator method. In the second example:

foreach (object obj in source) 
    yield return (T)obj; 

that has no knowledge of the operator; this is implemented via unbox.any (which is the same as castclass if T is a ref-type).

There is also a third option: if you went via dynamic, the runtime implementation tries to mimic compiler rules, so this will find the operator ... but not as part of the C#-to-IL compile step:

dynamic b = a; // note that `dynamic` here is *almost* the same as `object`
B b2 = b;
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • You're right. I tried this, and it throws: `B b = (B)(object)a;` I assume it's because I'm hiding `a`'s real type in the static context... – Cristian Diaconescu Jan 25 '13 at 14:22
  • @CristiDiaconescu yes; by going via `object`, it becomes a simple `castclass`, and `castclass` *does not support custom operators*. – Marc Gravell Jan 25 '13 at 14:23
  • BTW. I think in your last example you meant `B b2 = (B) b ;` – Cristian Diaconescu Jan 25 '13 at 14:23
  • Adding to this, you can cast through the inheritance tree, so ultimately *everything* can be casted to `object`. Thus `(new DerivedType[] { ... }).Cast()` always works, and `(new ParentType[] { new DerivedType(...), ... })).Cast()` works when all the elements are the derived type. – Elaskanator Dec 18 '21 at 02:24
3

Enumerable.Cast<T> is a .Net framework method and has a behavior that makes sense in all of the .Net languages that call it.

See also, Ander Hejlsberg's reply on this discussion.


Does the compiler sugar-up my explicit cast ?

What you call an "implicit cast operator" is actually an "implicit conversion operator". It's a common mistake.

C# allows you to specify conversions using the casting syntax. When this happens, you are using a different instance (converting), not changing the reference to the same instance (casting).

Amy B
  • 108,202
  • 21
  • 135
  • 185