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

nul*_*ull 15 c# generics extension-methods

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

我是C#的新手,从uint 1开始,这比我想要的大8位.在编写我自己的Clamp方法之前,我在网上找了一个,发现这个很棒的答案提示了一个扩展方法.问题是它无法推断出类型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;
    }
}
Run Code Online (Sandbox Code Playgroud)

1一个错误,使用byte是当然的方式.但我仍然对这个问题的答案感兴趣.

Eri*_*ert 22

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

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

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

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

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

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

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

在您的情况下,类型推断在"验证存在一组一致的边界"步骤中失败.T为界,都intuint.这是一个矛盾,因此该方法永远不会被添加到用于考虑重载决策的方法集中.从不考虑int论证转换的事实uint; 类型推理引擎仅适用于类型.

类型推断算法也不会在您的场景中以任何方式"回溯"; 它并没有说"OK,我无法推断一致的类型T,但也许是个别类型的作品之一.如果我都尝试界intuint?我们可以看到,如果其中任何实际产生一个可行的方法." (它确实做了类似于涉及lambdas时的事情,这可能导致它在某些情况下尝试任意多种类型的类型组合.)如果推理算法以这种方式工作,那么你将得到你想要的结果,但它不会.

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


Dav*_*rno 21

这是因为使用的是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);
Run Code Online (Sandbox Code Playgroud)

至:

_r = value.Clamp(0U, 255U);
Run Code Online (Sandbox Code Playgroud)

并且代码将编译.该U后缀告诉编译器的数量是一个uint值.

  • 为了澄清:C#语言将签名定义为包括参数类型但不包括返回类型.CLI规范定义签名以包括返回类型.您可能不会在返回类型上重载是C#的规则,而不是底层运行时的规则.由于这种小的差异,当不清楚是否要考虑返回类型时,术语"签名"可能会令人困惑. (5认同)
  • 但是,这并不能解释为什么对uint满意时不自动使用int的原因。这不会编译:`_r = value.Clamp((int)-1,(int)-22);`但是为什么不呢?`int`强制转换告诉编译器该数字是`int`,那么为什么不编译呢? (2认同)
  • @MatthewWatson - 因为`value`是'uint`? (2认同)

Raw*_*ing 15

要调用Clamp<T>(T, T, T)带有参数uint, int, int(如0255int文字).

由于没有从一种类型到另一种类型的隐式转换,编译器无法确定是否要生成T intuint.