繁体   English   中英

清单 <T> 和IReadOnlyList <T>

[英]IList<T> and IReadOnlyList<T>

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

  • 具有Count属性
  • 具有整数索引器(仅获取)

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

但是.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
}

编辑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();
    }
}

然后我可以像Foo(list.AsReadOnly())这样称呼它


编辑3:数组同时实现IList<T>IReadOnlyList<T>List<T>类也是如此。 这使得很少找到实现IList<T>而不实现IReadOnlyList<T>

您真不走运。 IList<T>不实现IReadOnlyList<T> List<T>确实实现了两个接口,但我认为这不是您想要的。

但是,您可以使用LINQ:

  • Count()扩展方法在内部检查该实例是否实际上是一个集合,然后使用Count属性。
  • ElementAt()扩展方法在内部检查实例是否实际上是列表,然后使用索引器。

如果您更关心维护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;
}

但是,我不能真诚地说我建议这样做,因为陷阱太大了:

  • DoInternalcollection每个调用都将在运行时解决。 您会丢失类型安全性,编译时检查等。
  • 将会发生性能下降(虽然不严重,对于单个情况,但可能是聚合时)

您的帮手建议是最有用的,但我认为您应该将其翻转一下。 鉴于.NET 4.5中引入了IReadOnlyList<T>接口 ,因此许多API都不支持它,但支持IList<T>接口

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

但是,如果您想在API上强调要使用IReadOnlyList<T> (以强调您没有对数据进行变异的事实),那么现在拥有的AsReadOnlyList扩展会更合适,但我d对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);
}

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

如果您决定重用代码是头等大事,那么您可以让这些重载将调用转发给private方法,该private方法接受IEnumerable<T>并按照Daniel所建议的方式使用LINQ,实际上是让LINQ在运行时进行规范化。

但是恕我直言,最好只复制/粘贴一次代码,并保留两个独立的重载,这两个重载仅因参数的类型而异。 我不相信这种规模的微体系结构可以提供任何切实的东西,另一方面,它需要非显而易见的操作且速度较慢。

您需要的是.Net 4.5中可用的IReadOnlyCollection<T> ,它本质上是一个IEnumerable<T> ,具有Count作为属性,但是如果您还需要索引,则需要IReadOnlyList<T> ,它也将提供索引器。

我不了解您,但是我认为这个接口已经很长时间以来一直是必需的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM