【问题标题】:Unexpected error on templated function argument: "cannot convert from 'ref T' to 'ref byte'"模板函数参数出现意外错误:“无法从 'ref' 转换为 'ref byte'”
【发布时间】:2016-02-07 06:18:46
【问题描述】:

我有一个测试类:

class Test
{
    void x(ref byte v) { }
    void x(ref sbyte v) { }
    void x<T>(ref T[] a) where T: byte, sbyte
    {
        for (int i = 0; i < a.Length; ++i)
            x(ref a[i]);
    }
}

在错误列表窗口中,我收到了错误

"参数 1: 无法从 ref T 转换为 x(ref a[I]); 行上的 'ref byte'"

Intellisense 还在该行中显示不同的消息:“'Test.x(ref byte)' 的最佳重载方法匹配有一些无效参数”。任何想法为什么?

我尝试这样做,而不是为每个数组类型编写显式重载,因为我的实际代码有大约 20 个不同的“标量”类型的“x”例程,我不想要更多的 20 个函数。

很好地调用多个约束是“anded”,而不是“ored”,但是没有约束我得到了同样的错误。

我明白为什么不需要第三个重载中的'ref',但是这个接口最终可能会重新分配数组,然后需要一个 ref。

顺便说一句:我认为对“结构”的约束将与任何 ValueType 匹配,包括原始类型,我是否正确?

这是现在的代码,但错误相同:

class Test
{
    void x(ref byte v) { }
    void x(ref sbyte v) { }
    void x<T>(T[] a)
    {
        for (int i = 0; i < a.Length; ++i)
            x(ref a[i]);
    }
}

【问题讨论】:

  • 这段代码没有任何意义。 T 必须同时是 bytesbyte
  • 我不认为你可以将原语作为泛型约束,如果可以的话,你当然不能将泛型类型约束为两个互斥的类型。
  • 您非常误解泛型的用途以及泛型约束的用途。您的问题标题表明了您的困惑来源:泛型不是模板。泛型的语法类似于模板,用例类似于模板,但它们是完全不同的动物。如果泛型类型参数只有两件事,那么只需编写两个方法;不要试图使它们通用。
  • 我还注意到,您的第三种方法不需要参考。基于这些误解,我认为您可能是从 C++ 转向 C#。数组类型在 C# 中通过引用自动传递;你不需要引用一个变量,这就是你正在做的。您已经有一个对 array 的引用。

标签: c#


【解决方案1】:

roslyn 给出的确切错误信息是:

(8,34,8,38): Error CS0701: 'byte' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.
(8,40,8,45): Error CS0701: 'sbyte' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.
(11,18,11,22): Error CS1503: Argument 1: cannot convert from 'ref T' to 'ref byte'

bytesbyte 不是泛型参数的有效约束。在 C# 中,您只能使用 BaseClass、Interface、引用类型、值类型和 new() 约束。

阅读this MSDN article,其中解释了如何使用通用参数约束。

对于真正感兴趣,编译器生成了第三条编译消息,因为它试图通过忽略参数T 上的无效约束来从错误中恢复。如果没有任何约束,将 T 用作 byte 是不安全的。

【讨论】:

    【解决方案2】:

    这里的问题是您从泛型方法调用非泛型方法并将 T 的参数传递给该调用。为此,您需要内部调用来调用 xElement&lt;T&gt; 方法(我这样命名它是因为您已经使用名称 x 来处理整个数组,而您的其他方法处理数组的一个元素只要)。但是,您又回到了第 1 点,因为您现在需要在内部方法体中包含一个控制流语句(如 switch),而这是您要避免的。

    这里真正的问题是模板和泛型不是一回事。第一个告诉编译器为在编译时实际检测到的每种类型创建方法的副本。第二个告诉运行时根据遇到的类型查找(或 JIT 编译,如果使用反射)方法的版本。由于您只提供了 byte 和 sbyte 的(固定的,非通用的)版本,编译器马上就知道它无法处理它在运行时可能遇到的“通用”情况。

    更简洁地说,在这个用例中,是的,您将需要一个 switch 语句,因为在您尝试使用它的意义上真正的模板是不可用的。

    还有一个选项,顺便说一句,你可以自己使用反射来找到方法:

    typeof(Test)
    // Get all public instance methods
       .GetMethods(BindingFlags.Public|
            BindingFlags.Instance|
            BindingFlags.DeclaredOnly)
    // Search by name and by parameter type
       .Where(mi => mi.Name == "xElement" && 
            mi.GetParameters()[0].ParameterType = typeof(T))
    // Take the first one found
       .First()
    // Call it
       .Invoke(this, new object[] { a[i] });
    

    【讨论】:

    • 而且我应该说你会将搜索方法(直到 .First() 结尾的所有内容)放在循环之外,然后在循环中调用 .Invoke,但这不是很对大容量有效。
    猜你喜欢
    • 1970-01-01
    • 2012-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-05
    • 2015-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多