IList and IReadOnlyList

后端 未结 4 1958
小鲜肉
小鲜肉 2021-02-01 02:20

If I have a method that requires a parameter that,

  • Has a Count property
  • Has an integer indexer (get-only)

What should the type

相关标签:
4条回答
  • 2021-02-01 02:24

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

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

    0 讨论(0)
  • 2021-02-01 02:27

    If you're more concerned with maintaining the principal of DRY over performance, you could use dynamic, like so:

    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 collection in DoInternal will 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) will occur

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

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

    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 AsReadOnlyList extension that you have now would be more appropriate, but I'd make the following optimization to 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);
    }
    
    0 讨论(0)
  • 2021-02-01 02:28

    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.

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

    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.

    0 讨论(0)
  • 2021-02-01 02:40

    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.

    However, you can use LINQ:

    • The Count() extension method internally checks whether the instance in fact is a collection and then uses the Count property.
    • The ElementAt() extension method internally checks whether the instance in fact is a list and than uses the indexer.
    0 讨论(0)
提交回复
热议问题