我正在写一个代表LED的类。在0到255的范围内,r, g和b基本上有3个uint
值。
我是c#的新手,从int1开始,这比我想要的8位大。在写我自己的Clamp方法之前,我在网上找了一个,发现了这个很棒的答案,建议扩展方法。问题是它不能推断出类型是uint
。为什么会这样?这段代码写得很完整。我必须明确地给出类型才能使它工作。
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一个错误,使用byte
当然是要走的路。但我还是对这个问题的答案很感兴趣。
其他答案都是正确的,但我认为这里有一个微妙的问题应该特别指出。
通常在c#中,整数字面值的类型是int
,但是它可以隐式地转换为常量范围内的任何数字类型。因此,尽管int
不能隐式地转换为uint
,但myuint = 123;
的赋值是合法的,因为int
适合。
从这个事实来看,很容易陷入int
字面量可以在任何需要uint
的地方使用的错误信念,但是您已经发现了为什么这种信念是错误的。
类型推断算法是这样的。(当然,这是一个极大的简化;lambda使这变得相当复杂。)
- 计算参数类型
- 分析参数与相应形式参数之间的关系 从这个分析中,推导出泛型类型参数的类型界限检查边界的完整性——每个泛型类型参数必须有一个边界——和一致性——边界不能是矛盾的。如果推理不完整或不一致,则该方法不适用。
- 如果推导的类型违反了它们的约束,该方法不适用。
- 否则,具有推导出的类型的方法被添加到用于重载解析的方法集中。
然后,重载解析继续比较候选集合中的其他方法,以找到最佳方法。
(注意,当然没有考虑返回类型;c#检查返回类型是否可以被赋值给在重载解析选择该方法之后(而不是在重载解析期间)赋值给的对象。
在您的例子中,类型推断在"验证是否存在一致的边界集"步骤中失败。T
同时绑定int
和uint
。这是一个矛盾,因此该方法甚至从未添加到重载解析要考虑的方法集中。int
参数可以转换为uint
这一事实从未被考虑过;类型推理引擎仅对类型起作用。
在您的场景中,类型推断算法也不会以任何方式"回溯";它不会说"好吧,我不能推断出T
的一致类型,但也许其中一个单独的类型有效。"如果我尝试了int
和uint
的边界呢?我们可以看看他们中的任何一个是否真的产生了一种有效的方法。"(当涉及到lambdas时,它所做的事情与类似,这可能导致它在某些场景中尝试任意多种可能的类型组合。)如果推理算法以这种方式工作,那么你就会得到你想要的结果,但事实并非如此。
这是因为您使用的是0
和255
,它们是int
值,而不是uint
值。c#中的纯整数总是被视为int
值(如果它们适合int
范围)。
你用uint.Clamp(int, int) => uint
的形式调用Clamp
。这被编译器转换为Clamp(unit, int, int) => uint
。编译器虽然有效地期望Clamp(T, T, T) => T
,所以它报告一个错误,因为uint
和int
类型的混合阻止它解决T
应该采用哪种类型。
换行:
_r = value.Clamp(0, 255);
:
_r = value.Clamp(0U, 255U);
,代码将被编译。U
后缀告诉编译器这个数字是一个uint
值。
您正在使用参数uint, int, int
调用Clamp<T>(T, T, T)
(因为0
和255
是int
字面量)。
由于没有从一种类型到另一种类型的隐式转换,编译器无法判断是将T
变为int
还是uint
。
当整数字面值没有后缀时,它的类型是下列类型中第一个可以表示其值的类型:int、uint、long、ulong。您正在使用0和255,它们很好地放到int
中,因此选择了一个。
你可以告诉编译器使用uint
,只需给文字
_r = value.Clamp(0U, 255U);
更多信息可从文档中找到