【问题标题】:C# overload resolution with IList<T> and IReadOnlyList<T>使用 IList<T> 和 IReadOnlyList<T> 的 C# 重载解决方案
【发布时间】:2018-03-26 10:18:35
【问题描述】:

我有一个方法,我想在我的解决方案中获取所有类似列表的对象。在 .NET 4.5 之前,这很简单:

public static T Method<T>(IList<T> list)
{
    // elided
}

但是,.NET 4.5 引入了IReadOnlyList&lt;T&gt;,这个方法也应该适用于它。

我不能只将签名更改为采用IReadOnlyList&lt;T&gt;,因为有些地方我将该方法应用于专门键入为IList&lt;T&gt; 的内容。

该算法无法在 IEnumerable&lt;T&gt; 上运行,而且它使用得太频繁(并且对象太大)而无法在每次调用时获取 IEnumerable&lt;T&gt; 并创建一个新的 List&lt;T&gt;

我尝试过添加重载:

public static T Method<T>(IReadOnlyList<T> list)
{
    // elided
}

... 但这不会为实现两个接口(@98​​7654330@、List&lt;T&gt; 和许多其他类型)的任何东西编译,因为编译器无法确定使用哪种方法(特别烦人,因为他们有相同的身体,所以没关系)。

我不想添加采用T[]List&lt;T&gt;Method 的重载,以及实现这两个接口的所有其他类型。

我应该如何做到这一点?

【问题讨论】:

  • 使用IEnumerable&lt;T&gt;?或 ICollection&lt;T&gt; 如果您希望能够指定集合应该支持添加/删除操作
  • 除非你使用IEnumerable&lt;T&gt;
  • IList<T> and IReadOnlyList<T> 的可能副本 - 不是目标的精确重复,而是相同的潜在问题以及答案中的大量解决方法和想法。
  • 如果一切都在一个解决方案中,那就幸运了。在这种情况下,只需搜索替换并编译。修复所有错误。完成。
  • 您可以接受IEnumerable&lt;T&gt; 但检查传递的内容是否实现了IListIReadOnlyList 或您可能需要的任何其他接口。如果确实如此 - 使用IListIReadOnlyList 做任何你需要的事情。如果没有 - 从IEnumerable 构造新的List 并用它做你需要的事情。这样你就不会因为在不必要的时候从IEnumerable 构建新列表而受到性能损失。

标签: c# overloading overload-resolution


【解决方案1】:

这可能是实际检查运行时类型很有用的情况之一:

public static T Method<T>(IEnumerable<T> source)
{
    if (source is IList<T> list)
        return Method(list);

    if (source is IReadOnlyList<T> readOnly)
        return Method(readOnly);

    return Method(source.ToList() as IList<T>);
}

private static T Method<T>(IReadOnlyList<T> list) { ... }
private static T Method<T>(IList<T> list) { ... }

您仍然需要复制代码,因为您需要单独实现 IListIReadOnlyList,因为没有可以利用的通用接口,但您至少可以避免模棱两可的调用问题。

【讨论】:

    【解决方案2】:

    您可能最好的选择是进行全局搜索并将IList 替换为IReadOnlyList。如果没有编译器错误,那么你应该没问题。

    只有在使用 IList.Add 时才会收到编译器错误 - 无论如何,这很鲁莽,因为数组不支持 Add

    【讨论】:

    • 问题是IList&lt;T&gt;没有直接或间接实现IReadOnlyList&lt;T&gt;所以这个方案是无效的。 IList<T>
    • 是的,我知道 - 感谢@InBetween 提到它。但鉴于 OP 似乎使用的是内置类型(List&lt;T&gt;、数组等) - 他们 确实 实现了这两个接口。从更一般的意义上讲,您是对的 - 此解决方案不适用于 每个人,因为他们可能使用实现 IList 但不是 IReadOnlyList 的类型。尽管如此,即使在这些情况下,编译器也应该立即解决这个问题(因此我的回答中有compiler errors 评论)。
    • 啊..对不起。我错误地认为List&lt;T&gt; 没有实现IReadOnlyList&lt;T&gt;。是的,这可能是一个较小的邪恶,但这完全取决于它的使用方式。
    【解决方案3】:

    你能改变方法调用的代码吗? 如果您创建这样的方法会怎样:

        public static T1 Method<T1, T2>(T2 list) where T2 : IList<T1>, IReadOnlyList<T1>
        {
            return default(T1);
        }
    

    在这种情况下,调用如下所示:

    List<string> listA = new List<String>();
    ReadOnlyCollection<string> listB = listA.AsReadOnly();
    
    string outVar1 = Method<string, List<string>>(listA);
    string outVar2 = Method<string, ReadOnlyCollection<string>>(listB);
    

    以这种方式为 IList 和 IReadOnlyList 创建两个扩展方法的另一种方法:

        public static T Test<T>(this IList<T> source)
        {
            return default(T);
        }
    
        public static T Test<T>(this IReadOnlyList<T> source)
        {
            return default(T);
        }
    

    然后这样称呼他们:

        string outVar1 = (listA as IReadOnlyList<string>).Test();
        string outVar2 = (listB as IList<string>).Test();
    

    【讨论】:

      【解决方案4】:

      也许您最好的解决方案是研究为什么您的算法无法在 IEnumerable 上运行并进行更改。您是否正在使用 IList&lt;T&gt;IReadOnlyList&lt;T&gt; 特定成员,您可以将其替换为 IEnumerable&lt;T&gt; 中可用的成员?例如:

      // instead of
      int c = list.Count;
      
      // use
      int c = list.Count();
      

      编辑:忽略下面的废话。我离开它是为了让 cmets 继续有意义。

      您不应在任何类中同时实现IList&lt;T&gt;IReadOnlyList&lt;T&gt;IList 规范中唯一的附加成员用于写入列表。如果您的列表是只读的,则不需要这样做。我认为您需要更改任何实现这两者的类,以便在使用它们时可以选择正确的方法。

      但是,由于IReadOnlyList&lt;T&gt; 的所有成员都包含在IList&lt;T&gt; 中(以及从IReadOnlyCollection&lt;T&gt; 派生的成员),我想知道.Net 中的IList&lt;T&gt; 是否应该实际更改,以便它继承IReadOnlyList&lt;T&gt;接口而不是复制成员。现在对你没有帮助。

      【讨论】:

      • You should not implement both IList&lt;T&gt; and IReadOnlyList&lt;T&gt; in any class. List&lt;T&gt; 两者都实现了吗?
      • 是的,我无法想象为什么!
      • 有很多 MSFT 类同时实现了IList&lt;T&gt;IReadOnlyList&lt;T&gt;,包括T[]List&lt;T&gt;IReadOnlyList&lt;T&gt; 在引入时无法从 IList&lt;T&gt; 继承,因为那将是一个重大更改;见here
      • @SteveHarris “我想知道.Net 中的IList&lt;T&gt; 是否应该实际更改,以便它继承IReadOnlyList&lt;T&gt; 接口而不是复制成员。” -- Yes, but that was impossible without breaking backwards compatibility.
      • 我明白了。那是一件相当复杂的事情。所以真的,需要一台时间机器来解决这个问题。
      猜你喜欢
      • 2012-10-02
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 2012-05-28
      • 2017-06-10
      • 1970-01-01
      • 2018-01-05
      相关资源
      最近更新 更多