Implicit conversion fails when changing struct to sealed class

前端 未结 2 1026
半阙折子戏
半阙折子戏 2021-02-05 16:41

Struct/class in question:

public struct HttpMethod
{
    public static readonly HttpMethod Get = new HttpMethod(\"GET\");
    public static readonly HttpMethod P         


        
2条回答
  •  一整个雨季
    2021-02-05 17:07

    Firstly, this is an observed behavioural difference between structs and classes. The fact that you have 'sealed' your class does not affect the outcome in this scenario.

    Also we know the following statement will compile as expected for HttpMethod type declared as both a struct and class, thanks to the implicit operator.

    string method = HttpMethods[0];
    

    Dealing with Arrays introduces some lesser understood compiler nuances.

    Covariance

    When HttpMethod is a class (reference type), with an array such as HttpRoute.HttpMethods Array covariance (12.5 C# 5.0 Language Spec) comes into play that allows HttpMethod[x] to be treated as an object. Covariance will respect inbuilt implicit reference conversions (such as type inheritance or conversion to object) and it will respect explicit operators, but it will not respect or look for user defined implicit operators. (While a bit ambigous the actual spec doc lists specifically default implicit operators and explicit operators, it does not mention the user defined operators but seeing everything else is so highly specified you can infer that user defined operators are not supported.)

    Basically Covariance takes precedence over many generic type evaluations. More on this in a moment.

    Array covariance specifically does not extend to arrays of value-types. For example, no conversion exists that permits an int[] to be treated as an object[].

    So when HttpMethod is a struct (value type), covariance is no longer an issue and the following generic extension from System.Linq namespace will apply:

    public static bool Contains(this IEnumerable source, TSource value);
    

    Because you have passed in a string comparator, the Contains statement will be evaluated as follows:

    public static bool Contains(this IEnumerable source, string value);
    

    When HttpMethod is a class (Reference Type), thanks to covariance, HttpMethod[] in it's current form comparable only with Object[] and thus IEnumerable, but not IEnumerable< T >, Why not? because the compiler needs to be able to determine the type to generate the generic implementation of IEnumerable< T > and to determine if it can perform an explicit cast from object to T. Put another way, Compiler cannot determine if T can definetly be a String or not, so it doesn't find the match in the Linq extension methods that we were expecting.

    So what can you do about it? (! Not this !) The first common attempt might be to try using .Cast< string >() to cast the HttpMethod instances to strings for the comparison:

    return HttpMethods.Cast().Contains(request.Method) && request.Url.AbsolutePath.StartsWith(Prefix);
    

    You will find that this does not work. Even though The parameter for Cast< T > is of type IEnumerable, not IEnumerable< T >. It is provided to allow you to use older collections that do not implement the generic version of IEnumerable with LINQ. Cast< T > is only designed to convert non-generic objects to their "true" type through the process of evaluating common origins for reference types or Un-Boxing for value types. If Boxing and Unboxing (C# Programming Guide) only applies to value types (structs) and since our HttpMethod type is a reference type (class) the only common origin between HttpMethod and String is Object. On HttpMethod there is no implicit, or even explicit operator that accepts Object and as it is not a value type there is no in built un-box operator that the compiler can use.

    Note that this Cast<> will fail at runtime in this scenario when HttpMethod is a value type (class) the compiler will be happy to let it build.

    Final Workaround

    Instead of Cast< T > or relying on implicit conversions we will need to force the elements in the HttpMethods array to be explicitly cast to string (This will still use out implicit operator!) but Linq again makes this a trivial, but necessary task:

    return HttpMethods.Select(c => (string)c).Contains(request.Method) && request.Url.AbsolutePath.StartsWith(Prefix);
    

提交回复
热议问题