为什么不能为这个通用的 Clamp 方法推断出类型?
Why can the type not be inferred for this generic Clamp method?
我正在写一个代表 LED 的 class。基本上 3 uint
r、g 和 b 的值在 0 到 255 范围内。
我是 C# 的新手,从 uint1 开始,它比我想要的 8 位大。在编写自己的 Clamp 方法之前,我在网上查找了一个方法,发现 this great looking answer 建议使用一种扩展方法。问题是它无法推断类型为 uint
。为什么是这样?这段代码上写满了 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
}
}
}
//
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
是当然要走的路。但我仍然对问题的答案感兴趣。
这是因为您使用的是 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
文字)。
由于没有implicit conversion从一种类型到另一种类型,编译器无法计算出是T
int
还是uint
.
当整数文字没有后缀时,其类型是以下类型中第一个可以表示其值的类型:int、uint、long、ulong。您使用的是 0 和 255,它们很好地进入 int
以便选择一个。
您可以告诉编译器使用 uint
只需在文字后缀
_r = value.Clamp(0U, 255U);
可以从 documentation 中找到更多信息。
其他答案是正确的,但这里有一个微妙的地方,我认为应该特别指出。
通常在 C# 中,整数文字的类型是 int
,但它可以隐式转换为常量在范围内的任何数值类型。因此,即使 int
不能隐式转换为 uint
,赋值 myuint = 123;
也是合法的,因为 int
适合。
从这个事实很容易陷入错误的信念,即 int
文字可以在任何需要 uint
的地方使用,但您已经发现为什么这种信念是错误的。
类型推断算法是这样的。 (当然,这是一个巨大的简化;lambda 使它变得更加复杂。)
- 计算参数的类型
- 分析参数与对应形参的关系
- 根据该分析,推断泛型类型参数的类型界限
- 检查完整性的界限——每个泛型类型参数都必须有界限——和一致性——界限不能矛盾。如果推理不完整或不一致,则该方法不适用。
- 如果推导的类型违反了它们的约束,则该方法不适用。
- 否则,具有推导类型的方法将添加到用于重载解析的方法集中。
过载解析然后继续比较候选集中的方法以找到最佳方法。
(请注意,return 类型当然没有被考虑;C# 检查 return 类型是否可以分配给 after重载决议选择了方法,而不是在重载决议期间。)
在您的案例中,类型推断在 "verify that there are a consistent set of bounds" 步骤失败。 T
绑定到 int
和 uint
。这是一个矛盾,因此该方法甚至从未添加到要考虑的重载决议的方法集中。 int
参数 是 可转换为 uint
的事实从未被考虑过;类型推理引擎仅适用于类型。
类型推断算法也不会"backtrack"以任何方式出现在您的场景中;它没有说 "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." (它 确实 做一些类似于涉及 lambda 的事情,这可能导致它在某些情况下尝试任意多种可能的类型组合。)如果推理算法就是这样工作的,然后你会得到你想要的结果,但事实并非如此。
基本上,这里的理念是类型推断算法不寻求找到任何使程序运行的方法,而是寻找一个链对类型进行推理,从参数 派生的信息中得出唯一的逻辑结论。 C# 尝试按照用户的意思去做,但也尽量避免猜测;在这种情况下,而不是潜在的猜测错误,它需要你清楚你打算推断的类型。
我正在写一个代表 LED 的 class。基本上 3 uint
r、g 和 b 的值在 0 到 255 范围内。
我是 C# 的新手,从 uint1 开始,它比我想要的 8 位大。在编写自己的 Clamp 方法之前,我在网上查找了一个方法,发现 this great looking answer 建议使用一种扩展方法。问题是它无法推断类型为 uint
。为什么是这样?这段代码上写满了 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
}
}
}
//
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
是当然要走的路。但我仍然对问题的答案感兴趣。
这是因为您使用的是 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
文字)。
由于没有implicit conversion从一种类型到另一种类型,编译器无法计算出是T
int
还是uint
.
当整数文字没有后缀时,其类型是以下类型中第一个可以表示其值的类型:int、uint、long、ulong。您使用的是 0 和 255,它们很好地进入 int
以便选择一个。
您可以告诉编译器使用 uint
只需在文字后缀
_r = value.Clamp(0U, 255U);
可以从 documentation 中找到更多信息。
其他答案是正确的,但这里有一个微妙的地方,我认为应该特别指出。
通常在 C# 中,整数文字的类型是 int
,但它可以隐式转换为常量在范围内的任何数值类型。因此,即使 int
不能隐式转换为 uint
,赋值 myuint = 123;
也是合法的,因为 int
适合。
从这个事实很容易陷入错误的信念,即 int
文字可以在任何需要 uint
的地方使用,但您已经发现为什么这种信念是错误的。
类型推断算法是这样的。 (当然,这是一个巨大的简化;lambda 使它变得更加复杂。)
- 计算参数的类型
- 分析参数与对应形参的关系
- 根据该分析,推断泛型类型参数的类型界限
- 检查完整性的界限——每个泛型类型参数都必须有界限——和一致性——界限不能矛盾。如果推理不完整或不一致,则该方法不适用。
- 如果推导的类型违反了它们的约束,则该方法不适用。
- 否则,具有推导类型的方法将添加到用于重载解析的方法集中。
过载解析然后继续比较候选集中的方法以找到最佳方法。
(请注意,return 类型当然没有被考虑;C# 检查 return 类型是否可以分配给 after重载决议选择了方法,而不是在重载决议期间。)
在您的案例中,类型推断在 "verify that there are a consistent set of bounds" 步骤失败。 T
绑定到 int
和 uint
。这是一个矛盾,因此该方法甚至从未添加到要考虑的重载决议的方法集中。 int
参数 是 可转换为 uint
的事实从未被考虑过;类型推理引擎仅适用于类型。
类型推断算法也不会"backtrack"以任何方式出现在您的场景中;它没有说 "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." (它 确实 做一些类似于涉及 lambda 的事情,这可能导致它在某些情况下尝试任意多种可能的类型组合。)如果推理算法就是这样工作的,然后你会得到你想要的结果,但事实并非如此。
基本上,这里的理念是类型推断算法不寻求找到任何使程序运行的方法,而是寻找一个链对类型进行推理,从参数 派生的信息中得出唯一的逻辑结论。 C# 尝试按照用户的意思去做,但也尽量避免猜测;在这种情况下,而不是潜在的猜测错误,它需要你清楚你打算推断的类型。