c# – 为什么无法推断泛型类型的类型参数?

默认情况

让我们假设以下示例性问题 – 我想创建一种方法,它将简单地输出任何List<>中的元素数量.采集.
我用一个方法创建了以下静态类:

public static class MyClass
{
    public static void MyMethod<T>(T obj) where T : List<int> // sort of pointless, yes
    {
        Console.WriteLine(obj.Count);
    }
}    

请注意,T是List< int>的子类.现在,我可以致电:

List<int> li = new List<int>();
MyClass.MyMethod<List<int>>(li);

现在,IDE告诉我“类型参数规范是多余的”.它可以从使用情况推断出类型:

List<int> li = new List<int>();
MyClass.MyMethod(li); // OK. li is List<int>, type argument is not required

一般情况

据你记忆,我想输出任何类型的List计数.像这样的东西会很棒:

public static void MyComplexMethod<T>(T obj) where T : List<any>
{
    Console.WriteLine(obj.Count);
}

但是,这是一种不正确的语法.我必须实现以下方法:

public static void MyComplexMethod<T1, T2>(T1 obj) where T1 : List<T2>
{
    Console.WriteLine(obj.Count);
}

现在,在不明确描述类型的情况下调用此方法会产生错误“无法从使用中推断出方法的类型参数”:

List<int> li = new List<int>();
MyClass.MyComplexMethod(li); // error
MyClass.MyComplexMethod<List<int>>(li); // error
MyClass.MyComplexMethod<List<int>, int>(li); // OK

MyClass.MyComplexMethod<List<double>, double>(new List<double>()); // OK
MyClass.MyComplexMethod<List<string>, string>(new List<string>()); // OK

// error. The type must be convertible in order to use...So, compiler knows it
MyClass.MyComplexMethod<List<string>, double>(new List<string>()); 

但是,对我来说,似乎这种类型应该可以使用.我提供List< int> – T1是List< int>显然,T2是int.为什么编译器不能这样做?实现理想行为的最合理方法是什么(T:List< any>)?

真实案例

如果有人想知道我为什么需要这个.实际上,当我尝试实现WCF代理包装器时,我偶然发现了这种情况,如下所示:

public static void Call<TServiceProxy, TServiceContract>(Action<TServiceProxy> action)
    where TServiceProxy : ClientBase<TServiceContract>, new()
    where TServiceContract : class
{
    TServiceProxy serviceProxy = new TServiceProxy();
    try
    {
        action(serviceProxy);
        serviceProxy.Close();
    }
    catch (Exception ex)
    {
        serviceProxy.Abort();
        // Log(ex);
        throw;
    }
}

Service.Call<EchoServiceClient>(x => {
    int v = DateTime.Now.ToString();
    x.Echo(v);
}); // not working

Service.Call<EchoServiceClient, IEchoService>(x => {
    int v = DateTime.Now.ToString();
    x.Echo(v);
}); // not convenient, pointless. EchoServiceClient inherits from ClientBase<IEchoService>

没有TServiceProxy:ClientBase< TServiceContract>我将无法执行serviceProxy.Abort().同样,TServiceProxy:ClientBase< any>这将是一个很好的解决方案,因为实际上TServiceContract并不重要 – 它只用于where约束.

最佳答案 您应该考虑您对该类型的实际要求.

在你的情况下,你想做什么?您希望能够在您在方法中创建的客户端上执行操作.该客户端是您作为泛型类型参数传递的类型.您是否需要知道它是ClientBase< something>为了执行动作?没有.

你还对这个对象做了什么?您可以打开和关闭频道.这些是ICommunicationObject确保的行动,ClientBase< T>实现.

这就是你的全部要求.所以你想要有以下约束:

>能够创建该类型的对象.
>实现ICommunicationObject的类型,以便您可以打开/关闭通道.

所以你的方法看起来像这样:

public static void Call<T>(Action<T> action)
    where T: ICommunicationObject, new()
{
    T serviceProxy = new T();
    try
    {
        action(serviceProxy);
        serviceProxy.Close();
    }
    catch (Exception ex)
    {
        serviceProxy.Abort();
        throw;
    }
}

最后,回答您关于编译器无法自动解决此问题的问题:如果您有泛型类型参数,则有两种可能性.编译器能够推断出所有类型参数,在这种情况下您可以将它们排除,或者编译器无法推断所有参数,在这种情况下您需要全部指定它们.毕竟Foo< X>()和Foo< X,Y>()是不同的方法签名,因此如果后者也允许Foo< X>(),则它将是不明确的.

至于为什么编译器无法在您的情况下推断所有类型参数,这仅仅是因为约束给出的类型参数之间的关系不会针对类型推断进行评估.

点赞