Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler not calling appropriate generic overload when passed with value type

I have public functions like this:

public static T Get<T>(this Mango m, T defaultValue = default(T)) where T : class
{
    //do something; return something;
}

public static T? Get<T>(this Mango m, T? defaultValue = default(T?)) where T : struct
{
    //do something; return something;
}

Basically I want to individually handle reference types and nullable types. It compiles; until i call for value types. For reference types it compiles.

mango.Get<string>(); // compiles..
mango.Get(""); // compiles..

mango.Get<int>(); // The type 'int' must be a reference type in order to use it as 
                  // parameter 'T' in the generic type or method Get<T>(Mango, T)
//also            // The call is ambiguous between the following methods or properties: 
                  // Get<int>(Mango, int) and Get<int>(Mango, int?)

What real ambiguity is here? When T is int, cant it call the struct overload appropriately? Also:

mango.Get<int>(0);  // The type 'int' must be a reference type in order to use it as 
                    // parameter 'T' in the generic type or method Get<T>(Mango, T)

Why is the compiler only detecting the reference type overload? I tried having two separate overloads:

public static T Get<T>(this Mango m) where T : class
{
    return default(T);
}

public static T? Get<T>(this Mango m) where T : struct
{
    return default(T);
}

public static T Get<T>(this Mango m, T def) where T : class
{
    return default(T);
}

public static T? Get<T>(this Mango m, T? def) where T : struct
{
    return default(T);
}

The problem persisted. And obviously, the first two methods dont compile here since overloading doesn't work merely on the basis of constraints.

I tried by removing the class constrained overload and keeping just the struct constrained one, like this:

public static T? Get<T>(this Mango m, T? defaultValue = default(T?)) where T : struct
{
    //do something; return something;
}

mango.Get<int>(); // voila compiles!
mango.Get<int>(0); // no problem at all..
// but now I can't have mango.Get<string>() for instance :(

Am I only left with renaming the two functions? I feel its appropriate to have a unified name so that the caller just doesnt have to worry about the implementation detail, but just call Get for any type.

Update: Marc's solution doesnt work if I have to avoid the optional parameter.

mango.Get<int>(); // still wouldnt work!!

But there's more magic :(:(

public static bool IsIt<T>(this T? obj) where T : struct
{
    return who knows;
}

public static bool IsIt<T>(this T obj) where T : class
{
    return perhaps;
}

By all means I'm expecting the same compiler bug (according to me) to annoy me. But no it works this time.

Guid? g = null;
g.IsIt(); //just fine, and calls the struct constrained overload
"abcd".IsIt(); //just fine, and calls the class constrained overload

So if overload resolution comes before constraint checking as Marc says, shouldn't I get the same error this time too? But no. Why is it so?? What the hell is going on? :x

like image 394
nawfal Avatar asked Feb 07 '13 11:02

nawfal


2 Answers

constraint checking is done after overload resolution, IIRC; the overload resolution seems to prefer the first version. You can force it to use the other, though:

mango.Get<int>((int?)0);

or even:

mango.Get((int?)0);

Personally, I'd probably just change the name to avoid the ambiguity.

like image 87
Marc Gravell Avatar answered Oct 31 '22 07:10

Marc Gravell


Interestingly, the compiler will check the constraints specified within generic types that are used within a method signature, but not for constraints within the signature itself.

Thus, if a method accepted two parameters, one of type T where T : struct along with a Nullable<T>[], the compiler would not consider the method for any T that was not a struct. The method's specified struct constraint for T is not considered in evaluating overloads, but the fact that Nullable<T> constrains T to struct, is.

I really find the total inability to consider constraints in overload evaluation bizarre, given that one could specify a default null value for the Nullable<T>[] parameter, and pretend the parameter didn't exist. The vb.net compilers and C# compilers seem to differ, though, when it comes to what they regard as ambiguous and what they accept.

like image 33
supercat Avatar answered Oct 31 '22 07:10

supercat