tags:

views:

1141

answers:

9

Does dot net have an interface like IEnumerable with a count property? I know about interfaces such as IList and ICollection which do offer a Count property but it seems like these interfaces were designed for mutable data structures first and use as a read only interface seems like an afterthought - the presence of an IsReadOnly field and mutators throwing exceptions when this property is true is IMO ample evidence for this.

For the time being I am using a custom interface called IReadOnlyCollection (see my own answer to this post) but I would be glad to know of other alternative approaches.

+2  A: 

IList can return IsReadOnly as true, which marks the collection as readonly. Other than that I'm afraid I don't know of anything fitting.

Lasse V. Karlsen
Yeah I just saw that but how do I change a List into an IList such that IsReadOnly is true?
SDX2000
You would typically return a wrapper object implementing IList or whatever you need, which internally references your original list.
Lasse V. Karlsen
You can use the List<T>.AsReadOnly method to return a readonly wrapper for your list.
Richard Szalay
The downside to wrapping and returning a readonly collection is that it won't get caught at compile time if some client code tries to add to it unless I am mistaken.
Daniel Auger
Yes you're right and this is why I decided to use make a new interface of my own (IReadOnlyCollection) please see my own answer to this question.
SDX2000
A: 

You can get .Count on IEnumerable with an extension method if you add a reference to System.Linq (in 3.5 anyway).

DeletedAccount
This is going to be an O(n) operation though, which might well not be what was wanted...
Will Dean
It's an O(n) operation if the underlying collection doesn't implement IList - but if it does, it just uses the IList.Count property.
Jon Skeet
@Jon thanks for the info but I do not want to expose an IList since I want the data to be readonly.
SDX2000
Thanks Jon, that's useful to know. Having had a look with Reflector, it's actually if it implements ICollection that the short-circuit occurs, but thanks for encouraging me to look, anyway.
Will Dean
+2  A: 

Since it's an interface, you would have to implement the Count property yourself, why don't you create a new interface that inherits IEnumerator and add a Count property?

Bruno
I agree IEnumeratorEx (see my own reply) is exactly what I want. IList/ICollection with half the methods throwing exceptions seems a bit unpalatable for a pro "strong typing" programmer like me.
SDX2000
Ok IEnumeratorEx is now IReadOnlyCollection.
SDX2000
+11  A: 

The key difference between the ICollection family and the IEnumerable family is the absence of certainty as to the count of items present (quite often the items will be generated/loaded/hydrated as needed) - in some cases, an Enumerable may not ever finish generating results, which is why the Count is missing.

Deriving and adding a Count is possible depending on your requirements, but it goes against this spirit, which is the purpose of ICollection - a collection of stuff that's all there.

Another way might be to use the System.Linq.Enumerable.Count method, i.e.

using System.Linq;

class X
{
   void Y(IEnumerable<int> collection)
   {
      int itemCount = collection.Count();
   }
}

or use the (System.Linq.Enumerable) .ToList() to pull all the items from the enumerator into a Collection and work from there.

(Also to answer your comment before having 50 rep:- the ".Count()" bit is a call to an extension method on the extension class System.Linq.Enumerable - the extension method is available on all things that derive from IEnumerable because the code has a "using System.Linq" which brings the extension methods in all classes in that namespace into scope - in this case its in the class Enumerable. If you're in VS, pressing F12 will bring you to the definition of S.L.Enumerable. BTW C# In Depth is a fantastic book for learning LINQ properly - its a page turner thats really helps you get the whole picture compared to learning the bits of LINQ piece by piece)

Ruben Bartelink
How does "itemCount = collection.Count()" work? IEnumerable<int> does not have a Count property.
SDX2000
No, it has a Count() extension methods (as of .NET 3.5). Count() either uses the Count property if the collection is an IList, or iterates through the whole collection and counts the elements otherwise.
Jon Skeet
@Ruben: Thanks for the vote of confidence in the book :)
Jon Skeet
@Jon: I'm just jealous of people who get to use their late mover advantage and learn C#2 and 3 via the book rather than fragmented blog posts from you, scottgu and a LINQ book (still recommend reading one like in Action, just read c# In Depth first)!
Ruben Bartelink
@Ruben: Just think of the pain they've been enduring by being forced to use C# 1 for all these years :)
Jon Skeet
Ruben Bartelink
+1 Hmm... good answer :-)
Dead account
Actually, It's just become a Nice Answer, ta :P
Ruben Bartelink
+1  A: 

IList or ICollection would be the way to go, if you want to use the standard interfaces.

