为什么C#构造函数不能推断类型?

187
为什么构造函数不支持像泛型方法一样的类型推断?
public class MyType<T>
{
   private readonly T field;
   public MyType(T value) { field = value; }
}

var obj = new MyType(42); // why can't type inference work out that I want a MyType<int>?

尽管你可以通过工厂类来解决这个问题,
public class MyTypeFactory
{
   public static MyType<T> Create<T>(T value)
   {
      return new MyType<T>(value);
   }
}
var myObj = MyTypeFactory.Create(42);

为什么构造函数不能支持类型推断?这是出于实际或哲学上的原因吗?


2
我在两年前有过同样的问题:https://dev59.com/LHVD5IYBdhLWcg3wOpBh,所以从技术上讲这是一个重复的问题。不过Eric的回答非常出色和完整。 - Keith
如果您想为强类型视图传递多个类,请尝试以下方法:return View(Tuple.Create(new Person(), new Address())); - Marquinho Peli
这是我认为正确的答案。因为它是唯一提供实用解决方案的答案。这个解决方案可以在现实中使用。使用工厂模式。如果你将你的工厂命名为与你的泛型类型相同,那就更好了。 - Jonathan Alfaro
请为该功能请求投票!提案:构造函数类型参数推断 - Colonel Panic
5个回答

145

构造函数为什么不支持类型推断,这是否有哲学上的原因?

没有。当你有

new Foo(bar)
然后我们可以识别在范围内所有名为Foo的类型,而不考虑泛型参数个数,然后使用修改后的方法类型推断算法对每个类型进行重载分辨率。然后我们需要创建一个“好坏程度”算法,确定两个适用于 具有相同名称但具有不同泛型参数个数的两个类型中 的哪个构造函数是更好的构造函数。为了保持向后兼容性,非泛型类型上的ctor始终必须获胜。
  

构造函数为什么不能支持类型推断,这有实际原因吗?

是的。即使该功能的益处超过了其成本(这些成本相当大),也不足以实现该功能。该功能不仅必须是净收益,它还必须比我们可能投资的所有其他可能功能都具有更高的收益。它还必须比花费时间和精力修复错误、性能工作和可能的其他领域更好。最理想的情况是,它必须很好地适合发布的“主题”。

此外,正如您正确指出的那样,您可以在没有实际具备该功能的情况下通过使用工厂模式获得此功能的好处。易于解决问题的存在使该功能不太可能被实现。
该功能已经在可能的功能列表上很长时间了。它从来没有接近过足以实际实施的位置。

2015年3月更新

拟议的功能使其接近C# 6的榜首,但随后被取消。


34
在我看来,这仍然有点奇怪和不连贯。我认为实现一致性的功能可以使语言受益更多。但这只是我的个人观点。 - Dykam
12
@Triynko:哦,天啊,C#编译器无法推断出各种可能性。例如,class B<T> { public virtual void M<U>() where U : T {} } class D : B<int> { public override void M<U>() {} } ... (new D()).M();——你和我可以使用我们的精神类型推导来确定唯一可能的类型参数是int,但C#编译器错过了这个技巧。我可以列举无数例子;与F#相比,我们的类型推理非常弱。 - Eric Lippert
5
@Triynko: 或者 short M<T>(out T y){...} ... var x = M(out x); 我们可以推断出返回类型只可能是short,因此x必须是short,所以T也必须是short。但是,如果隐式类型局部存在任何循环,C#编译器会立即出错。 - Eric Lippert
6
void M<T>(T t1, T t2){} ... M(new Giraffe(), new Turtle()); 我们可以推断代码的作者可能意图让 T 成为 Animal,但是C#编译器不会推理出“Giraffe和Turtle共享一个基类Animal,所以它是T的最佳候选对象”的结果。 - Eric Lippert
16
语言一致性是一个优先考虑的问题,但与更实际的问题相比,它的优先级较低。我向您保证,我们有一个长到您手臂长度的功能列表,每个功能只需要一个句子来描述;即使我们的预算是目前的二十倍,我们仍然不想实现列表中的所有功能!相信我,你不会想使用一个已经加入了所有可能添加的功能的语言。我们非常谨慎地选择如何花费有限的精力,以为客户提供最大的价值。 - Eric Lippert
显示剩余16条评论

16
public class MyType<T> 
{ 
   private readonly T field; 
   public MyType(T value) { field = value; } 
} 

