【问题标题】:Generics can't infer second parameter? [duplicate]泛型无法推断第二个参数? [复制]
【发布时间】:2011-10-11 10:22:57
【问题描述】:

我注意到 C# 编译器不会推断出第二个泛型参数。
示例:

C++ 模板代码:(是的,我知道模板不像泛型那样工作)

class Test {
public:
template <class T,class V> 
    T test(V v) {
       //do something with v
       return T();
    }
};

int i = 0;
Test t = new Test();
double j = t.test<double>(i); //infers V as int

模板(和泛型)不能推断返回类型,所以在 C++ 中我给它第一个模板参数,第二个模板参数是从变量类型推断出来的。

现在,C# 中的相同示例:

class Test {
    public T test<T,V>(V v) where T: new() {
       //do something with v
       return new T();
    }
};

int i = 0;
Test t = new Test();
double j = t.test<double>(i); //Error Using the generic method 'Test.test<T,V>(V)' requires '2' type arguments

但如果我使用 1 种类型,我不必明确指定类型:

class Test {
    public V test<V>(V v) where V: new() {
       return new V();
    }
};

int i = 0;
Test t = new Test();
int j = t.test(i); //OK infers V as int.

那么,为什么 C# 泛型不能推断第二种类型(而在 C++ 模板中它显然可以)?
我确信它是这样设计的(我怀疑 .Net 团队忽略了这一点),所以 为什么 它是这样设计的,我必须明确指定这两种类型?

编辑:

从到目前为止我们在答案中的讨论来看,两种语言都支持按模板参数数量重载。

那么,为什么 C# 是这样设计的?不允许显式声明一个参数的语言实现有什么不同?

【问题讨论】:

  • @CodeInChaos :感谢您的参考,但那里也没有真正的答案。 “可能简化类型推理规则”不是真正的原因。他们已经在 C++ 中做到了,为什么不在这里呢?
  • 简化是一个非常真实的原因。与 C++ 相比,C# 经常选择更简单但不太通用的规则。
  • CodeInChaos:也许,但这是一个假设。找不到任何依据。
  • @Yochai,您为什么希望您的问题的答案与“语言实现的不同”有关?据推测,这不起作用的原因与 C# 中可能存在但不存在的每个看起来不错的功能相同。 Eric Lippert 写道:“我通常对why is feature X not implemented? 的回应是,在有人设计、实施、测试、记录和发布该功能之前,所有功能当然都是未实现的,而且还没有人花钱这样做。”跨度>

标签: c# .net templates generics


【解决方案1】:

C# 被设计成比 C++ 稍微不那么费脑筋的语言。

特别是,出于各种原因,我认为将 C# 泛型与 C++ 模板进行比较并不是一个好主意 - 从根本上说,它们是在 中完成相似 事情的两种完全不同的方法一些 情况。 C++ 方法在某些方面肯定是灵活的——尽管它不允许(据我了解)仅以二进制形式存在的模板,或者在执行时创建新的模板特化。基本上,C++ 模板方法不能很好地适应 .NET 的其余部分。

现在至于为什么您不能指定 some 类型参数并允许推断其他类型参数(这是 语言 决定而不是 平台 em> 决定;我确信就 .NET 本身而言它是可行的) - 再次,我相信这是为了简单起见。在 C# 中选择正确的方法正确的类型参数已经非常复杂 - 比大多数 C# 开发人员所能理解的还要复杂。它涉及:

  • 可能从目标的编译时类型考虑类型层次结构中的方法
  • 按参数数量重载
  • 类型参数的数量重载
  • 命名参数的效果
  • 可选参数的作用
  • 泛型类型参数约束对参数类型的影响(不是目标方法指定的约束,注意)
  • 委托转换的方法组
  • 匿名函数转换
  • 类型参数的类型推断
  • 动态输入
  • 泛型协变和逆变

就我个人而言,我认为这足以让我头脑清醒,不允许还有更多可能性,因为“如果 M 具有 至少 一样多的类型,它仍然可以成为候选者参数作为指定类型参数”。您还想要命名类型参数和可选类型参数吗? ;)

我已经看过很多重载,彻底遵循规范等。我发现了一些让语言设计者摸不着头脑并试图弄清楚编译器应该做什么的地方。我发现编译器肯定出错的地方。 如果没有很好的理由,我不想在这里增加任何复杂性。

所以是的,它基本上是为了简单起见,有时这很痛苦 - 但通常你可以解决它。对于每个潜在功能,您需要考虑:

  • 该功能对最终开发人员的好处
  • 最终开发人员在理解该功能上所花费的时间
  • 语言设计者彻底设计和指定语言的成本
  • 编译器编写者正确实现它的成本
  • 测试团队彻底测试的成本(连同与过载有关的所有其他内容)
  • 未来潜在功能的成本(如果这使语言变得更复杂,那么其他功能的“潜在可理解”额外复杂性就会减少)

【讨论】:

  • 只有一个逗号会很有用,比如 call&lt;MyClass, &gt; ... 不是很美观,但在指定通用参数 (?) 类型时已经存在于语法中
【解决方案2】:

正如 Dan 所说,C# 不允许您仅推断泛型参数集的某些类型参数。这可能会根据泛型参数的数量启用重载(C# 允许,至少对于泛型类)。

但是,您可以指定泛型类的参数,并推断该类中泛型方法的参数。但这种解决方法并不总是一个好的解决方案。

【讨论】:

  • C++ 也允许你重载模板函数。并且还允许专业化(这更加复杂)
  • @Yochai:C++模板函数是通过参数类型重载的,这与基于模板参数数量的重载有很大不同。而对于类,模板特化实际上统一了模板参数的数量。
  • 您也可以通过模板参数的数量来重载。 template &lt;class T,class V&gt; T test(V v) { return new T() }; template &lt;class T,class V,class U&gt; T test(V v) { U u = new U(); return new T() };
  • @Yochai:在修复了大约 8 个编译错误之后,这似乎可行。
  • @Yochai:您对模板参数计数重载的演示。
【解决方案3】:

在某些情况下,如果您希望指定某些类型参数并推断其他类型参数,则可以提供帮助的一件事是创建一个具有您想要指定的参数的通用静态类,然后在该类中具有一个通用静态方法想要推断的参数。例如,我有一个方法,给定一个可转换为 Action(T,U,V) 的方法以及一个 T,将生成一个 Action(U,V),它将使用最初指定的委托调用该委托T 连同 U 和 V。该方法将被调用为(vb 语法):

NewAction = ActionOf(Of FooType, BarType).NewAction(AddressOf MyFunctionOfBozFooBar, someBoz)

编译器可以使用 someBoz 的类型确定泛型类型参数之一,即使它需要明确指定 FooType 和 BarType 参数。

【讨论】:

    猜你喜欢
    • 2015-08-03
    • 2021-11-06
    • 1970-01-01
    • 1970-01-01
    • 2016-10-11
    • 2021-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多