为什么不能从与委托方法的一部分相同的泛型定义的参数中推断出泛型类型?

Why can't the generic type be inferred from a parameter defined by the same generic as part of a delegate method?

我不明白为什么无法推断出对 GenericMethod<T>() 的调用。我的实际问题包含更多通用参数,使得调用代码非常冗长且难以阅读。

GenericMethod<T>() 可以在没有显式类型参数的情况下调用,如果参数本身是 'T',但约束不适用于委托,一旦你输入约束类型参数,你回到原点。

delegate void GenericDelegate<T>(T t);

static void GenericMethod<T>(GenericDelegate<T> _) { }

static void IntMethod(int _) { }

static void CallingMethod()
{
    // The type arguments for method 'GenericMethod<T>(GenericDelegate<T>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
    GenericMethod(IntMethod);
}

是否有另一种方法可以使调用代码像这里描述的那样简单? GenericMethod<T>() 是内部库的一部分。

看看 Eric Lippert 关于类型推断的 series

归结为任何语言特性的原因

  • 实施起来可能很昂贵(时间、资源)。
  • 必须深思熟虑,特别是如果它是一个重大变化。

I do not understand why the call to GenericMethod<T>() cannot be inferred.

问题有点含糊。让我试着把它弄脆。

What does the C# specification say about generic method type inference where an argument is a method group?

推理分两个阶段进行。在第一阶段,从参数类型已知的匿名函数参数类型的参数进行推导。方法组没有类型,所以第一阶段方法组没有扣分。

在第二阶段,从lambdas 和方法组中进行推导,其中目标类型是已推断输入类型的委托类型

在您的示例中,输入类型是 T,尚未推断,因此不会发生推断。

这就是类型推断失败的原因。

这个答案可能不能令人满意,但那是因为你问了一个模糊的问题。让我们再次尝试通过提出后续问题来解决问题的更清晰版本:

What justifies the design decision that input types must be inferred before type inference considers evidence from method groups?

我们做出这个决定是因为来自方法组的证据是通过首先确定用户打算引用的组中的确切方法得出的。那么我们如何确定该方法呢?在任何其他情况下,我们确定方法组中的方法的方式相同。 我们对该方法组进行重载解析。但是重载决策需要知道参数的类型,这意味着在分析方法组时输入类型必须已知

比如我们有void M<A, B>(A a, Func<A, B> f){}string N(int y),还有M(123, N)那么我们首先判断A是第一个阶段的int,并且现在 Func<A, B> 中的输入是已知的,因此重载解析可以确定 string N(int) 是预期的,因此 Bstring.

现在你的后续问题肯定是:

But in my situation the method group only has one member. Can't the compiler simply skip overload resolution and determine that I mean the only method in the group?

您真正想说的是您想要两种不同的 规则,一种是针对您的场景量身定制的,一种是通用的。

首先,C# 设计团队试图避免为狭义的、罕见的情况创建规则的情况。类型推断的重点不是使用技巧和启发式方法进行所有可能的推断。它旨在有原则尽可能使用现有算法。而且,推理算法已经很复杂了;让我们不要让它变得更复杂。

但是哦,情况变得更糟了。假设 C# 设计团队决定实施规则 "overload resolution is skipped if the method group contains one method"。我想这会让你暂时满意,但连锁反应是什么? 我们刚刚创建了一个炸弹,当您将第二个方法添加到方法组时,它会稍后引爆。它使得在不相关位置添加第二种方法成为潜在的破坏性更改,这很不幸。

此外,您可能不会长期满意。 "I added a second method, but it was of a different arity, so change overload resolution again to discard methods of the wrong arity until there is only one left" ...等等。同样,设计、指定、实施、测试、记录和解释算法变得越来越复杂和困难。随着这些算法变得越来越复杂,它们更有可能按照用户的意图去做,当然,但是当它们不这样做时,用户就会面临一堆规则的泥潭,需要试图去理解。 (有人可能会争辩说我们已经在那里了,所以我们不要让它变得更糟。)

总之:原则上,你想要的那种推导是肯定可以的。编译器团队选择不这样做,以努力使推理算法能够被人类理解。

Why is it different when doing it with a lambda? This compiles: GenericMethod((int v) => IntMethod(v))

您现在应该有必要的信息来回答这个问题,但要拼写出来:

  • lambda内部IntMethod的重载解析知道vint类型,所以重载解析成功。

  • 既然重载解析成功了,我们知道lambda是int => void,可以推断出void GenericDelegate<T>(T t).

如果你改写 GenericMethod(v => IntMethod(v)) 类型推断会再次失败。在 v 的类型已知之前无法分析 lambda 的主体,而 v 的类型取决于 T 的推断类型,这就是我们正在努力工作的out,所以推理算法没有进展,失败了。

Is there another way to make the calling code as simple as described here?

没有。只需插入 <int> 并完全跳过类型推断。或者将参数转换为适当的 GenericDelegate 类型,这将使​​重载决策在给定泛型委托中的类型的方法组上运行。

提供了为什么这不起作用的所有细节,以及更多围绕它的设计理念。我将为未来的谷歌员工提供一种用代码描述问题的更简短的方法。稍微修改一下问题代码,为什么无法推断类型应该很明显:

delegate void GenericDelegate<T>(T t);

static void GenericMethod<T>(GenericDelegate<T> method) { }

static void Method(int _) { }
static void Method(float _) { }

static void CallingMethod()
{
    GenericMethod(Method);
}

当方法组 Method 有两个重载时,编译器无法推断 T 的类型。即使该方法组仅包含一个重载,问题实际上也完全相同!

现在回答我的另一个问题,调用代码可以像上面描述的那样简单吗?好吧,并非没有重大重组。不过,这样的东西对我有用:

static void GenericMethod<T>(IMethod<T> method) { }

interface IMethod<T>
{
    void Method(T _);
}

class IntMethod: IMethod<int>
{
    public void Method(int _) { }
}

static void CallingMethod()
{
    GenericMethod(new IntMethod());
}

我承认,它与上面的代码示例有很大不同,但实现了我的目标:一个干净的调用站点。该解决方案的代价是稍微混乱的声明。这在可读性和使用方面是赢还是输在很大程度上取决于用例。