IList <T>和IReadOnlyList <T>

Şaf*_*Gür 35 .net c# collections interface .net-4.5

如果我有一个需要参数的方法,

  • Count房产
  • 有一个整数索引器(get-only)

这个参数的类型应该是什么?我会IList<T>在.NET 4.5之前选择,因为没有其他可索引的集合接口,并且数组实现它,这是一个很大的优点.

但.NET 4.5引入了新的IReadOnlyList<T>界面,我也希望我的方法能够支持它.我如何编写这种方法来支持这两种方法,IList<T>IReadOnlyList<T>不是违反像DRY这样的基本原则?

编辑:丹尼尔的回答给了我一些想法:

public void Foo<T>(IList<T> list)
    => Foo(list, list.Count, (c, i) => c[i]);

public void Foo<T>(IReadOnlyList<T> list)
    => Foo(list, list.Count, (c, i) => c[i]);

private void Foo<TList, TItem>(
    TList list, int count, Func<TList, int, TItem> indexer)
    where TList : IEnumerable<TItem>
{
    // Stuff
}
Run Code Online (Sandbox Code Playgroud)

编辑2:或者我可以接受一个IReadOnlyList<T>并提供这样的帮助:

public static class CollectionEx
{
    public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> list)
    {
        if (list == null)
            throw new ArgumentNullException(nameof(list));

        return list as IReadOnlyList<T> ?? new ReadOnlyWrapper<T>(list);
    }

    private sealed class ReadOnlyWrapper<T> : IReadOnlyList<T>
    {
        private readonly IList<T> _list;

        public ReadOnlyWrapper(IList<T> list) => _list = list;

        public int Count => _list.Count;

        public T this[int index] => _list[index];

        public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我可以称之为 Foo(list.AsReadOnly())


编辑3: 数组实现两者,类IList<T>也是IReadOnlyList<T>如此List<T>.这使得很难找到一个实现IList<T>但不实现的类IReadOnlyList<T>.

Dan*_*rth 23

你在这里运气不好.IList<T>没有实现IReadOnlyList<T>.List<T>确实实现了两个接口,但我认为这不是你想要的.

但是,您可以使用LINQ:

  • Count()扩展方法内部检查其实实例是否是一个集合,然后使用Count属性.
  • ElementAt()扩展方法内部检查其实实例是否是列表,比使用索引.

  • @JeppeStigNielsen:虽然将来实现的任何合理的列表类型*都应该实现`IList <T>`和`IReadOnlyList <T>`,但是在`IReadOnlyList <之前编写了许多实现`IList <T>`的好类. T>`存在.我一直希望有一种方法可以使`IList <T>`从带有索引的getter的协变只读接口继承,运行时 - 如果需要 - 自动创建一个只读索引器这将包装读写一个,但由于没有这样的手段存在,现在将需要接受`IList <T>`或`IReadOnlyList <T>`. (3认同)
  • 因此,答案将是改变方法,使其接受IEnumerable &lt;T&gt;,该方法受实现IList &lt;T&gt;或IReadOnlyList &lt;T&gt;的任何对象的支持。 (2认同)
  • 如果只允许“IList&lt;T&gt;”和“IReadOnlyList&lt;T&gt;”作为输入很重要,我将创建两个公共重载,一个用于“IList&lt;T&gt;”,一个用于“IReadOnlyList&lt;T&gt;”,然后创建带有“IEnumerable&lt;T&gt;”的私有重载。否则我只会使用“IEnumerable&lt;T&gt;”创建一个公共版本。 (2认同)

Jon*_*Jon 5

由于IList<T>并且IReadOnlyList<T>不共享任何有用的“祖先”,并且如果您不希望您的方法接受任何其他类型的参数,那么您唯一可以做的就是提供两个重载。

如果您决定重用代码是最优先考虑的,那么您可以让这些重载将调用转发到一个private方法,IEnumerable<T>该方法以 Daniel 建议的方式接受和使用 LINQ,实际上是让 LINQ 在运行时进行规范化。

但是恕我直言,最好只复制/粘贴一次代码并保留两个仅在参数类型上不同的独立重载;我不相信这种规模的微架构提供任何有形的东西,另一方面,它需要不明显的操作并且速度较慢。

  • @DanielHilgarth:我相信“看到第三个副本就重构”。*无条件地*忽略复制/粘贴听起来不像是好的工程。 (4认同)
  • 我强烈不同意复制/粘贴,即使在这个级别上也是如此。 (3认同)
  • 唯一的区别是输入类型,其他一切都完全相同。使用复制和粘贴,您会发现一些细微的错误。您可能修复了一个版本,但忘记了另一个。我不明白为什么从一开始就阻止这样的事情不是好的工程。您允许相同代码的两个副本的方法有什么优势?我不这样做的方法有什么缺点? (3认同)
  • @DanielHilgarth:当然,但答案中提到的要求暗示它不会那么糟糕(仅使用“Count”和索引器你能得到多复杂?)。如果我们谈论 5 行代码,我会复制/粘贴和记录。否则,其他一些解决方案可能会更可取。我只是说完全禁止复制/粘贴不是一个好主意。 (2认同)

cas*_*One 5

如果您更关心维护DRY的主要性能而不是性能,则可以使用dynamic,如下所示:

public void Do<T>(IList<T> collection)
{
    DoInternal(collection, collection.Count, i => collection[i]);
}
public void Do<T>(IReadOnlyList<T> collection)
{
    DoInternal(collection, collection.Count, i => collection[i]);
}

private void DoInternal(dynamic collection, int count, Func<int, T> indexer)
{
    // Get the count.
    int count = collection.Count;
}
Run Code Online (Sandbox Code Playgroud)

但是,我不能真诚地说我会推荐这个,因为陷阱太大了:

  • 每次调用collectioninDoInternal都将在运行时解决。你失去了类型安全、编译时检查等。
  • 性能下降(虽然不严重,对于单一情况,但在聚合可能发生)

你的助手建议是最有用的,但我认为你应该把它翻过来;鉴于该IReadOnlyList<T>接口是在 .NET 4.5 中引入的,许多 API 不支持它,但支持该IList<T>接口

也就是说,您应该创建一个AsList包装器,它IReadOnlyList<T>IList<T>实现中接受并返回一个包装器。

但是,如果您想强调您正在使用的 API IReadOnlyList<T>(强调您没有改变数据的事实),那么AsReadOnlyList您现在拥有的扩展会更合适,但我会进行以下优化到AsReadOnly

public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> collection)
{
    if (collection == null)
        throw new ArgumentNullException("collection");

    // Type-sniff, no need to create a wrapper when collection
    // is an IReadOnlyList<T> *already*.
    IReadOnlyList<T> list = collection as IReadOnlyList<T>;

    // If not null, return that.
    if (list != null) return list;

    // Wrap.
    return new ReadOnlyWrapper<T>(collection);
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,没有必要遇到这些麻烦。只需使该方法采用`IReadOnlyList&lt;T&gt;`。任何实现 `IList&lt;T&gt;` 的类也将实现 `IReadOnlyList&lt;T&gt;`。这包括 BCL 类型,如 `T[]` 和 `List&lt;T&gt;`,并且应该包括您自己的列表类型(如果您已经编写了)。 (3认同)