C#泛型方法类型参数不是从用法推断的
最近,我尝试了访问者模式的一个实现,我尝试用通用接口强制执行Accept&Visit方法: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); } 很好 现在,当我添加另一个可访问类型时,悲
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());