Note that you can "hide" methods required by the interface if you don't want them in your class's public interface -- for example, since it's meaningless to add things to a readonly collection you can do this:

void ICollection<DataType>.Add(DataType item)
{
    throw new NotSupportedException();
}

public DataType this[int index]
{
    get { return InnerList[index]; }
}

DataType IList<DataType>.this[int index]
{
    get { return this[index]; }
    set { throw new NotSupportedException(); }
}

etc.

Miral
+1  A: 

Taking into consideration some of the comments I have decided to go with implementing a wrapper class implementing a custom interface...

interface IReadOnlyCollection<T> : IEnumerable<T>
{
    int Count { get; }
}

//Now cannot be misused by downcasting to List
// This wrapper can also be used with lists since IList inherits from ICollection
public class CollectionWrapper<T> : IReadOnlyCollection<T>
{

    public CollectionWrapper(ICollection<T> collection)
    {
        _collection = collection;
    }

    public int Count
    {
        get
        {
            return _collection.Count;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return (IEnumerator<T>)_collection.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return (IEnumerator)((IEnumerable)_collection).GetEnumerator();
    }

    ////////Private data///////
    ICollection<T> _collection;
}


class Program
{
    static void Main(string[] args)
    {
        List<int> list = new List<int>();

        list.Add(1);
        list.Add(2);
        list.Add(3);
        list.Add(4);

        CollectionWrapper<int> collection = new CollectionWrapper<int>(list);

        Console.WriteLine("Count:{0}", collection.Count);
        foreach (var x in collection)
        {
            Console.WriteLine(x);
        }

        foreach (var x in (IEnumerable)collection)
        {
            Console.WriteLine(x);
        }
    }
}

Thanks all for your suggestions.

Edit: Now cannot be misused by downcasting to List (or whatever).

SDX2000
IEnumerableEx - agh, what a name :)
mackenir
I am glad you liked it ;)
SDX2000
That doesn't work - the caller could just cast back to List<T> and modify the collection. You want a genuine wrapper - see my answer.
Jon Skeet
+1 for ReadOnlyCollection
SDX2000
@Jon though for now I am the sole owner for the code and I cant Imagine doing something so hideous (downcasting from IEnumeratorEx)
SDX2000
Whoops, there is one more problem is Count really good on IEnumerable? It seems like it should have been on IEnumerator. But I guess that is easier said than done.
SDX2000
Yes, it's appropriate on IEnumerable. IEnumerable represents the collection; IEnumerator represents a "cursor" on a collection.
Jon Skeet
I think you're wrong in assuming that a IEnumerable *represents* a collection. It does not need to, and that's why there's no a Count property on it.
Trap
Ok the thing is I have a collection but I want to expose the IEnumerable<T> and Count part only. I agree the name IEnumerableEx is misleading, may be IReadOnlyCollection would have been more appropriate.
SDX2000
@Trap: MSDN describes IEnumerable<T> as: "Exposes the enumerator, which supports a simple iteration over a collection of a specified type." Note the collection part. Not every logical collection can be easily counted though, hence the lack of Count property.
Jon Skeet
For instance, you can think of the sequence of bytes in a network stream as a collection - but it's a collection which is discovered as it's iterated through, so you wouldn't want to have to have a Count property on the IEnumerable.
Jon Skeet
A: 

An array can be cast to an IList, which makes the IList ReadOnly == true :)

Sekhat
And that would be nice if he/she had an array :)
leppie
+3  A: 

It sounds like you really just want ReadOnlyCollection<T> - expose it as IList<T>, but by wrapping the original list like this you just get a read-only wrapper with an appropriate count.

Jon Skeet
Question: Will the compiler catch something trying to add to that collection?
Daniel Auger
@Daniel: Not if you expose it as IList<T>. If you expose it as ReadOnlyCollection<T> the compiler will pick it up; ROC<T> implements IList<T>.Add explicitly.
Jon Skeet
A: 

As Jon Skeet mentions, you're much better off using System.Collections.ObjectModel.ReadOnlyCollection instead of creating your own wrapper class.

Then you can implement your sample as follows:

class Program {
    static void Main(string[] args) {
        List<int> list = new List<int>();
        list.Add(1);
        list.Add(2);
        list.Add(3);
        list.Add(4);

        ReadOnlyCollection<int> collection = new ReadOnlyCollection<int>(list);
        Console.WriteLine("Count:{0}", collection.Count);
        foreach (var x in collection) {
            Console.WriteLine(x);
        }
        foreach (var x in (IEnumerable)collection) {
            Console.WriteLine(x);
        }
    }
}
Justin Van Patten