开发者

Why doesn't Enumerable.Cast<> use my conversion operator?

开发者 https://www.devze.com 2023-02-06 19:32 出处:网络
Using this type: class Foo { public static implicit operator int(Foo obj) { return 5; } } var test=new[] { new Foo() };

Using this type:

class Foo
{
  public static implicit operator int(Foo obj)
  {
    return 5;
  }
}

var test=new[] { new Foo() };

The following works as ex开发者_如何学Gopected

var ok=test.Select(x => (int)x).ToList();

but using Cast<> fails with an InvalidCastException - why?

var fail=test.Cast<int>().ToList();


Read Jon Skeet's blog about reimplementing Linq (EduLinq), specifically part 33, where he says this:

It's worth noting that (as of .NET 3.5 SP1) Cast and OfType only perform reference and unboxing conversions. They won't convert a boxed int to a long, or execute user-defined conversions. Basically they follow the same rules as converting from object to a generic type parameter. (That's very convenient for the implementation!)


Casting operators are purely C# compiler level features, the run-time doesn't know anything about them so there is no simple way to implement this via generic Cast method. One way to do this is to perform run-time code generation:


    public static class Converter<TSource, TResult>
    {
        static Converter()
        {
            var sourceParameter = Expression.Parameter(typeof(TSource));
            var conversionExpression = Expression.Lambda<Func<TSource, TResult>>(
                Expression.Convert(sourceParameter, typeof(TResult)),
                sourceParameter);

            Instance = conversionExpression.Compile();
        }

        public static Func<TSource, TResult> Instance
        {
            get;
            private set;
        }
    }

    public static class EnumerableEx
    {
        public static IEnumerable<TResult> Cast<TSource, TResult>(this IEnumerable<TSource> source)
        {
            return source.Select(Converter<TSource, TResult>.Instance);
        }
    }

but then you'll loose compile-time checking:


var test = new[] { new Foo() };
var ok = test.Cast<Foo, int>().ToList(); // compiles and works ok
var error = test.Cast<Foo, double>().ToList(); // compiles but fails at run-time

Another way is to use reflection as in Puzzling Enumerable.Cast InvalidCastException but this will not work with built-in conversions like from int to long.


The documentation for Enumerable.Cast is actually a bit vague on that and talks about cast and conversion. It does however say that "If an element cannot be cast to type TResult, this method will throw an exception" and your class Foo can't be cast to int but can be converted using cast syntax. The latter is a method call.

Usually Cast and OfType work similar to 'as' an 'is' and if you wrote:

var foo = new Foo()
var bar = foo is int;

bar would be false. It would seem that Cast is consistent with that (though the documentation found on MSDN is not entirely). and fails when the is-operator would return false. (There are one special case where this will not be the case and that's if the value in the sequence is null and T is a reference type)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号