为什么';tc#推断我的泛型类型?

为什么';tc#推断我的泛型类型?,c#,.net,c#-4.0,type-inference,C#,.net,C# 4.0,Type Inference,我对泛型方法有很多功能性的乐趣。在大多数情况下,C#类型推断足够聪明,可以找出它必须在我的泛型方法上使用哪些泛型参数,但现在我有了一个C#编译器无法成功的设计,而我相信它可以成功地找到正确的类型 有人能告诉我,在这种情况下,编译器是否有点笨,或者是否有一个非常明确的原因,为什么它不能推断出我的泛型参数 代码如下: 类和接口定义: interface IQuery<TResult> { } interface IQueryProcessor { TResult Process

我对泛型方法有很多功能性的乐趣。在大多数情况下,C#类型推断足够聪明,可以找出它必须在我的泛型方法上使用哪些泛型参数,但现在我有了一个C#编译器无法成功的设计,而我相信它可以成功地找到正确的类型

有人能告诉我,在这种情况下,编译器是否有点笨,或者是否有一个非常明确的原因,为什么它不能推断出我的泛型参数

代码如下:

类和接口定义:

interface IQuery<TResult> { }

interface IQueryProcessor
{
    TResult Process<TQuery, TResult>(TQuery query)
        where TQuery : IQuery<TResult>;
}

class SomeQuery : IQuery<string>
{
}
IQueryProcessor
接口现在接受一个
IQuery
参数。这样,它可以返回
TResult
,这将从消费者的角度解决问题。我们需要在实现中使用反射来获得实际的实现,因为需要具体的查询类型(在我的例子中)。但这里来了动态打字来拯救我们,这将为我们做反思。您可以在本文中了解更多信息。

C#不会根据泛型方法的返回类型推断泛型类型,只会根据该方法的参数推断泛型类型

它也不使用约束作为类型推断的一部分,这将消除泛型约束为您提供类型


有关详细信息,请参见。

它不使用约束来推断类型。而是推断类型(如果可能),然后检查约束

因此,虽然唯一可能的
TResult
可以与
SomeQuery
参数一起使用,但它不会看到这一点


另外请注意,
SomeQuery
也完全有可能实现
IQuery
,这就是为什么对编译器进行限制可能不是个坏主意的原因之一。

规范非常清楚地说明了这一点:

第7.4.2节类型推断

如果提供的参数数量与方法中的参数数量不同,则推理立即失败。否则,假设泛型方法具有以下签名:

trm(T1-x1…Tm-xm)

对于形式为M(E1…Em)的方法调用,类型推断的任务是为每个类型参数X1…Xn查找唯一的类型参数S1…Sn,以便调用M(E1…Em)变得有效。

如您所见,返回类型不用于类型推断。如果方法调用没有直接映射到类型参数,则推理立即失败


编译器不仅假定您想要
string
作为
TResult
参数,而且它也不能。想象一个从字符串派生的
TResult
。两者都是有效的,那么选择哪一个呢?最好是直截了当。

许多人已经指出,C#不会根据约束进行推断。这是正确的,与问题有关。推理是通过检查参数及其相应的形式参数类型进行的,这是推理信息的唯一来源

然后,一群人链接到这篇文章:

那篇文章既过时又与问题无关。它已经过时了,因为它描述了我们在C#3.0中做出的一个设计决策,然后我们在C#4.0中逆转了这个决策,主要是基于对那篇文章的回应。我刚刚在这篇文章中添加了一个更新

这与此无关,因为本文是关于从方法组参数到泛型委托形式参数的返回类型推断。这不是原始海报所问的情况

我要读的相关文章是:


更新:我听说C#7.3稍微改变了应用约束时的规则,这使得上述十年前的文章不再准确。当我有时间的时候,我会回顾我以前的同事们所做的改变,看看是否值得在我的新博客上发布一个更正;在那之前,请谨慎行事,看看C#7.3在实践中的作用。

原因已经得到了很好的回答,但还有一个替代解决方案。我经常面临同样的问题,但是
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);
    }
}
处理器接口 { 作废处理(TQuery查询、out TResult结果) 其中TQuery:IQuery; } 课堂测试 { 空隙试验(I/p) { var query=new SomeQuery(); //而不是 //字符串结果=p.Process(查询); //你写 字符串结果; p、 处理(查询、输出结果); } }
我能想到的唯一缺点是它禁止使用“var”。

此问题的另一个解决方法是为类型解析添加额外的参数。为了避免现有代码库中的更改,可以将此类参数添加到扩展方法中。例如,您可以添加以下扩展方法:

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);
    }
}
静态类queryprocessextension
{
公共静态TResult进程(
此IQueryProcessor处理器、TQuery查询、,
//TQuery->IQuery类型解析的附加参数:
Func类型解析程序)
问:伊奎里在哪里
{
返回处理器。处理(查询);
}
}
现在,我们可以按如下方式使用此扩展:

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);
}
无效测试(IQueryP)
{
var query=new SomeQuery();
//您现在可以这样称呼它:
p、 过程(查询,x=>x);
//而不是
p、 过程(查询);
}
这远非理想,但比显式提供类型要好得多

dotnet存储库中此问题的p.S.相关链接:


我不会再讨论为什么了,我没有幻想能比埃里克·利珀特做得更好

然而,有一种解决方案不需要l
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);
}
public interface IQuery<TQuery, TResult> where TQuery: IQuery<TQuery, TResult>
{
}
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
}
public Task<TResult> ProcessAsync<TQuery, TResult>(IQuery<TQuery, TResult> query)
    where TQuery: IQuery<TQuery, TResult>
{
    var handler = serviceProvider.Resolve<QueryHandler<TQuery, TResult>>();
    // etc.
}
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;
    }
}