Why doesn't C# infer my generic types?
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.
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.
The reason I believe C# should be able to infer it is because of the following:
IQuery<TResult>
. IQuery<TResult>
version that type implements is IQuery<string>
and thus TResult must be string
. SOLUTION
For me the best solution was to change the IQueryProcessor
interface and use dynamic typing in the implementation:
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.
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.
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.
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
C# will not infer generic types based on the return type of a generic method, only the arguments to the method.
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.
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.
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.
上一篇: 类型推理如何与重载泛型方法一起工作
下一篇: 为什么C#不推断我的泛型?