46

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

  • Count有财产_
  • 有一个整数索引器(仅获取)

这个参数的类型应该是什么?我会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
}

编辑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>and IReadOnlyList<T>List<T>类也是如此。这使得很难找到一个实现IList<T>但不实现的类IReadOnlyList<T>

4

4 回答 4

33

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

但是,您可以使用 LINQ:

  • 扩展方法在Count()内部检查实例是否实际上是一个集合,然后使用该Count属性。
  • 扩展方法在ElementAt()内部检查实例是否实际上是一个列表,然后使用索引器。
于 2012-10-11T11:19:05.583 回答
5

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

如果您决定重用代码是首要任务,那么您可以让这些重载将调用转发给以 Daniel 建议的方式private接受IEnumerable<T>和使用 LINQ 的方法,实际上让 LINQ 在运行时进行规范化。

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

于 2012-10-11T11:26:08.093 回答
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;
}

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

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

您的帮助建议是最有用的,但我认为您应该翻转它;鉴于该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);
}
于 2012-10-11T15:33:56.217 回答
-1

您需要的是IReadOnlyCollection<T>.Net 4.5 中的可用内容,它本质上是IEnumerable<T>具有Count属性的,但如果您也需要索引,那么您也需要IReadOnlyList<T>提供索引器。

我不了解你,但我认为这个界面是必不可少的,它已经丢失了很长时间。

于 2014-06-28T22:28:33.487 回答