它们可以,没有必要再告诉构造函数'什么是T',因为你已经在类声明中做过了。

另外你的工厂是不正确的,你需要有public class MyTypeFactory<T>而不仅仅是public class MyTypeFactory - 除非你在MyType类中声明工厂。

更新后的编辑:

好的,42是long、short、int还是其他什么?

假设你有以下内容:

class Base
{
   public virtual void DoStuff() { Console.WriteLine("Base"); }
}

class Foo : Base
{
   public override void DoStuff() { Console.WriteLine("Foo");  }
}

然后你做了这件事

var c = new Foo();

var myType = new MyType(c);

你会期望使用foo还是base?我们需要告诉编译器在T的位置上使用什么。

当你真正想要使用类型base时。

因此,

var myType = new MyType<Base>(c);

2
编译器无法推断类型,因为可能存在多个构造函数。为了增强您的示例,当存在 MyType(double) 时,编译器如何知道调用哪个构造函数? - Steve Guidi
2
@PostMan - 但是在Create方法中,编译器可以简单地推断T为int。 - theburningmonk
你肯定不想让工厂变成通用的,因为那会阻止类型推断。 - Ben Voigt
@Ben Voigt - 我没有这么说,原始问题是在他的工厂中,我只是纠正了它。 - PostMan
4
@PostMan:我不同意你回答中的一部分,即:<quote>你的工厂是不正确的,你需要有public class MyTypeFactory<T>而不仅是public class MyTypeFactory - 除非你将工厂声明在 MyType 类内部</quote> 实际上,正确的做法是使用现在问题中所拥有的 MyTypeFactory.Create<T>,而不是你说需要的 MyTypeFactory<T>.Create。使用 MyTypeFactory<T>.Create 将会阻止类型推断,因为出现在成员引用运算符 . 之前的泛型参数无法被推断,而方法上的参数可以被推断。 - Ben Voigt
显示剩余4条评论

12

泛型类型推断无法像您希望的那样在构造函数中起作用的主要原因是,当您声明“MyType<T>”时,“MyType”类甚至不存在。请记住,同时拥有以下两种情况是合法的:

public class MyType<T> {
}

public class MyType {
}

两者都是合法的。如果你确实声明了两者,并且这两者都声明了一个冲突的构造函数,你该如何消除语法的歧义。


你如何消除语法歧义?在这种情况下,你需要指定 T。 - Pedro Henrique
但在这个问题的情况下(以及我的情况),只有一个类(是通用的)。没有歧义。如果我有 var myVariable = new MyClass(),编译器如何知道不要使myVariable的类型为Object或MyClass的基类?所有在这些答案中提出的相反情况都不能真正解释为什么它不能从构造函数周围的本地代码中推导类型……然后如果它弄错了,程序员就会使其更明确……就像在任何其他推断类型的情况下一样。如果需要,总是可以覆盖默认选择。 - C Perkins
@CPerkins 当然,如果您创建了非泛型版本,那么使用您建议的语法的所有使用者现在都会遇到编译器错误。 - Kirk Woll
2
有时候说“如果你这样做,可能会发生不好的事情,所以不允许”,是合理的,但是当类或方法声明的更改会导致其他很多情况出现问题时,这种推理并不成立。以基本重写为例,可能会声明一个额外的重写,从而创建歧义并破坏现有代码。那又怎样呢?这需要重构或一些替代方案,而不是事先决定不允许覆盖方法。 - C Perkins

0
构造函数需要与类本身具有相同的泛型规范。否则,就无法知道您示例中的int是与类相关还是与构造函数相关。
var obj = new MyType<int>(42);

这是不是类 MyType<T>,其构造函数是 MyType(int) 或者是类 MyType,其构造函数是 MyType<T>(T)


2
这种歧义也可能出现在普通的泛型方法中。 - Dykam
7
大多数情况下是可以知道的,但不是完全不可能。如果无法明确确定意图,我们会显示错误。这与任何其他重载解析问题相同;如果提供的信息不足,则会显示错误。 - Eric Lippert

0

虽然这个问题已经被回答了很多次,但我觉得我需要澄清一件事情:C# 支持构造函数的泛型类型推断。问题在于,它既不支持向构造函数添加泛型参数,也不支持类型泛型类型推断。想要推断类型本身的泛型类型参数基本上就像要求 Foo.Bar(0) 推断为 Foo<int>.Bar(0) 一样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接