Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can the type not be inferred for this generic Clamp method?

I'm writing a class that represents an LED. Basically 3 uint values for r, g and b in the range of 0 to 255.

I'm new to C# and started with uint1, which is bigger than 8 bit that I want. Before writing my own Clamp method I looked for one online and found this great looking answer suggesting an extension method. The problem is that it could not infer the type to be uint. Why is this? This code has uint written all over it. I have to explicitly give the type to make it work.

class Led
{
    private uint _r = 0, _g = 0, _b = 0;

    public uint R
    {
        get
        {
            return _r;
        }
        set
        {
            _r = value.Clamp(0, 255); // nope

            _r = value.Clamp<uint>(0, 255); // works
        }
    }
}

// https://stackoverflow.com/a/2683487
static class Clamp
{
    public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
    {
        if (val.CompareTo(min) < 0) return min;
        else if (val.CompareTo(max) > 0) return max;
        else return val;
    }
}

1 a mistake, using byte is the way to go of course. But I'm still interested in the answer to the question.

like image 408
null Avatar asked Sep 22 '15 12:09

null


1 Answers

The other answers are correct but there is a subtle point here that I thought should be called out specifically.

Normally in C#, the type of an integer literal is int, but it may be converted implicitly to any numeric type in which the constant is in range. So, even though int is not implicitly convertible to uint, the assignment myuint = 123; is legal because the int fits.

From this fact it is easy to fall into the incorrect belief that int literals can be used anywhere that a uint is expected, but you have discovered why that belief is false.

The type inference algorithm goes like this. (This is a massive simplification of course; lambdas make this considerably more complicated.)

  • Compute the types of the arguments
  • Analyze the relationships between arguments and corresponding formal parameters
  • From that analysis, deduce type bounds on generic type parameters
  • Check the bounds for both completeness -- every generic type parameter must have a bound -- and consistency -- bounds must not be contradictory. If inference is incomplete or inconsistent then the method is inapplicable.
  • If the deduced types violate their constraints, the method is inapplicable.
  • Otherwise, the method with the deduced types is added to the set of methods used for overload resolution.

Overload resolution then proceeds to compare methods in the candidate set against each other to find the best.

(Note that the return type is of course nowhere considered; C# checks to see whether the return type may be assigned to whatever it is assigned to after overload resolution has chosen the method, not during overload resolution.)

In your case type inference is failing at the "verify that there are a consistent set of bounds" step. T is bounded to both int and uint. This is a contradiction, so the method is never even added to the set of methods for overload resolution to consider. The fact that the int arguments are convertible to uint is never considered; the type inference engine works solely on types.

The type inference algorithm also does not "backtrack" in any way in your scenario; it does not say "OK, I can't infer a consistent type for T, but perhaps one of the individual types works. What if I tried both bounds int and uint? We can see if either of them actually produce a method that works." (It does do something similar to that when lambdas are involved, which can cause it to try arbitrarily many possible combinations of types in some scenarios.) If the inference algorithm worked that way then you would get the result you desire, but it does not.

Basically the philosophy here is that the type inference algorithm is not seeking to find any way to make the program work, but rather to find a chain of reasoning about types that derives a unique logical conclusion from information derived from the arguments. C# tries to do what the user means it to do, but also tries to avoid guessing; in this case rather than potentially guessing wrong, it requires you to be clear about the types you intend it to infer.

like image 109
Eric Lippert Avatar answered Nov 02 '22 11:11

Eric Lippert