C# IList<T> 和 IReadOnlyList<T>

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12838122/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 00:38:28  来源:igfitidea点击:

IList<T> and IReadOnlyList<T>

c#.netcollectionsinterface.net-4.5

提问by ?afak Gür

If I have a method that requires a parameter that,

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

  • Has a Countproperty
  • Has an integer indexer (get-only)
  • Count房产
  • 有一个整数索引器(仅限获取)

What should the type of this parameter be? I would choose IList<T>before .NET 4.5 since there was no other indexable collection interface for this and arrays implement it, which is a big plus.

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

But .NET 4.5 introduces the new IReadOnlyList<T>interface and I want my method to support that, too. How can I write this method to support both IList<T>and IReadOnlyList<T>without violating the basic principles like DRY?

但是 .NET 4.5 引入了新IReadOnlyList<T>接口,我希望我的方法也支持它。我如何编写这种方法来支持IList<T>并且IReadOnlyList<T>不违反 DRY 等基本原则?

Edit: Daniel's answer gave me some ideas:

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

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
}


Edit 2:Or I could just accept an IReadOnlyList<T>and provide a helper like this:

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

Then I could call it like Foo(list.AsReadOnly())

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



Edit 3:Arrays implement both IList<T>and IReadOnlyList<T>, so does the List<T>class. This makes it pretty rare to find a class that implements IList<T>but not IReadOnlyList<T>.

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

采纳答案by Daniel Hilgarth

You are out of luck here. IList<T>doesn't implement IReadOnlyList<T>. List<T>does implement both interfaces, but I think that's not what you want.

你在这里倒霉了。IList<T>不执行IReadOnlyList<T>List<T>确实实现了两个接口,但我认为这不是你想要的。

However, you can use LINQ:

但是,您可以使用 LINQ:

  • The Count()extension method internally checks whether the instance in fact is a collection and then uses the Countproperty.
  • The ElementAt()extension method internally checks whether the instance in fact is a list and than uses the indexer.
  • Count()扩展方法内部检查其实实例是否是一个集合,然后使用Count属性。
  • ElementAt()扩展方法内部检查其实实例是否是列表,比使用索引。

回答by Jon

Since IList<T>and IReadOnlyList<T>do not share any useful "ancestor", and if you don't want your method to accept any other type of parameter, the only thing you can do is provide two overloads.

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

If you decide that reusing codes is a top priority then you could have these overloads forward the call to a privatemethod that accepts IEnumerable<T>and uses LINQ in the manner Daniel suggests, in effect letting LINQ do the normalization at runtime.

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

However IMHO it would probably be better to just copy/paste the code once and just keep two independent overloads that differ on just the type of argument; I don't believe that micro-architecture of this scale offers anything tangible, and on the other hand it requires non-obvious maneuvers and is slower.

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

回答by casperOne

If you're more concerned with maintaining the principal of DRYover performance, you coulduse dynamic, like so:

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

However, I can't say in good faith that I'd recommend this as the pitfalls are too great:

但是,我不能真诚地说,我建议以此为陷阱实在是太大了:

  • Every call on collectionin DoInternalwill be resolved at run time. You lose type safety, compile-time checks, etc.
  • Performance degradation (while not severe, for the singular case, but can be when aggregated) willoccur
  • 每次调用collectioninDoInternal都将在运行时解决。你失去了类型安全、编译时检查等。
  • 性能下降(虽然不严重,对于单一情况,但在聚合可能发生)

Your helper suggestion is the most useful, but I think you should flip it around; given that the IReadOnlyList<T>interfacewas introduced in .NET 4.5, many API's don't have support for it, but have support for the IList<T>interface.

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

That said, you should create an AsListwrapper, which takes an IReadOnlyList<T>and returns a wrapper in an IList<T>implementation.

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

However, if you want to emphasize on your API that you are taking an IReadOnlyList<T>(to emphasize the fact that you aren't mutating the data), then the AsReadOnlyListextension that you have now would be more appropriate, but I'd make the following optimization to AsReadOnly:

但是,如果您想强调您正在使用的 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);
}

回答by MaYaN

What you need is the IReadOnlyCollection<T>available in .Net 4.5 which is essentially an IEnumerable<T>which has Countas the property but if you need indexing as well then you need IReadOnlyList<T>which would also give an indexer.

你需要的是IReadOnlyCollection<T>在.NET 4.5可用其本质上是一个IEnumerable<T>具有Count作为属性,但如果你需要索引以及那么你需要IReadOnlyList<T>这也将给出一个索引。

I don't know about you but I think this interface is a must have that had been missing for a very long time.

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