我通过通用方法获得了很多Funcy乐趣(有趣)。在大多数情况下,C#类型推断足够聪明,可以找出它必须在我的泛型方法中使用的泛型参数,但现在我有一个C#编译器不成功的设计,而我相信它可以成功找到正确的类型。
在这种情况下,有人能告诉我编译器是否有点愚蠢,还是有一个非常明确的原因导致它无法推断我的泛型参数?
以下是代码:
类和接口定义:
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)的类型参数。尝试指定 明确地输入参数。
我认为C#应该能够推断它的原因是:
IQuery<TResult>
。IQuery<TResult>
版本只有IQuery<string>
,因此TResult必须为string
。解
对我来说,最好的解决方案是更改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);
}
}
IQueryProcessor
界面现在接受IQuery<TResult>
参数。这样它就可以返回TResult
,这将从消费者的角度解决问题。我们需要在实现中使用反射来获得实际的实现,因为需要具体的查询类型(在我的例子中)。但是这里有动态打字救援,这将为我们做反思。您可以在此article中了解详情。
答案 0 :(得分:51)
许多人指出C#不会根据约束做出推断。这是正确的,与问题相关。通过检查参数及其相应的形式参数类型进行推断,这是推理信息的唯一来源。
然后有很多人链接到这篇文章:
那篇文章既过时又与问题无关。它已经过时了,因为它描述了我们在C#3.0中做出的设计决策,然后我们在C#4.0中进行了反转,主要是基于对该文章的响应。我刚刚在文章中添加了这种效果的更新。
这是无关紧要的,因为该文章是关于从方法组参数到泛型委托形式参数的返回类型推断。这不是原始海报所要求的情况。
我要阅读的相关文章就是这一篇:
更新:我听到有消息称C#7.3略微改变了施加约束的规则,使上述十年之久的文章不再准确。当我有空的时候,我会回顾一下我以前的同事所做的改变,看看是否值得在我的新博客上发布更正;在此之前,请谨慎使用,看看C#7.3在实践中的作用。
答案 1 :(得分:15)
C#不会根据泛型方法的返回类型推断泛型类型,只会推断方法的参数。
它也不使用约束作为类型推断的一部分,这消除了为您提供类型的通用约束。
有关详细信息,请参阅Eric Lippert's post on the subject。
答案 2 :(得分:11)
它不使用约束来推断类型。相反,它推断类型(如果可能),然后检查约束。
因此,虽然唯一可能与TResult
参数一起使用的SomeQuery
,但它不会看到这一点。
另请注意,SomeQuery
完全可以实现IQuery<int>
,这也是编译器限制的一个原因可能不是一个坏主意。
答案 3 :(得分:4)
该规范非常清楚地说明了这一点:
第7.4.2节类型推断
如果提供的参数数量与方法中的参数数量不同,则推断会立即失败。否则,假设泛型方法具有以下签名:
Tr M(T1 x1 ... Tm xm)
通过形式M(E1 ... Em)的方法调用,类型推断的任务是为每个类型参数X1 ... Xn找到唯一类型参数S1 ... Sn,以便调用M(E1 ... Em)变得有效。
如您所见,返回类型不用于类型推断。如果方法调用没有直接映射到类型参数推断,则会立即失败。
编译器不仅假设您希望string
作为TResult
参数,也不能。想象一下从字符串派生的TResult
。两者都有效,哪有选择?最好是明确的。
答案 4 :(得分:2)
为什么得到了很好的回答,但是还有另一种解决方案。我经常遇到相同的问题,但是dynamic
或任何使用反射或分配数据的解决方案在我看来都是不可能的(喜欢电子游戏...)
因此,我改为将返回值作为out
参数传递,然后正确推断出该参数。
interface IQueryProcessor
{
void Process<TQuery, TResult>(TQuery query, out TResult result)
where TQuery : IQuery<TResult>;
}
class Test
{
void Test(IQueryProcessor p)
{
var query = new SomeQuery();
// Instead of
// string result = p.Process<SomeQuery, string>(query);
// You write
string result;
p.Process(query, out result);
}
}
我能想到的唯一缺点是它禁止使用'var'。
答案 5 :(得分:1)
我不会再解释为什么了,我没有比埃里克·利珀特(Eric Lippert)能够做出更好解释的幻想。
但是,有一种解决方案不需要后期绑定或额外的参数来调用方法。但是,它不是超级直观,因此我将它留给读者来决定它是否有所改进。
首先,修改$ ionic cordova run android --prod
> ionic-app-scripts build --prod --platform android
[10:08:45] ionic-app-scripts 3.2.2
[10:08:45] build prod started ...
[10:08:45] clean started ...
[10:08:45] clean finished in 24 ms
[10:08:45] copy started ...
[10:08:45] deeplinks started ...
[10:08:45] deeplinks finished in 84 ms
[10:08:45] ngc started ...
[10:08:47] ionic-app-script task: "build"
[10:08:47] Error: Cannot find module '@angular/compiler-cli/ngtools2'
Error: Cannot find module '@angular/compiler-cli/ngtools2'
at Function.Module._resolveFilename (module.js:547:15)
at Function.Module._load (module.js:474:25)
at Module.require (module.js:596:17)
at require (internal/module.js:11:18)
at C:\Users\User\AppData\Roaming\npm\node_modules\@ionic\app-scripts\dist\aot\aot-compiler.js:206:90
at <anonymous>
[ERROR] An error occurred while running subprocess ionic-app-scripts.
以使其自引用:
IQuery
您的public interface IQuery<TQuery, TResult> where TQuery: IQuery<TQuery, TResult>
{
}
如下所示:
IQueryProcessor
实际查询类型:
public interface IQueryProcessor
{
Task<TResult> ProcessAsync<TQuery, TResult>(IQuery<TQuery, TResult> query)
where TQuery: IQuery<TQuery, TResult>;
}
处理器的实现可能类似于:
public class MyQuery: IQuery<MyQuery, MyResult>
{
// Neccessary query parameters
}
答案 6 :(得分:0)
此问题的另一个解决方法是为类型解析添加其他参数。 例如,我们可以添加以下扩展名:
static class QueryProcessorExtension
{
public static TResult Process<TQuery, TResult>(
this IQueryProcessor processor, TQuery query,
//Additional parameter for TQuery -> IQuery<TResult> type resolution:
Func<TQuery, IQuery<TResult>> typeResolver)
where TQuery : IQuery<TResult>
{
return processor.Process<TQuery, TResult>(query);
}
}
现在我们可以按以下方式使用此扩展名:
void Test(IQueryProcessor p)
{
var query = new SomeQuery();
//You can now call it like this:
p.Process(query, x => x);
//Instead of
p.Process<SomeQuery, string>(query);
}
远非理想,但比显式提供类型要好得多。
P.S。 dotnet资源库中此问题的相关链接:
答案 7 :(得分:0)
我知道这是一个非常古老的线程,我不知道以下代码段在十年前是否有效(out var
肯定不是),但今天它正在编译:
interface IQuery<TQuery, TResult>
{
}
interface IQueryProcessor
{
void Process<TQuery, TResult>(IQuery<TQuery, TResult> query, out TResult result)
where TQuery : IQuery<TQuery, TResult>;
TResult ProcessAndReturn<TQuery, TResult>(IQuery<TQuery, TResult> query)
where TQuery : IQuery<TQuery, TResult>;
}
class SampleQueryResult
{
}
class SampleQuery : IQuery<SampleQuery, SampleQueryResult>
{
}
class Program
{
static void Main(string[] args)
{
IQueryProcessor qp = null; // get it from di ?
qp.Process(new SampleQuery(), out var r1);
var r2 = qp.ProcessAndReturn(new SampleQuery());
SampleQueryResult r;
r = r1;
r = r2;
}
}
所以今天,无需显式设置 TResult
类型以使用 Process
方法。