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

本文关键字:方法 Clamp 类型 不能 为什么 | 更新日期: 2023-09-27 18:14:09

我正在写一个代表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当然是要走的路。但我还是对这个问题的答案很感兴趣。

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

其他答案都是正确的,但我认为这里有一个微妙的问题应该特别指出。

通常在c#中,整数字面值的类型是int,但是它可以隐式地转换为常量范围内的任何数字类型。因此,尽管int不能隐式地转换为uint,但myuint = 123;的赋值是合法的,因为int适合。

从这个事实来看,很容易陷入int字面量可以在任何需要uint的地方使用的错误信念,但是您已经发现了为什么这种信念是错误的。

类型推断算法是这样的。(当然,这是一个极大的简化;lambda使这变得相当复杂。)

  • 计算参数类型
  • 分析参数与相应形式参数之间的关系
  • 从这个分析中,推导出泛型类型参数的类型界限检查边界的完整性——每个泛型类型参数必须有一个边界——和一致性——边界不能是矛盾的。如果推理不完整或不一致,则该方法不适用。
  • 如果推导的类型违反了它们的约束,该方法不适用。
  • 否则,具有推导出的类型的方法被添加到用于重载解析的方法集中。

然后,重载解析继续比较候选集合中的其他方法,以找到最佳方法。

(注意,当然没有考虑返回类型;c#检查返回类型是否可以被赋值给重载解析选择该方法之后(而不是在重载解析期间)赋值给的对象。

在您的例子中,类型推断在"验证是否存在一致的边界集"步骤中失败。T同时绑定intuint。这是一个矛盾,因此该方法甚至从未添加到重载解析要考虑的方法集中。int参数可以转换为uint这一事实从未被考虑过;类型推理引擎仅对类型起作用。

在您的场景中,类型推断算法也不会以任何方式"回溯";它不会说"好吧,我不能推断出T的一致类型,但也许其中一个单独的类型有效。"如果我尝试了intuint的边界呢?我们可以看看他们中的任何一个是否真的产生了一种有效的方法。"(当涉及到lambdas时,它所做的事情与类似,这可能导致它在某些场景中尝试任意多种可能的类型组合。)如果推理算法以这种方式工作,那么你就会得到你想要的结果,但事实并非如此。

这里的基本原理是,类型推断算法不是寻找任何使程序工作的方法,而是寻找一个关于类型的推理链,从从参数派生的信息中派生出唯一的逻辑结论。c#试图做用户想要做的事情,但也试图避免猜测;在这种情况下,它要求您明确要推断的类型,而不是可能猜测错误。

这是因为您使用的是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字面量)。

由于没有从一种类型到另一种类型的隐式转换,编译器无法判断是将T变为int还是uint

当整数字面值没有后缀时,它的类型是下列类型中第一个可以表示其值的类型:int、uint、long、ulong。您正在使用0和255,它们很好地放到int中,因此选择了一个。

你可以告诉编译器使用uint,只需给文字

加上后缀
_r = value.Clamp(0U, 255U);

更多信息可从文档中找到