1

I am trying to understand what Enumerable.Cast does to perform the cast, and why I can't get it to work even when an implicit (or explicit) conversion operator has been defined for the target types.

The following code puts together a few tests I've prepared to understand what was going wrong. What I'm really after is understanding why test5 throws and InvalidCastException; however at this stage I think that the problem is connected in the failure of test3 and test4.

void Main()
{
   List<Currency> values = new List<Currency>() { new Euro(), new Dollar() };

   Dollar d = new Dollar();
   Euro e = new Euro();

   var test1a = (Dollar)e; // valid
   var test1b = (Euro)d; // valid

   Console.WriteLine(values[0].GetType()); // Euro

   var test2 = values[0] as Dollar; // valid

   var test3 = (Dollar)values[0]; // Why does this fail? - InvalidCastException: Unable to cast object of type 'Euro' to type 'Dollar'

   var test4 = values.Cast<Euro>(); // no error
   Console.WriteLine(test4.ToString()); // what's this CastIterator?

   var test5 = test4.ToList(); // Why does this fail? - InvalidCastException: Unable to cast object of type 'Dollar' to type 'Euro'

}

class Currency { public double Amount {get; set;} = 0; }
class Euro : Currency 
{ 
 public Euro(double amount = 0) { this.Amount = amount; }
 public static implicit operator Euro(Dollar d)
 {
    Euro e = new Euro(); e.Amount = d.Amount*1.2; return e;
 }
}

class Dollar : Currency 
{
 public Dollar(double amount = 0) { this.Amount = amount; }
 public static implicit operator Dollar(Euro e)
 {
    Dollar d = new Dollar(); d.Amount = e.Amount/1.2; return d;
 }
}

As you can see, I've specified implicit conversion operators for the classes, however that's not enough for test3 and 4 to work. Why is that?

alelom
  • 2,130
  • 3
  • 26
  • 38
  • First, there is no test4 that is separate and distinct from test5. Linq uses deferred execution; in other words Linq functions (and the expressions passed to them) are only evaluated/executed if the enumerable returned by the Linq methods are enumerated. When is `values.Cast()` being enumerated and thus Cast() being executed? When the enumerable in the `test4` variable is enumerated, which happens as a neccessary part of the internal logic implementation of the ToList() method. –  May 14 '19 at 17:49
  • With respect to the error regarding conversion, check the documentation you linked to. Pay attention to the Remarks section. It tells you exactly how Cast() works, and its limitations. It even mentions what should be used instead in situations like yours. You just need to read... ;-) –  May 14 '19 at 17:51
  • With respect to test3, note that implicit conversions and explicit conversions are different things. Related and similar, but not the same. You only have provided an operator override for implicit conversions, however, test3 is attempting an [explicit conversion](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/casting-and-type-conversions#explicit-conversions), with your classes lacking in implementation for explict conversions... –  May 14 '19 at 17:55
  • The `(Dollar)values[0]` is a Possible duplicate of [Cast object containing int to float results in InvalidCastException](https://stackoverflow.com/q/24447387/11683). Oh, actually the `Cast` one is too. – GSerg May 14 '19 at 17:57
  • And by the way, regarding test2, `values[0] as Dollar` might not throw an exception, but that doesn't mean it converts. Like test3, it attempts an explicit conversion (well, the [documentation for `as`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as) would have told you so, too), but will not succeed. Like when crossing a street, keep eyes open and look in all directions -- that includes checking the results you get from your attempted conversions, instead of just blindly assuming it... ;-) (Assumptions and beliefs are the arch nemesis of any aspiring programmer) –  May 14 '19 at 18:06

1 Answers1

0

If you want to know what a LINQ function does, don't hesitate to visit the reference source in Enumerable.CS

Here you'll find:

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;
    }
}

So what it does, for every object in your source sequence it will execute:

(TResult) obj;

To find out why you get problems, simply create a list of your source, and start enumerating yourself:

List<Currency> currencies = ...
foreach (Currency currency in currencies)
{
    Dollar d = (Dollar)currency;
    Euro e = (Euro) currency;
    Console.WriteLine("Dollar {0}; Euro {1}", d, e);
}

Your debugger will tell you what is wrong.

var test2 = values[0] as Dollar; // valid
var test3 = (Dollar)values[0];   // InvalidCastException

Can it be that test3 means: ( (Dollar)values ) [0];. If you put parentheses, would that solve the problem?

 var test3 = (Dollar) (values[0]);
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116