【问题标题】:Why type arguments cannot be inferred for generic types?为什么不能为泛型类型推断类型参数?
【发布时间】:2015-10-21 09:41:03
【问题描述】:

默认情况

让我们假设以下示例性问题 - 我想创建一个方法,该方法将简单地输出任何 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&lt;int&gt; 的子类。现在,我可以打电话了:

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

一般情况

据你所知,我想输出 any 类型的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&lt;int&gt; - T1 是List&lt;int&gt; 而T2 是int,显然。为什么编译器不能做到这一点?实现理想行为的最合理方法是什么 (where T : List&lt;any&gt;)?

真实案例

如果有人只是想知道我为什么需要这个。实际上,当我尝试实现 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&lt;TServiceContract&gt;,我将无法做到serviceProxy.Abort()。同样,where TServiceProxy : ClientBase&lt;any&gt; 将是一个很好的解决方案,因为实际上 TServiceContract 并不重要 - 它仅用于 where 约束。

【问题讨论】:

  • 你可以使用一个接口然后扩展类型,然后你可以像ClientBase&lt;SomInterface&gt;那样对接口进行约束
  • 使用IList 而不是List&lt;any&gt;
  • @M.kazemAkhgary 太好了,谢谢!对于List&lt;&gt;,问题已解决。真实案例呢?我还没有找到像IClientBase 这样的东西。
  • 你试过了吗? public static void Call&lt;TServiceContract&gt;(Action&lt;ClientBase&lt;TServiceContract&gt;&gt; action) where TServiceContract : class
  • @Henrik 那么,我将无法实例化TServiceProxy proxy = new TSeviceProxy();

标签: c# .net generics inheritance


【解决方案1】:

您应该考虑一下您对该类型的实际要求是什么。

在你的情况下,你想做什么?您希望能够在您在该方法中创建的客户端上执行action。该客户端属于您作为泛型类型参数传递的类型。您是否需要知道它是 ClientBase&lt;something&gt; 才能执行操作?没有。

你还对这个对象做了什么?您打开和关闭通道。这些是ICommunicationObject 确保的操作,ClientBase&lt;T&gt; 实现了这些操作。

这就是您的所有要求。所以你希望有以下约束:

  • 能够创建该类型的对象。
  • 实现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;
    }
}

最后,回答您关于为什么编译器无法自动解决此问题的问题:如果您有泛型类型参数,则有两种可能性。编译器能够推断出 all 类型的参数,在这种情况下您可以将它们排除在外,或者编译器无法推断出所有参数,在这种情况下您需要全部指定它们。毕竟Foo&lt;X&gt;()Foo&lt;X, Y&gt;() 是不同的方法签名,所以如果后者也允许Foo&lt;X&gt;(),那就是模棱两可了。

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

【讨论】:

  • 非常感谢您的全面回答!在你解释后听起来很合乎逻辑。 ICommunicationObject 的解决方案也很有效:)
  • 似乎接口在泛型方法中很有用。好点子。 @YeldarKurmangaliyev
猜你喜欢
  • 2014-02-04
  • 1970-01-01
  • 2012-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多