为什么C#不推断我的泛型类型?

Ste*_*ven 64 .net c# type-inference c#-4.0

我使用泛型方法有很多Funcy乐趣(有趣).在大多数情况下,C#类型推断足够聪明,可以找出它必须在我的泛型方法上使用的泛型参数,但现在我有一个C#编译器不成功的设计,而我相信它可以成功找到正确的类型.

在这种情况下,有人能告诉我编译器是否有点愚蠢,还是有一个非常清楚的原因导致它无法推断我的泛型参数?

这是代码:

类和接口定义:

interface IQuery<TResult> { }

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

class SomeQuery : IQuery<string>
{
}
Run Code Online (Sandbox Code Playgroud)

一些不编译的代码:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么是这样?我在这里错过了什么?

这是编译器错误消息(它不会给我们留下太多想象):

无法从用法中推断出方法IQueryProcessor.Process(TQuery)的类型参数.尝试显式指定类型参数.

我认为C#应该能够推断它的原因是由于以下原因:

  1. 我提供了一个实现的对象IQuery<TResult>.
  2. 只有IQuery<TResult>类型实现的版本才是IQuery<string>TResult必须的string.
  3. 有了这些信息,编译器就有了TResult和TQuery.

对我来说,最好的解决方案是更改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);
    }
}
Run Code Online (Sandbox Code Playgroud)

IQueryProcessor接口现在只需在一个IQuery<TResult>参数.这样它就可以返回一个TResult,这将从消费者的角度解决问题.我们需要在实现中使用反射来获得实际的实现,因为需要具体的查询类型(在我的例子中).但是这里有动态打字救援,这将为我们做反思.您可以在本文中阅读有关此内容的更多信息.

Eri*_*ert 54

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

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

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-in​​ference-does-not-work-on-member-groups.aspx

那篇文章既过时又与问题无关.它已经过时了,因为它描述了我们在C#3.0中做出的设计决策,然后我们在C#4.0中进行了反转,主要是基于对该文章的响应.我刚刚在文章中添加了这种效果的更新.

这是无关紧要的,因为该文章是关于从方法组参数到泛型委托形式参数的返回类型推断.这不是原始海报所要求的情况.

我要阅读的相关文章是这样的:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

更新:我听到有消息称C#7.3略微改变了施加约束的规则,使得上述十年之久的文章不再准确.当我有空的时候,我会回顾一下我以前的同事所做的改变,看看是否值得在我的新博客上发布更正; 在此之前,请谨慎使用,看看C#7.3在实践中的作用.

  • 谢谢你的回答(答案).C#编译器是否有可能在将来进行约束推理,或者这是不可能的? (3认同)
  • @EricLippert:考虑到您对 C# 7.3 的更新,我非常有兴趣阅读有关此主题的更多内容。 (2认同)

Ree*_*sey 15

C#不会基于泛型方法的返回类型推断泛型类型,只会推断方法的参数.

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

有关详细信息,请参阅Eric Lippert关于此主题的帖子.


Jon*_*nna 11

它不使用约束来推断类型.相反,它推断类型(如果可能),然后检查约束.

因此,虽然唯一可能TResultSomeQuery参数一起使用,但它不会看到这一点.

还要注意,完全可以SomeQuery实现IQuery<int>,这是为什么这对编译器的限制可能不是一个坏主意的一个原因.


Tho*_*rin 7

我不会再讨论为什么,我不幻想能够比埃里克·利珀特(Eric Lippert)做出更好的解释。

但是,有一个解决方案不需要对方法调用进行后期绑定或额外参数。然而它并不是非常直观,所以我将让读者来决定它是否是一个改进。

首先,修改IQuery以使其自引用:

public interface IQuery<TQuery, TResult> where TQuery: IQuery<TQuery, TResult>
{
}
Run Code Online (Sandbox Code Playgroud)

你的IQueryProcessor看起来像这样:

public interface IQueryProcessor
{
    Task<TResult> ProcessAsync<TQuery, TResult>(IQuery<TQuery, TResult> query)
        where TQuery: IQuery<TQuery, TResult>;
}
Run Code Online (Sandbox Code Playgroud)

实际的查询类型:

public class MyQuery: IQuery<MyQuery, MyResult>
{
    // Neccessary query parameters
}
Run Code Online (Sandbox Code Playgroud)

处理器的实现可能如下所示:

public Task<TResult> ProcessAsync<TQuery, TResult>(IQuery<TQuery, TResult> query)
    where TQuery: IQuery<TQuery, TResult>
{
    var handler = serviceProvider.Resolve<QueryHandler<TQuery, TResult>>();
    // etc.
}
Run Code Online (Sandbox Code Playgroud)


Ed *_* S. 5

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

7.4.2 类型推断

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

Tr M(T1 x1 … Tm xm)

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

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

编译器不只是假设您想要string作为TResult参数,也不能。想象一个TResult从字符串派生的。两者都是有效的,那么选择哪个?最好是明确的。

  • 还要注意第一个突出显示的语句是一个错误;当我们添加命名和可选参数时,这一行从未更新,并且也与适用于其扩展形式的“params”方法的类型推断不一致。规范的未来版本将更正该行以将这些考虑在内。 (5认同)

gun*_*171 1

这最初是在问题中发布的,并代表 OP 移至此处。

对我来说,最好的解决方案是更改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);
    }
}
Run Code Online (Sandbox Code Playgroud)

IQueryProcessor接口现在接受一个IQuery<TResult>参数。这样就可以返回a TResult,这就从消费者的角度解决了问题。我们需要在实现中使用反射来获得实际的实现,因为需要具体的查询类型(在我的例子中)。但是动态类型来拯救我们了,它将为我们进行反射。您可以在本文中阅读更多相关内容。