为什么不能推断出这种通用Clamp方法的类型?

时间:2015-09-22 12:16:30

标签: c# generics extension-methods

我正在写一个代表LED的课程。基本上r,g和b的3 uint值在0到255范围内。

我是C#的新手,并以uint 1 开头,这比我想要的大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
        }
    }
}

// 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是理所当然的。但我仍然对这个问题的答案感兴趣。

4 个答案:

答案 0 :(得分:22)

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

您正在使用Clamp形式调用uint.Clamp(int, int) => uint。这由编译器转换为Clamp(unit, int, int) => uint。虽然编译器实际上期望Clamp(T, T, T) => T,但它会报告错误,因为uintint类型的混合会阻止它解析T应该采用的类型。

更改行:

_r = value.Clamp(0, 255);

为:

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

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

答案 1 :(得分:21)

其他答案是正确的,但这里有一个微妙的观点,我认为应该特别提出。

通常在C#中,整数文字的类型为int,但它可以隐式转换为常量在范围内的任何数字类型。因此,即使int无法隐式转换为uint,分配myuint = 123;也是合法的,因为int适合。

从这个事实可以很容易地认为int文字可以用在预期uint的任何地方,但你已经发现了为什么这种信念是错误的。

类型推断算法是这样的。 (这当然是一个大规模的简化; lambdas使这变得更加复杂。)

  • 计算参数的类型
  • 分析参数与相应形式参数之间的关系
  • 从该分析中推断出泛型类型参数的类型界限
  • 检查两者的完整性边界 - 每个泛型类型参数必须有一个绑定 - 并且一致性 - 边界不能相互矛盾。如果推断不完整或不一致,则该方法不适用。
  • 如果推断的类型违反了约束条件,则该方法不适用。
  • 否则,将带有推导类型的方法添加到用于重载解析的方法集中。

然后重载分辨率继续比较候选集中的方法以找到最佳方法。

(注意,当然没有考虑返回类型; C#检查在重载决策选择方法之后是否可以将返回类型分配给分配给的任何类型,而不是在过载期间分辨率。)

在您的情况下,类型推断在“验证存在一组一致的边界”步骤中失败。 Tintuint都有约束力。这是一个矛盾,因此该方法永远不会被添加到用于考虑重载决策的方法集中。从未考虑int参数 可转换为uint的事实;类型推理引擎仅适用于类型。

类型推断算法也不会在您的场景中以任何方式“回溯”;它没有说“好的,我无法推断T的一致类型,但也许其中一种类型有效。如果我尝试了两个边界intuint怎么办?可以看出他们中的任何一个是否真的产生了一种有效的方法。“ (做类似于涉及lambdas时的事情,这可能导致它在某些情况下尝试任意多种类型的类型组合。)如果推理算法以这种方式工作,那么你会得到结果你渴望,但事实并非如此。

基本上,这里的哲学是类型推理算法并不寻求找到使程序工作的任何方式,而是找到关于派生唯一的类型的推理链来自参数的信息的逻辑结论。 C#尝试做用户意味着做的事情,但也试图避免猜测;在这种情况下,而不是可能猜错,它需要你清楚你想要推断的类型。

答案 2 :(得分:16)

您使用参数Clamp<T>(T, T, T)致电uint, int, int0255int文字)。

由于从一种类型到另一种类型没有implicit conversion,编译器无法确定是否要T intuint

答案 3 :(得分:4)

当整数文字没有后缀时,其类型是这些类型中的第一个,其值可以表示为:int,uint,long,ulong。你正在使用0和255,它很好地进入int,因此选择了一个。

您可以通过添加文字

来告诉编译器使用uint
_r = value.Clamp(0U, 255U);

可以从documentation找到更多信息。