返回IList(不复制)的IList.Select()

本文关键字:IList Select 返回 复制 | 更新日期: 2023-09-27 18:27:21

我有一个带有IList<T>参数的方法。它是IList而不是IEnumerable,因为该方法需要快速随机访问,而且大多数条目根本不会被查询(该算法类似于二进制搜索),而IList似乎是唯一适合此操作的.NET接口。

public static int DoStuff<T>(System.Collections.Generic.IList<T> list)
{
  // ...
}

但现在我遇到了类似以下情况:

System.Tuple<int, int>[] originalList = { /* ... */ };
System.Collections.Generic.IList<int> list = originalList
    .Select(x => x.Item1)
    .ToList();

所需的值不直接在列表中,而是列表项的成员。上面的LINQ代码解决了这个问题,但有一个警告:整个列表都会被复制!我不想那样,因为名单可能很大。

如何在不复制的情况下执行这样的选择?有没有办法对返回IList而不是IEnumerable的IList执行Select

到目前为止我考虑的解决方案:

  1. 将选择器传递给DoStuff,并让它进行动态选择
  2. 编写一个包装器,它接受一个IList和一个选择器,实现IList,并在查询项时进行选择

我不喜欢(1),因为做选择不是DoStuff的工作。现在,(2)将是我的解决方案,但我想知道是否有更好的方法可以做到这一点,甚至可能是我忽略的内置方法。

返回IList(不复制)的IList.Select()

不,据我所知,框架中没有任何东西可以做到这一点。您的第二个选项对我来说似乎很明智。您需要将其设为只读,为IList<T>的变异操作抛出适当的异常。

如果您使用.NET 4+,一种替代方案是使用IReadOnlyList<T>,这将大大简化实现,因为它没有所有那些您可能会抛出异常的成员。它还将强制您的DoStuff方法在编译时只使用"读取"操作,而不是让它尝试更改列表,结果在执行时失败。

示例实现,完全未经测试:

public static class ListViewExtensions
{
    // Used to take advantage of type inference.
    public static ListView<TSource, TTarget> ToListView<TSource, TTarget>
        (this IList<TSource> source, Func<TSource, TTarget> selector)
    {
        return new ListView<TSource, TTarget>(source, selector);
    }
}
public sealed class ListView<TSource, TTarget> : IReadOnlyList<TTarget>
{
    // Or IReadOnlyList<TSource>... it's a shame that IList<T> doesn't
    // implement IReadOnlyList<T> :(
    private readonly IList<TSource> source;
    private readonly Func<TSource, TTarget> selector;
    public ListView(IList<TSource> source, Func<TSource, TTarget> selector)
    {
        // TODO: Nullity validation
        this.source = source;
        this.selector = selector;
    }
    public int Count { get { return source.Count; } }
    public TTarget this[int index]
    {
        get { return selector(source[index]); }
    }
    public IEnumerator<TTarget> GetEnumerator()
    {
        return source.Select(selector);
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}