Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/314.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C#泛型方法类型参数不是从用法推断的_C#_Generics_Methods_Interface_Type Inference - Fatal编程技术网

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

C#泛型方法类型参数不是从用法推断的,c#,generics,methods,interface,type-inference,C#,Generics,Methods,Interface,Type Inference,最近,我尝试了访问者模式的一个实现,我尝试用通用接口强制执行Accept&Visit方法: public interface IVisitable<out TVisitable> where TVisitable : IVisitable<TVisitable> { TResult Accept<TResult>(IVisitor<TResult, TVisitable> visitor); } 很好 现在,当我添加另一个可访问类型时,悲

最近,我尝试了访问者模式的一个实现,我尝试用通用接口强制执行Accept&Visit方法:

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

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

public class Bar : IVisitable<Bar>
{
    public TResult Accept<TResult>(IVisitor<TResult, Bar> visitor) => visitor.Visit(this);
}
这突然打破了Accept方法中的类型推断!(这破坏了整个设计)

给我:

“无法从用法推断方法
'Foo.Accept(IVisitor)
的类型参数。”

有人能详细解释一下原因吗?
CountVisitor
只实现了一个版本的
IVisitor
接口,或者,如果由于某种原因无法消除
IVisitor
,那么它们都具有相同的
t
-
int
,=没有其他类型在那里工作。当有不止一个合适的候选者时,类型推断是否就放弃了?(有趣的事实:ReSharper认为
theFoo.Accept(…)
中的
int
是多余的:P,即使没有它也无法编译)

在C#中,您可以通过使用

dynamic
关键字删除“双重分派”来简化访问者模式

您可以这样实现访问者:

public class CountVisitor : IVisitor<int, IVisitable>
{
   public int Visit( IVisitable v )
   {
       dynamic d = v;
       Visit(d);
   }

    private int Visit( Foo f ) 
    {
        return 42;
    }

    private int Visit( Bar b )
    {
        return 7;
    }
}
公共类countvisor:IVisitor
{
公共int访问(IVisitable v)
{
动态d=v;
访问(d);
}
私人内部访问(Foo f)
{
返回42;
}
私人内部访问(b栏)
{
返回7;
}
}

通过这样做,您不需要在
Foo
Bar
上实现Accept方法,尽管它们仍然必须为
访问者实现一个公共接口才能正常工作。

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

int count = theFoo.Accept<int>(new CountVisitor());
首先,您会得到相同的错误,但请注意如果强制使用字符串会发生什么:

    int count = theFoo.Accept<string>(new CountVisitor());
int count=foo.Accept(new CountVisitor());
错误CS1503:参数1:无法从
'CountVisitor'
转换为
'IVisitor'

这意味着编译器首先查看方法泛型类型(在您的例子中是
TResult
),如果找到更多候选方法,则会立即失败。它甚至没有进一步研究类泛型类型

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

当有不止一个合适的候选者时,类型推断是否就放弃了

是的,在这种情况下是这样的。在尝试推断方法的泛型类型参数(
TResult
)时,类型推断算法在对类型
IVisitor
进行两次推断时似乎失败


从第7.5.2条(我能找到的最新版本)中:

trm(T1-x1…Tm-xm)

使用
M(E1…Em)
形式的方法调用,类型推断的任务是找到唯一的类型参数
S1…Sn
用于每个类型参数
X1…Xn
,以便调用
M(E1…Em)
变得有效

编译器采取的第一步如下(§7.5.2.1):

对于每个方法参数
Ei

  • 如果
    Ei
    是匿名函数,则根据
    Ei
    Ti

  • 否则,如果
    Ei
    具有类型
    U
    xi
    是值参数,则从
    U
    Ti
    进行下限推断

您只有一个参数,因此唯一的
Ei
是表达式
newcountvisitor()
。这显然不是匿名函数,所以我们在第二个要点中。在我们的例子中,
U
属于
CountVisitor
类型,这一点很简单。“
xi
是一个值参数”位基本上意味着它不是一个
out
in
ref
等变量,这里就是这种情况

此时,我们需要从
CountVisitor
IVisitor
对§7.5.2.9的相关部分进行下限推断(由于变量切换,我们在本例中有
V
=
IVisitor
):

  • 否则,通过检查以下情况是否适用来确定设置
    U1…Uk
    V1…Vk
    • V
      是同一秩的数组类型
      V1[…]
      U
      是数组类型
      U1[…]
      (或其有效基类型为
      U1[…]
      )的类型参数
    • V
      IEnumerable
      ICollection
      IList
      之一,
      U
      是一维数组类型
      U1[]
      (或有效基类型为
      U1[]的类型参数
    • V
      是一个构造的类、结构、接口或委托类型
      C
      ,并且有一个唯一的类型
      C
      ,使得
      U
      (或者,如果
      U
      是一个类型参数,则其有效基类或其有效接口集的任何成员)与相同、继承(直接或间接)或实现(直接或间接)
      C
(“唯一性”限制意味着在接口
C{}类U:C,C{}
的情况下,从
U
推断到
C
时不进行任何推断,因为
U1
可以是
X
Y

我们可以跳过前两种情况,因为它们显然不适用,第三种情况就是我们遇到的情况。编译器试图找到一个唯一的类型
C
,由
CountVisitor
实现并找到两个这样的类型,
public class Bar : IVisitable<Bar>
{
    public TResult Accept<TResult>(IVisitor<TResult, Bar> visitor) => visitor.Visit(this);
}
public class CountVisitor : IVisitor<int, Foo>, IVisitor<int, Bar>
{
    public int Visit(Foo visitable) => 42;
    public int Visit(Bar visitable) => 7;
}
var theFoo = new Foo();
int count = theFoo.Accept(new CountVisitor());
public class CountVisitor : IVisitor<int, IVisitable>
{
   public int Visit( IVisitable v )
   {
       dynamic d = v;
       Visit(d);
   }

    private int Visit( Foo f ) 
    {
        return 42;
    }

    private int Visit( Bar b )
    {
        return 7;
    }
}
int count = theFoo.Accept<int>(new CountVisitor());
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());
    }
}
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());