为什么C#不推断我的generics?
我有很多有趣的(有趣的)通用的方法。 在大多数情况下,C#types推理是足够聪明的,以找出什么generics参数必须使用我的generics方法,但现在我有一个devise,C#编译器不成功,而我相信它可以成功find正确的types。
任何人都可以告诉我,在这种情况下,编译器是否有点愚蠢,还是有一个非常明确的理由,为什么它不能推断我的generics参数?
代码如下:
类和接口定义:
interface IQuery<TResult> { } interface IQueryProcessor { TResult Process<TQuery, TResult>(TQuery query) where TQuery : IQuery<TResult>; } class SomeQuery : IQuery<string> { }
一些不能编译的代码:
class Test { void Test(IQueryProcessor p) { var query = new SomeQuery(); // Does not compile :-( p.Process(query); // Must explicitly write all arguments p.Process<SomeQuery, string>(query); } }
为什么是这样? 我在这里错过了什么?
这里是编译器的错误信息(对我们的想象没有多大的影响):
方法IQueryProcessor.Process(TQuery)的types参数不能从用法中推断出来。 尝试明确指定types参数。
我相信C#应该能够推断出它的原因是因为:
- 我提供了一个实现
IQuery<TResult>
的对象。 - 只有types实现的
IQuery<TResult>
版本是IQuery<string>
,因此TResult必须是string
。 - 有了这个信息,编译器就有了TResult和TQuery。
解
对我来说,最好的解决scheme是更改IQueryProcessor
接口,并在实现中使用dynamictypes:
public interface IQueryProcessor { TResult Process<TResult>(IQuery<TResult> query); } // Implementation sealed class QueryProcessor : IQueryProcessor { private readonly Container container; public QueryProcessor(Container container) { this.container = container; } public TResult Process<TResult>(IQuery<TResult> query) { var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult)); dynamic handler = container.GetInstance(handlerType); return handler.Handle((dynamic)query); } }
IQueryProcessor
接口现在接受一个IQuery<TResult>
参数。 这样它可以返回一个TResult
,这将从消费者的angular度来解决问题。 我们需要在实现中使用reflection来获得实际的实现,因为需要具体的查询types(在我的情况下)。 但是,这里来dynamic打字的救援将为我们做反思。 你可以在这篇文章中阅读更多。
一群人指出,C#不会基于约束进行推理。 这是正确的,并与这个问题有关。 通过检查参数和相应的forms参数types来进行推论,这是推理信息的唯一来源。
一群人然后链接到这篇文章:
那篇文章既是过时的,也是与这个问题无关的。 它已经过时了,因为它描述了我们在C#3.0中做出的一个devise决定,然后我们在C#4.0中进行了逆转,主要基于对该文章的回应。 我刚刚为这篇文章添加了一个更新。
这是无关的,因为这篇文章是关于从方法组参数到generics委托forms参数的返回types推断 。 这不是原来的海报问的情况。
我的相关文章是这样写的:
C#不会根据generics方法的返回types来推断genericstypes,而只是generics方法的参数。
它也不使用约束作为types推断的一部分,从而消除了为您提供types的一般约束。
有关详细信息,请参阅Eric Lippert关于此主题的文章 。
它不使用约束来推断types。 相反,它推断types(如果可能),然后检查约束。
因此,虽然唯一可能的TResult
可以与SomeQuery
参数一起使用,但它不会看到这一点。
还要注意的是, SomeQuery
也完全可以实现IQuery<int>
,这是编译器的限制可能不是一个坏主意的原因之一。
这个规范很清楚地说明了这一点:
7.4.2节types推断
如果提供的参数数量不同于方法中的参数数量,则推理立即失败。 否则,假定generics方法具有以下签名:
Tr M(T1 x1 … Tm xm)
通过formsM(E1 … Em)的方法调用,types推断的任务是为每个types参数X1 … Xnfind唯一types参数S1 … Sn,使得调用M(E1 … Em)变得有效。
如您所见,返回types不用于types推断。 如果方法调用不直接映射到types参数,则推论立即失败。
编译器不只是假设你想把string
作为TResult
参数,也不可以。 想象一下从string派生的TResult
。 两者都是有效的,所以select哪个? 最好是明确的。