.net NET中的泛型方法不能推断其返回类型,为什么?

e5njpo68  于 2022-12-27  发布在  .NET
关注(0)|答案(7)|浏览(169)

给定:

static TDest Gimme<TSource,TDest>(TSource source) 
{ 
    return default(TDest); 
}

为什么我不能:

string dest = Gimme(5);

而不会出现编译器错误:
第一个月
5可以被推断为int,但有一个限制,即编译器不会/不能将返回类型解析为string。我在几个地方读到过 * 这是设计 *,但没有真正的解释。我在一些地方读到过,这在C#4中可能会改变,但它没有。
有人知道为什么返回类型不能从泛型方法中推断出来吗?这是不是一个答案很明显就在你眼前的问题?我希望不是!

1tuwyuhd

1tuwyuhd1#

这里的一般原则是类型信息只"单向"流动,从表达式的 * 内部 * 到 * 外部 *。你给出的例子非常简单。假设我们在对方法R G<A, R>(A a)进行类型推断时希望类型信息"双向"流动,并考虑一些疯狂的场景,这会产生:

N(G(5))

假设N有10个不同的重载,每个重载都有不同的参数类型,我们是否应该对R进行10次不同的推理?如果我们这样做了,我们是否应该以某种方式选择"最好的"一个?

double x = b ? G(5) : 123;

G的返回类型应该被推断为什么呢?,Int,因为条件表达式的另一半是int?,或者double,因为最终这个东西会被赋值给double?,现在也许你开始明白这是怎么回事了;如果你要说你的推理是从外到内,那么"你走了多远"?在这条路上可能有"许多"步骤。看看当我们开始结合这些步骤时会发生什么:

N(b ? G(5) : 123)

现在我们该怎么做?我们有十个N的重载可供选择。我们说R是int吗?它可以是int或任何类型,int可以隐式转换为。但在这些类型中,哪些可以隐式转换为参数类型N?我们是否要自己编写一个小prolog程序,并要求prolog引擎解决R可能的所有返回类型,以满足以下每一个条件N上可能的过载,然后选出最好的一个
(我不是在开玩笑;有些语言本质上是写一个小小的序言程序,然后使用逻辑引擎来计算出所有东西的类型。例如,F#做的类型推理比C#复杂得多。Haskell的类型系统实际上是图灵完备的;你可以在类型系统中编码任意复杂的问题,并要求编译器解决它们。正如我们稍后看到的,C#中的重载解析也是如此--你不能像在Haskell中那样在C#类型系统中编码Halting Problem,但你可以将NP-HARD问题编码为重载解析问题。)(见下文
这仍然是一个非常简单的表达式。假设你有

N(N(b ? G(5) * G("hello") : 123));

现在我们必须多次解决这个问题,对于G,也可能是对于N,我们必须解决它们,我们有五个过载解决问题要解决,公平地说,所有的问题,应该同时考虑它们的参数和上下文类型。如果N有10种可能性,那么N可能有100种可能性需要考虑(N(...))和一千个N(N(N(...))),很快你就会让我们解决问题,很容易有数十亿种可能的组合,使编译器非常缓慢。
这就是为什么我们有类型信息单向流动的规则,它防止了鸡和蛋的问题,即你试图从内部类型确定外部类型,又从外部类型确定内部类型,从而导致可能性的组合爆炸。
注意类型信息对于lambdas来说是双向的!如果你说N(x=>x.Length),那么肯定的是,我们考虑N的所有可能的重载,它们的参数中有函数或表达式类型,并尝试x的所有可能的类型。there are situations in which you can easily make the compiler try out billions of possible combinations以找到唯一有效的组合。使泛型方法能够这样做的类型推理规则极其复杂,甚至让JonSkeet都感到紧张。This feature makes overload resolution NP-HARD
让类型信息在lambda中双向流动,以便泛型重载解析能够正确有效地工作,这花了我大约一年的时间。这是一个如此复杂的特性,只有在我们绝对肯定能从这项投资中获得惊人的回报时,我们才想采用它。使LINQ工作是值得的。但是没有像LINQ这样的相应特性能够证明使这项工作在总体上花费巨大的费用是合理的。

    • 更新**:事实证明,你可以在C#类型系统中对任意困难的问题进行编码。C#具有带有泛型逆变的名义泛型子类型,而且已经证明,你可以从泛型类型定义中构建一个图灵机,并强制编译器执行该机器。可能会进入无限循环。在我写这个答案的时候,这样的类型系统的不可判定性是一个开放的问题。详细信息请参见https://stackoverflow.com/a/23968075/88656
gopyfrb3

gopyfrb32#

您必须做到:

string dest = Gimme<int, string>(5);

你需要在调用泛型方法时指定你的类型,它怎么知道你想要输出一个字符串呢?
System.String是一个不好的例子,因为它是一个密封类,但是假设它不是。如果你没有在调用中指定类型,编译器怎么知道你不需要它的子类呢?
举个例子:

System.Windows.Forms.Control dest = Gimme(5);

编译器如何知道实际上要生成什么控件呢?你需要这样指定它:

System.Windows.Forms.Control dest = Gimme<int, System.Windows.Forms.Button>(5);
dldeef67

dldeef673#

调用Gimme(5)忽略返回值是法律的的语句编译器如何知道返回哪种类型?

ffscu2ro

ffscu2ro4#

当我需要做类似的事情时,我会使用这个技巧:

static void Gimme<T>(out T myVariable)
{
    myVariable = default(T);
}

像这样使用它:

Gimme(out int myVariable);
Print(myVariable); //myVariable is already declared and usable.

请注意,out变量的内联声明从C#7.0开始可用

xtupzzrd

xtupzzrd5#

我想这是一个设计决定。我也发现它在用Java编程时很有用。
与Java不同,C#似乎是朝着函数式编程语言的方向发展的,而且你可以反过来得到类型推断,所以你可以有:

var dest = Gimme<int, string>(5);

这将推断dest的类型。我猜将此与Java风格的推断混合可能会被证明是相当难以实现的。

owfi6suc

owfi6suc6#

如果一个函数应该返回少数类型中的一个,你可以让它返回一个定义了扩展转换为这些类型的类,我认为这不可能以泛型的方式实现,因为扩展ctype操作符不接受泛型类型参数。

xmakbtuz

xmakbtuz7#

public class ReturnString : IReq<string>
{
}

public class ReturnInt : IReq<int>
{
}

public interface IReq<T>
{
}

public class Handler
{
    public T MakeRequest<T>(IReq<T> requestObject)
    {
        return default(T);
    }
}

var handler = new Handler();
string stringResponse = handler.MakeRequest(new ReturnString());
int intResponse = handler.MakeRequest(new ReturnInt());

相关问题