3

TL;DR - I would expect these all to work the same, but (per comments) they do not:

var c1 = new[] { FileMode.Append }.Cast<int>();
var c2 = new[] { FileMode.Append }.Select(x => (int)x);
var c3 = new[] { FileMode.Append }.Select(x => x).Cast<int>();

foreach (var x in c1 as IEnumerable)
  Console.WriteLine(x); // Append (I would expect 6 here!)

foreach (var x in c2 as IEnumerable)
  Console.WriteLine(x); // 6

foreach (var x in c3 as IEnumerable)
  Console.WriteLine(x); // 6

This is a contrived example; I obviously wouldn't cast the collections to IEnumerable if I didn't have to, and in that case everything would work as expected. But I'm working on a library with several methods that take an object and return a serialized string representation. If it determines via reflection that the object implements IEnumerable, it will enumerate it and, in almost all cases, return the expected result...except for this strange case with Array.Cast<T>.

There's 2 things I could do here:

  1. Tell uses to materialize IEnumerables first, such as with ToList().
  2. Create an overload for each affected method that takes an IEnumerable<T>.

For different reasons, neither of those is ideal. Is it possible for a method that takes an object to somehow infer T when Array.Cast<T>() is passed?

Todd Menier
  • 37,557
  • 17
  • 150
  • 173
  • See [this answer](https://stackoverflow.com/a/41169272/3791245) for a peek at the source of `IEnumerable.Cast`. In this particular use case, `Cast` just returns the original array as it was - of type `FileMode` and not of type `int`. (It's not really casting anything, since this particular enum was implemented as an int.) – Sean Skelly Jul 23 '21 at 19:51
  • If you're dealing with an `IEnumerable` then you'd want to do `foreach(int x in whatever)` to get the desired casting. – juharr Jul 23 '21 at 19:54

1 Answers1

4

Is it possible for a method that takes an object to somehow infer T when Array.Cast() is passed?

No, not in the example you gave.

The reason you get the output you do is that the Enumerable.Cast<T>() method has an optimization to allow the original object to be returned when it's compatible with the type you ask for:

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

So in your first case, nothing actually happens. The Cast<T>() method is just returning the object you passed into the method, and so by the time you get it back, the fact that it ever went through Cast<T>() is completely lost.

Your question doesn't have any other details about how you got into this situation or why it matters in a practical sense. But we can say conclusively that given the code you posted, it would be impossible to achieve the goal you've stated.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thanks, glad to confirm I'm not going crazy at least. Context/background for this question is [here](https://github.com/tmenier/Flurl/issues/632) if you're curious. Unless I introduce `IEnumerable` overloads I'll likely have to close it as a "won't fix". :) – Todd Menier Jul 23 '21 at 20:06
  • I still find it interesting that in my original example, c1 and c3 behave differently. But I verified that while you can cast `FileMode[]` to `IEnumerable`, do `Select(x => x)` on it first and you can't. Counterintuitive, but it does explain it. – Todd Menier Jul 23 '21 at 20:32
  • 1
    _"do Select(x => x) on it first and you can't"_ -- that's because, while you can look at the `x => x` and understand that it's an identity projection, it would be too costly at runtime for the `Select()` method to do the same, so it has no practical way to optimize the identity projection the way that `Cast()` optimizes the cast. `Select()` has no choice but to _always_ return a new iterator that invokes your projection delegate, so the return object is _always_ not the original one. – Peter Duniho Jul 23 '21 at 20:47