I'm having lots of Funcy fun (fun intended) with generic methods. In most cases C# type inference is smart enough to find out what generic arguments it must use on my generic methods, but now I've got a design where the C# compiler doesn't succeed, while I believe it could have succeeded in finding the correct types.
我用一般的方法得到了很多简单的乐趣。在大多数情况下,c#类型推断足够聪明,可以找出它必须在我的泛型方法中使用哪些泛型参数,但是现在我有了c#编译器不能成功的设计,而我相信它可以成功地找到正确的类型。
Can anyone tell me whether the compiler is a bit dumb in this case, or is there a very clear reason why it can't infer my generic arguments?
有人能告诉我在这种情况下编译器是否有点笨吗?或者有一个非常清楚的原因为什么它不能推断我的泛型参数?
Here's the code:
这是代码:
Classes and interface definitions:
类和接口定义:
interface IQuery<TResult> { }
interface IQueryProcessor
{
TResult Process<TQuery, TResult>(TQuery query)
where TQuery : IQuery<TResult>;
}
class SomeQuery : IQuery<string>
{
}
Some code that does not compile:
一些不编译的代码:
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);
}
}
Why is this? What am I missing here?
这是为什么呢?我错过了什么?
Here's the compiler error message (it doesn't leave much to our imagination):
下面是编译器错误消息(它没有给我们留下太多的想象):
The type arguments for method IQueryProcessor.Process(TQuery) cannot be inferred from the usage. Try specifying the type arguments explicitly.
方法IQueryProcessor.Process(TQuery)的类型参数不能从用法中推断出来。尝试显式地指定类型参数。
The reason I believe C# should be able to infer it is because of the following:
我认为c#应该能够推断出它的原因是:
- I supply an object that implements
IQuery<TResult>
. -
我提供了一个实现IQuery
的对象。 - That only
IQuery<TResult>
version that type implements isIQuery<string>
and thus TResult must bestring
. -
类型实现的唯一IQuery
版本是IQuery ,因此TResult必须是string。 - With this information the compiler has TResult and TQuery.
- 有了这些信息,编译器就有了TResult和TQuery。
SOLUTION
解决方案
For me the best solution was to change the IQueryProcessor
interface and use dynamic typing in the implementation:
对我来说,最好的解决方案是改变IQueryProcessor接口,在实现中使用动态类型:
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);
}
}
The IQueryProcessor
interface now takes in a IQuery<TResult>
parameter. This way it can return a TResult
and this will solve the problems from the consumer's perspective. We need to use reflection in the implementation to get the actual implementation, since the concrete query types are needed (in my case). But here comes dynamic typing to the rescue which will do the reflection for us. You can read more about this in this article.
IQueryProcessor接口现在接受一个IQuery
4 个解决方案
#1
41
A bunch of people have pointed out that C# does not make inferences based on constraints. That is correct, and relevant to the question. Inferences are made by examining arguments and their corresponding formal parameter types and that is the only source of inference information.
一些人指出c#不基于约束进行推断。这是正确的,与问题相关。推理是通过检查参数及其相应的形式参数类型进行的,这是推理信息的唯一来源。
A bunch of people have then linked to this article:
一群人随后联系到这篇文章:
http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx
That article is both out-of-date and irrelevant to the question. It is out-of-date because it describes a design decision we made in C# 3.0 which we then reversed in C# 4.0, mostly based on the response to that article. I've just added an update to that effect to the article.
那篇文章既过时又与问题无关。它已经过时了,因为它描述了我们在c# 3.0中做出的设计决策,然后我们在c# 4.0中进行了反向操作,主要是基于对那篇文章的响应。我刚刚为这篇文章添加了一个更新。
It is irrelevant because the article is about return type inference from method group arguments to generic delegate formal parameters. That is not the situation the original poster asks about.
这是不相关的,因为本文是关于从方法组参数返回类型推断到泛型委托形式参数的。这不是最初的海报所要求的情况。
The relevant article of mine to read is rather this one:
我的相关文章是这样的:
http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx
#2
14
C# will not infer generic types based on the return type of a generic method, only the arguments to the method.
c#不会根据泛型方法的返回类型推断泛型类型,而只推断方法的参数。
It also doesn't use the constraints as part of the type inference, which eliminates the generic constraint from supplying the type for you.
它也不使用约束作为类型推断的一部分,它消除了为您提供类型的一般约束。
For details, see Eric Lippert's post on the subject.
有关细节,请参阅Eric Lippert关于这一主题的文章。
#3
8
It doesn't use constraints to infer types. Rather it infers types (when possible) and then checks constraints.
它不使用约束来推断类型。相反,它推断类型(如果可能的话),然后检查约束。
Therefore, while the only possible TResult
that could be used with a SomeQuery
parameter, it won't see this.
因此,虽然唯一可以与SomeQuery参数一起使用的TResult,但它不会看到这个。
Note also, that it would be perfectly possible for SomeQuery
to also implement IQuery<int>
, which is one reason why this is limitation on the compiler may not be a bad idea.
还要注意,SomeQuery也很可能实现IQuery
#4
4
The spec lays this out pretty clearly:
这个规范很清楚地说明了这一点:
Section 7.4.2 Type Inference
部分7.4.2类型推断
If the supplied number of arguments is different than the number of parameters in the method, then inference immediately fails. Otherwise, assume that the generic method has the following signature:
如果提供的参数数与方法中的参数数不同,则推理立即失败。否则,假设泛型方法具有以下签名:
Tr M(T1 x1 … Tm xm)
Tr M(T1 x1…Tm xm)
With a method call of the form M(E1 …Em) the task of type inference is to find unique type arguments S1…Sn for each of the type parameters X1…Xn so that the call M(E1…Em)becomes valid.
使用form M(E1…Em)的方法调用,类型推断的任务是为每个类型参数X1…Xn找到唯一的类型参数S1…Sn,以便调用M(E1…Em)变得有效。
As you can see, the return type is not used for type inference. If the method call does not map directly to the type arguments inference immediately fails.
如您所见,返回类型不用于类型推断。如果方法调用没有直接映射到类型参数,则立即失败。
The compiler does not just assume that you wanted string
as the TResult
argument, nor can it. Imagine a TResult
derived from string. Both would be valid, so which to choose? Better to be explicit.
编译器不仅假设您想要字符串作为TResult参数,它也不能。想象一下从弦中衍生出的颤音。两者都是有效的,那么选择哪一个呢?更好的明确。
#1
41
A bunch of people have pointed out that C# does not make inferences based on constraints. That is correct, and relevant to the question. Inferences are made by examining arguments and their corresponding formal parameter types and that is the only source of inference information.
一些人指出c#不基于约束进行推断。这是正确的,与问题相关。推理是通过检查参数及其相应的形式参数类型进行的,这是推理信息的唯一来源。
A bunch of people have then linked to this article:
一群人随后联系到这篇文章:
http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx
That article is both out-of-date and irrelevant to the question. It is out-of-date because it describes a design decision we made in C# 3.0 which we then reversed in C# 4.0, mostly based on the response to that article. I've just added an update to that effect to the article.
那篇文章既过时又与问题无关。它已经过时了,因为它描述了我们在c# 3.0中做出的设计决策,然后我们在c# 4.0中进行了反向操作,主要是基于对那篇文章的响应。我刚刚为这篇文章添加了一个更新。
It is irrelevant because the article is about return type inference from method group arguments to generic delegate formal parameters. That is not the situation the original poster asks about.
这是不相关的,因为本文是关于从方法组参数返回类型推断到泛型委托形式参数的。这不是最初的海报所要求的情况。
The relevant article of mine to read is rather this one:
我的相关文章是这样的:
http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx
#2
14
C# will not infer generic types based on the return type of a generic method, only the arguments to the method.
c#不会根据泛型方法的返回类型推断泛型类型,而只推断方法的参数。
It also doesn't use the constraints as part of the type inference, which eliminates the generic constraint from supplying the type for you.
它也不使用约束作为类型推断的一部分,它消除了为您提供类型的一般约束。
For details, see Eric Lippert's post on the subject.
有关细节,请参阅Eric Lippert关于这一主题的文章。
#3
8
It doesn't use constraints to infer types. Rather it infers types (when possible) and then checks constraints.
它不使用约束来推断类型。相反,它推断类型(如果可能的话),然后检查约束。
Therefore, while the only possible TResult
that could be used with a SomeQuery
parameter, it won't see this.
因此,虽然唯一可以与SomeQuery参数一起使用的TResult,但它不会看到这个。
Note also, that it would be perfectly possible for SomeQuery
to also implement IQuery<int>
, which is one reason why this is limitation on the compiler may not be a bad idea.
还要注意,SomeQuery也很可能实现IQuery
#4
4
The spec lays this out pretty clearly:
这个规范很清楚地说明了这一点:
Section 7.4.2 Type Inference
部分7.4.2类型推断
If the supplied number of arguments is different than the number of parameters in the method, then inference immediately fails. Otherwise, assume that the generic method has the following signature:
如果提供的参数数与方法中的参数数不同,则推理立即失败。否则,假设泛型方法具有以下签名:
Tr M(T1 x1 … Tm xm)
Tr M(T1 x1…Tm xm)
With a method call of the form M(E1 …Em) the task of type inference is to find unique type arguments S1…Sn for each of the type parameters X1…Xn so that the call M(E1…Em)becomes valid.
使用form M(E1…Em)的方法调用,类型推断的任务是为每个类型参数X1…Xn找到唯一的类型参数S1…Sn,以便调用M(E1…Em)变得有效。
As you can see, the return type is not used for type inference. If the method call does not map directly to the type arguments inference immediately fails.
如您所见,返回类型不用于类型推断。如果方法调用没有直接映射到类型参数,则立即失败。
The compiler does not just assume that you wanted string
as the TResult
argument, nor can it. Imagine a TResult
derived from string. Both would be valid, so which to choose? Better to be explicit.
编译器不仅假设您想要字符串作为TResult参数,它也不能。想象一下从弦中衍生出的颤音。两者都是有效的,那么选择哪一个呢?更好的明确。