C# 泛型方法类型参数不是从用法中推断出来的

标签 c# generics methods interface type-inference

最近我试验了访问者模式的实现,我尝试使用通用接口(interface)强制执行 Accept 和 Visit 方法:

public interface IVisitable<out TVisitable> where TVisitable : IVisitable<TVisitable>
{
    TResult Accept<TResult>(IVisitor<TResult, TVisitable> visitor);
}

- 其目的是 1) 将特定类型“Foo”标记为此类访问者可访问,而访问者又是“此类 Foo 的访问者”,以及 2) 在实现可访问类型上强制执行正确签名的 Accept 方法,像这样:

public class Foo : IVisitable<Foo>
{
    public TResult Accept<TResult>(IVisitor<TResult, Foo> visitor) => visitor.Visit(this);
}

到目前为止一切顺利,访问者界面:

public interface IVisitor<out TResult, in TVisitable> where TVisitable : IVisitable<TVisitable>
{
    TResult Visit(TVisitable visitable);
}

-应该 1) 将访问者标记为“能够访问”TVisitable 2) 此 TVisitable 的结果类型 (TResult) 应该是什么 3) 强制每个 TVisitable 访问者实现正确签名的访问方法是“能够访问”,像这样:

public class CountVisitor : IVisitor<int, Foo>
{
    public int Visit(Foo visitable) => 42;
}

public class NameVisitor : IVisitor<string, Foo>
{
    public string Visit(Foo visitable) => "Chewie";
}

非常愉快和美丽,这让我写:

var theFoo = new Foo();
int count = theFoo.Accept(new CountVisitor());
string name = theFoo.Accept(new NameVisitor());

很好。

现在悲伤的时刻开始了,当我添加另一个可访问类型时,比如:

public class Bar : IVisitable<Bar>
{
    public TResult Accept<TResult>(IVisitor<TResult, Bar> visitor) => visitor.Visit(this);
}

可以访问的是CountVisitor :

public class CountVisitor : IVisitor<int, Foo>, IVisitor<int, Bar>
{
    public int Visit(Foo visitable) => 42;
    public int Visit(Bar visitable) => 7;
}

这突然打破了 Accept 方法中的类型推断! (这破坏了整个设计)

var theFoo = new Foo();
int count = theFoo.Accept(new CountVisitor());

给我:

"The type arguments for method 'Foo.Accept<TResult>(IVisitor<TResult, Foo>)' cannot be inferred from the usage."

谁能详细说明这是为什么? IVisitor<T, Foo>只有一个版本CountVisitor 的接口(interface)实现 - 或者,如果 IVisitor<T, Bar>由于某种原因无法消除,它们都具有相同的T - int , = 无论如何,没有其他类型可以在那里工作。一旦有多个合适的候选者,类型推断是否会立即放弃? (有趣的事实:ReSharper 认为 int 中的 theFoo.Accept<int>(...) 是多余的 :P,即使没有它也无法编译)

最佳答案

似乎类型推断以一种贪婪的方式工作,首先尝试匹配方法 泛型,然后是类泛型。所以如果你说

int count = theFoo.Accept<int>(new CountVisitor());

它有效,这很奇怪,因为 Foo 是类泛型类型的唯一候选者。

首先,如果您将方法泛型类型替换为第二类泛型类型,它会起作用:

public interface IVisitable<R, out T> where T: IVisitable<int, T>
{
    R Accept(IVisitor<R, T> visitor);
}

public class Foo : IVisitable<int, Foo>
{
    public int Accept(IVisitor<int, Foo> visitor) => visitor.Visit(this);
}

public class Bar : IVisitable<int, Bar>
{
    public int Accept(IVisitor<int, Bar> visitor) => visitor.Visit(this);
}

public interface IVisitor<out TResult, in T> where T: IVisitable<int, T>
{
    TResult Visit(T visitable);
}

public class CountVisitor : IVisitor<int, Foo>, IVisitor<int, Bar>
{
    public int Visit(Foo visitable) => 42;
    public int Visit(Bar visitable) => 7;
}

class Program {
    static void Main(string[] args) {
        var theFoo = new Foo();
        int count = theFoo.Accept(new CountVisitor());
    }
}

其次(这是强调类型推断如何工作的奇怪部分)看看如果替换 int 会发生什么与 stringBar访客:

public class CountVisitor : IVisitor<int, Foo> , IVisitor<string, Bar>
{
    public int Visit(Foo visitable) => 42;
    public string Visit(Bar visitable) => "42";
}

首先,您会遇到同样的错误,但请注意如果强制使用字符串会发生什么:

    int count = theFoo.Accept<string>(new CountVisitor());

error CS1503: Argument 1: cannot convert from 'CountVisitor' to 'IVisitor<string, Foo>'

这表明编译器首先查看方法 泛型类型(在您的情况下为 TResult),如果找到更多候选者则立即失败。它甚至没有进一步查看类泛型类型。

我试图从 Microsoft 找到类型推断规范,但找不到。

关于C# 泛型方法类型参数不是从用法中推断出来的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51900775/

相关文章:

c# - 为什么 C/C++ 有头文件,不像 C# 和 Java 等其他语言?

c# - 如何使用 EC2 api 来判断实例状态?

c# - 如何在发出请求之前将 WCF Http 客户端绑定(bind)到特定的出站 IP 地址

Java 泛型扩展

java - ArrayDeque 类的字符

java - 具有上限的未知类型的类

Java 8 通过签名推断方法引用解析,这是否损坏?

jquery - 如何从插件调用 jQuery 插件中的公共(public)函数

c# - 在 mysql 数据库中存储大文件/二进制数据 : when is it ok?

具有无限参数的 c# 方法或具有数组或列表的方法?