为什么不能为这个通用的 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 是当然要走的路。但我仍然对问题的答案感兴趣。

这是因为您使用的是 0255,它们是 int 值,而不是 uint 值。 C# 中的纯整数始终被视为 int 值(如果它们在 int 范围内)。

您正在使用 uint.Clamp(int, int) => uint 形式调用 Clamp。这由编译器转换为 Clamp(unit, int, int) => uint。编译器虽然有效地期待 Clamp(T, T, T) => T,因此它报告错误,因为 uintint 类型的混合阻止它解析什么类型 T 应该采用。

换行:

_r = value.Clamp(0, 255);

至:

_r = value.Clamp(0U, 255U);

代码将编译。 U 后缀告诉编译器该数字是一个 uint 值。

您正在使用参数 uint, int, int 调用 Clamp<T>(T, T, T)(因为 0255int 文字)。

由于没有implicit conversion从一种类型到另一种类型,编译器无法计算出是Tint还是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 绑定到 intuint。这是一个矛盾,因此该方法甚至从未添加到要考虑的重载决议的方法集中。 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# 尝试按照用户的意思去做,但也尽量避免猜测;在这种情况下,而不是潜在的猜测错误,它需要你清楚你打算推断的类型。