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.
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.)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With