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

时间:2015-10-21 09:41:04

标签: c# .net generics inheritance

默认情况

让我们假设以下示例性问题 - 我希望创建一个方法,只输出任意List<>集合中的元素数量
我用一个方法创建了以下静态类:

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

请注意TList<int>的子类。现在,我可以致电:

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

现在,IDE告诉我&#34;类型参数规范是多余的&#34;。它可以根据用途推断出类型:

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);
}

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

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。为什么编译器不能这样做?实现理想行为的最合理方法是什么(where 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>

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

1 个答案:

答案 0 :(得分:2)

您应该考虑您对该类型的实际要求。

在你的情况下,你想做什么?您希望能够在您在方法中创建的客户端上执行action。该客户端是您作为泛型类型参数传递的类型。您是否需要知道它是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>(),那么它将是不明确的。

至于为什么编译器无法在你的情况下推断所有类型的参数,这只是因为约束给出的类型参数之间的关系不会被推断出类型推断。