为什么C#不支持在类构造函数上使用隐式泛型类型?

54

如果编译器可以推断出泛型类型参数,C#不需要你指定它,例如:

List<int> myInts = new List<int> {0,1,1,
    2,3,5,8,13,21,34,55,89,144,233,377,
    610,987,1597,2584,4181,6765};

//this statement is clunky
List<string> myStrings = myInts.
    Select<int,string>( i => i.ToString() ).
    ToList<string>();

//the type is inferred from the lambda expression
//the compiler knows that it's taking an int and 
//returning a string
List<string> myStrings = myInts.
    Select( i => i.ToString() ).
    ToList();

这在匿名类型中非常需要,因为你不知道类型参数是什么(在intellisense中它显示为'a),因为它是由编译器添加的。

类级别的类型参数不能实现这个:

//sample generic class
public class GenericDemo<T> 
{
    public GenericDemo ( T value ) 
    {
        GenericTypedProperty = value;
    }

    public T GenericTypedProperty {get; set;}
}

//why can't I do:
int anIntValue = 4181;
var item = new GenericDemo( anIntValue ); //type inference fails

//however I can create a wrapper like this:
public static GenericDemo<T> Create<T> ( T value )
{
    return new GenericDemo<T> ( value );
}

//then this works - type inference on the method compiles
var item = Create( anIntValue );

为什么C#不支持类级别的泛型类型推断?

为什么这个问题被投票关闭,认为它是为什么C#构造函数不能推断类型?的重复,而那个问题比这个问题晚两年呢?这不应该是重复吗? - Keith
我认为另一个问题更简洁,而且它有更好的答案。 - Sam
@Sam - 是的,Eric Lippert 对那个问题的回答很权威,但我认为两个问题都不应该被关闭。 - Keith
1
更新2017年:如果您想要此功能,请在https://github.com/dotnet/csharplang/issues/281投票支持该提案。 - Colonel Panic
@ColonelPanic 感谢,我已经点赞了那个问题,但它并不是真正的投票阶段——回复似乎是“有些复杂,但我们也想解决它”。我会继续关注它的,谢谢。 - Keith
3个回答

33
实际上,你的问题并不差。我已经玩弄了一个通用编程语言几年了,尽管我从来没有真正开发过它(也许永远不会),但我一直在思考通用类型推断,我的首要任务之一始终是允许构建类而无需指定通用类型。
C#简单地缺乏使这种可能性成为现实的规则集。我认为开发人员从未看到包含这个的必要性。实际上,以下代码将非常接近你的建议并解决问题。C#所需要的只是增加语法支持。
class Foo<T> {
    public Foo(T x) { … }
}

// Notice: non-generic class overload. Possible in C#!
class Foo {
    public static Foo<T> ctor<T>(T x) { return new Foo<T>(x); }
}

var x = Foo.ctor(42);

由于这段代码实际上可以工作,我们已经证明了问题不是语义的问题,而仅仅是缺乏支持。我想我得收回之前的发帖。;-)


11
为什么C#不支持类级别的泛型类型推断?
因为它们通常是有歧义的。相比之下,对于函数调用来说,类型推断是很容易的(如果所有类型都出现在参数中)。但是对于构造函数调用(本质上是为了讨论而夸大其词的函数),编译器必须同时解决多个级别的问题。一个级别是类级别,另一个级别是构造函数参数级别。我认为解决这个问题在算法上是不容易的。直觉上,我会说这甚至是NP完备的。
为了说明无法解决的极端情况,请想象以下类,并告诉我编译器应该怎么做:
class Foo<T> {
    public Foo<U>(U x) { }
}

var x = new Foo(1);

从Konrad的问题提出的方式中,我已经知道类型“int”不能仅从“new Foo(1)”示例中推断出来,但是有人能解释一下为什么吗?我的意思是,如果我说“var x = 1”,我最终得到一个int,那么这个示例与此有何不同呢? - Protector one
1
我认为“因为它们通常是模棱两可的”不是一个很好的理由;泛型方法也是模糊的,如果存在与其签名相同的非泛型方法。 - Sam
@Sam 我完全同意,因此请看我的其他(被接受的)答案。然而,我确实相信这就是它们在C#中不存在的原因。这个问题真的很老了,答案是在Stack Overflow强制执行更严格的规则关于答案之前写的,并且在评论被实施之前 - 因此,整个线程读起来像是一个讨论(它本来就是)。我没有删除这个答案的唯一原因是否则这里的其他答案会变得更加混乱。 - Konrad Rudolph

3
感谢Konrad,这是一个很好的回答(+1),但是让我对其进行扩展。
假设C#有一个显式的构造函数:
//your example
var x = new Foo( 1 );

//becomes
var x = Foo.ctor( 1 );

//your problem is valid because this would be
var x = Foo<T>.ctor<int>( 1 );
//and T can't be inferred

您说得很对,第一个构造函数无法推断。

现在让我们回到这个类。

class Foo<T> 
{
    //<T> can't mean anything else in this context
    public Foo(T x) { }
}

//this would now throw an exception unless the
//typeparam matches the parameter
var x = Foo<int>.ctor( 1 );

//so why wouldn't this work?
var x = Foo.ctor( 1 );

当然,如果我将您的构造函数添加回去(带有其备用类型),我们就会出现歧义调用——就像无法解析常规方法重载一样。

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