Struct/class in question:
public struct HttpMethod
{
public static readonly HttpMethod Get = new HttpMethod(\"GET\");
public static readonly HttpMethod P
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.
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<TSource>(this IEnumerable<TSource> source, TSource value);
Because you have passed in a string comparator, the Contains statement will be evaluated as follows:
public static bool Contains<string>(this IEnumerable<string> 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<string>().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.
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);
Things I know:
HttpMethod1[]
to IEnumerable<string>
because covariance only works on reference types.HttpMethod2[]
to IEnumerable<string>
because covariance only works on reference conversions, and this is a user-defined conversion.Things I suspect but need to confirm:
UPDATE:
IEnumerable<char>
that occasionally messes up type inference.)Here's a program fragment that displays the problem; update your conversions to convert to C instead of string:
public interface IFoo<out T> {}
public class C {}
public class Program
{
public static bool Contains<T>(IFoo<T> items, T item)
{
System.Console.WriteLine(typeof(T));
return true;
}
public static void Main()
{
IFoo<HttpMethod1> m1 = null;
IFoo<HttpMethod2> m2 = null;
var res1 = Contains(m1, new C()); //works
var res2 = Contains(m2, new C()); //doesn't work
}
}
This looks like a possible bug in type inference, and if it is, it is my fault; many apologies if that is the case. Sadly I do not have time to look into it further today. You might want to open an issue on github and have someone who still does this for a living look into it. I would be fascinated to learn what the result was, and if it turns out to be a bug in either the design or the implementation of the inference algorithm.