Posted By: dpratt71 | Dec 8th, 2008 @ 10:23 AM
page 1 of 3
Comments: 56 | Views: 1410
I posted this to my nacent blog, but I'd probably reach a wider audience broadcasting to Mars, so...

I find myself wanting to use IEnumerable<> in my own API, but it's not very readable. I'd love it if MS stole an idea from themselves, and gave us some syntax sugar for this useful interface. In other words, instead of IEnumerable<Foo>, I'd like to see just Foo*. What do you think?
W3bbo
W3bbo
The Master of Baiters
Foo* would be a pointer to a Foo Wink

Anyway, I'm not sure if I totally understand what you're aiming for. 'foreach' is the syntactical sugar around IEnumerable since it manages the whole while(collection.GetEnumerator().MoveNext()) thing for you.
W3bbo
W3bbo
The Master of Baiters
Java uses the elipse in a similar way to C#'s 'param's keyword to represent variable-parameter functions, Mathcad uses the semicolon, there's a whole load of unassigned punctuation that could be used.

But I''m weary of adding in too many language features, otherwise it becomes the anthesis of what the language was in the beginning; like how Firefox is now the bloated browser it. I propose C# adopts a 'modular' approach like XHTML or CSS (this would require a compiler rearchitecture though, but it would release it from inevitable bloat).

As for your problem, it reminds me of Delphi's support for indexer properties (similar to C#'s this[], but named).

Minh
Minh
WOOH! WOOH!
I'm all for clearer intention... so what if we add new keywords? The compiler is supposed to work for us...

I want syntatic sugar for this...

private void RunMe()
{
if (!InvokeRequired)
{
myLabel.Text = "You pushed the button!";
}
else
{
Invoke(new ThreadStart(RunMe));
}
}

1.  There's other "readonly collection" types.  ReadOnlyCollection<T> for instance.  It's not always appropriate to expose IEnumerable only because you want a "readonly collection" type.

2.  IEnumerable<Foo> isn't the ugly syntax you want to make it out to be.  Foo* or Foo+ may be shorter, but they are also less meaningful.  I think it was at least a little questionable that we added the Foo? syntax, when this is nothing but sugar for Nullable<Foo>.  My gut tells me it's even more questionable to do the same in this case.  Obviously that's only an opinion.
W3bbo
W3bbo
The Master of Baiters
ReadOnlyCollection implements IList<T>'s members only explicitly, so they are inaccessible when the ROC is not cast as an IList<T>, and even then every mutator operation throws an exception.

ROC is meant to be a Decorator wrapper around another list (which can be modified). I think ROCs work best with C#'s auto-implemented properties:

class PublicFacingCollection : ReadOnlyCollection<Object> { // I often alias generic types like this for readability and semantics
}

class Foo {

public PublicFacingCollection TheCollection { get; private set; }

private List<Object> underlyingCollection;

public Foo() {

underlyingCollection = new List<Object>();

TheCollection = new PublicFacingCollection(underlyingCollection); // the PublicFacingCollection instance no-longer needs to be dealt with by the Foo owner class. The underlyingCollection can be modified and the changes shown up in the TheCollection property instantly. Consumers of the Foo type do not see any mutator methods of the ROC as they are implemented explicitly.

}

}
CannotResolveSymbol
CannotResolveSymbol
{insert caption here}
I'm assuming you mean like this:

class Foo {
public IEnumerable<Object> TheCollection { get; private set; }<br>private List<Object> underlyingCollection;<br><br>public Foo() {<br>underlyingCollection = new List<Object>();<br>TheCollection = (IEnumerable<Object>) underlyingCollection; <br>}<br>}

In this case, your collection isn't actually read-only.  A user of your library can cast TheCollection back to List<Object> and modify the contents of the list, which is bad in most situations (since you're directly accessing a private data member).
TommyCarlier
TommyCarlier
I want my scalps!
One thing I sometimes miss is an interface between IEnumerable<T> and ICollection<T> that represents a read-only collection (implements IEnumerable<T>) with a Count-property. Something like this:
public interface IReadOnlyCollection<T> : IEnumerable<T>

{

    int Count { get; }

}
stevo_
stevo_
Human after all
If it supports count would it not also support indexed based access? and I agree.. personally readonlycollection makes me cringe.. I don't like exposing it, and it can only ever then act as runtime protection.. I would love some readonly interfaces for data structures.. and perhaps even some immutable data structure interfaces and implementations (surely the implementations already exist in f# for example).
TommyCarlier
TommyCarlier
I want my scalps!

No it wouldn't have to support index-based access. Just look at ICollection<T>: you can enumerate it (via IEnumerable<T>), it has a Count-property, but also an Add(T)-method and some additional members (like Remove() and IsReadOnly). Yet it doesn't have index-based access. That's what IList<T> is for.

Now: IEnumerable<T> « ICollection<T> « IList<T>
What I'd like: IEnumerable<T> « ICountEnumerable<T> « ICollection<T> « IList<T>

There's lots you lose with IEnumerable<T>.  Count and random access are the most obvious.  LINQ sort of gives this back to you.  The Count() method will give you the count, and is optimized to try and cast the IEnumerable<T> to an ICollection<T> in order to get the count without having to iterate over the collection.  However, I believe that is bad design.  If the underlying collection is changed to no longer be an ICollection<T> the complexity of Count() has radically changed.  Since the public interface only exposed IEnumerable<T>, I have to expect that sort of behavior.

The ReadOnlyCollection types aren't ideal.  I'd much prefer it if the collection designs had started with IReadOnlyCollection and ICollection had derived from there.  IOW, I share your consternation about the existence of non-readonly methods that throw exceptions.  That said, however, we have what we have, and in practice there's seldom if ever an issue with the IsReadOnly/exception design of ReadOnlyCollection types.
evildictaitor
evildictaitor
if( !succeed( try() ) ) { while(true) try(); }
Be aware that the reason Count and Random access are not provided in IEnumerable<> is quite deliberate - whereas random access and counting in a List<T> is fast, random access and counting in a LinkedList<> is not - furthermore, IEnumerable<T> allows some interesting properties - for example there's no reason why an IEnumerable<T> should actually ever terminate, and it's certainly not nessisarilly the case that it should know if (or when) it's going to terminate when it starts.

For example, if you're reading through a series of lines in a text file, there's a good argument to say that you only need to read them one at a time. Finding out how many lines there are requires you to read the entire file into memory (and the file may be big), but reading each line into memory one at a time does not require you to read all of the file into memory. Thus by using IEnumerable<T> rather than a Collection<T>, some benefit has been achieved.

Another example is when tokenising a HTML stream to be rendered - if we were to insist on knowing how many elements there were before we started, then we would need to start parsing after the entire page contents has returned. If on the other hand we do not need to do this (by using an IEnumerable<T> rather than an ICollection<T>) we can start processing the page BEFORE the entire thing has been downloaded - allowing for a speedup of the effective render time for the page.

One of the things that slightly concerns me with some of the comments on this page is people noting that if you have a List<T> and you expose this as an IEnumerable<T> that this can (in principle) be cast back to a List<T> and modified - but as soon as I see this I think "that sentiment is certainly true - but if a programmer is willing to forego safe programming practises, is that not his/her fault when it all goes wrong?". Certainly in my code I expect that when I expose an IEnumerable<T> that either myself or any other programmer will never try to cast it back to a List<T> - if they need random access they can use the constructor of List<T> that takes an IEnumerable<T>, but according to the contract that the method gives, it does not say that the IEnumerable<T> is castable to a List<T>, and to assume that it can be cast back is an unsafe assumption - if later I change the implementation to a LinkedList<T> then their code will break, even though the method signature will remain the same.

The question you need to then ask is WHY and WHO you are doing these optimisations for. If you're a component maker who's exposing these properties to a customer, then sure, go right ahead and protect these things with ReadOnlyCollections. If on the other hand you're programming to prevent yourself or your collegages from doing dodgy assumptions, then perhaps you and your collegagues need to have a sit down and discuss whether there is a better way of doing what they're doing.

Remember:

class {
 List<T> _val;
  public IEnumerable<T> {
  get { return _val; }
 }
}

has a computational complexity of 1 and memory overhead of 0.

class {
  List<T> _val;
  public IEnumerable<T> {
    get { return new ReadOnlyCollection(_val); }
  }
}

has a computational complexity and memory overhead of O(_val.Count) - and so you're slowing and bloating your program at the same time.
I always wonder about posts like this.  It's obvious from the post your replying to that I fully understand why IEnumerable<T> doesn't have a Count property or provide random access.  That kinda was the entire point of my post, don't ya think?
Oh, and after reading the entire post, you've missed several things here in any event.

The only one who talked about casting the IEnumerable<T> was myself.  Go read what I said carefully.  I said that LINQ was doing the cast as an optimization.  LINQ casts to an ICollection<T>, not to a List<T>.  This cast is safe and not dodgy.  What's dodgy is relying on the computational complexity of an IEnumerable<T> exposed by someone like the original poster, when calling such LINQ methods as Count() and ElementAt().  When using LINQ you must expect the worst case scenario, because you're dealing with an IEnumerable<T>.  It's just a happy bonus when it performs better.  That's why you shouldn't return an IEnumerable<T> just because you want a "read only" collection.

No one was suggesting you should wrap the collection in a ReadOnlyCollection that was returned to the user as an IEnumerable<T>.  Where you got that nonsense is beyond me.
evildictaitor
evildictaitor
if( !succeed( try() ) ) { while(true) try(); }
You're reading too much into the "reply to" box from C9. I was just commenting on the whole thread, not just replying to you.

My point about why Count isn't there was a response to TommyCarlier who explictly lamented the fact that it's not there by default on an IEnumerable<T>.

The optimisation you mention of casting to an ICollection<T> is fine, because it first checks that the cast is valid, and ensures that if the cast is valid that it doesn't mutate the collection - my point was more general and related to CannotResolveSymbol's post who noted that exposing IEnumerable<T> isn't a guarrantee that the inner property won't be mutated by the caller.

You say that noone was suggesting you wrap the collection in a ReadOnlyCollection, but W3bbo's code example does exactly that, so your point is invalid.

Please don't take the C9 "reply to" as nessisarrilly a refutation of what you said. I just clicked on the reply closest to the bottom of the screen, and I meant nothing by it.


Edit: I note actually that the order complexity of ReadOnlyCollection isn't O(_val.Count), but actually a minor overhead, since the ReadOnlyCollection defers all of the accesses of the list to the contained list and throws an exception on all mutation attempts, so it's not quite as bad as I made it sound.
I wouldn't have made the connection to TommyCarlier's post, because he only talks about Count, while you talk about both Count and random access.  It's also interesting to note, that thanks to LINQ, IEnumerable<T> now has both of the features you say were deliberately left out.  Yes, I understand the difference between the two designs, but you're glossing over the reasoning here.

W3bbo's code example didn't do what you did.  He didn't wrap the collection in a ROC just to expose it to the user as an IEnumerable<T>.  What he did was the most appropriate way to expose a collection that's supposed to be read-only.  He wrapped it in a ROC and exposed a ROC.  There are reasons to expose an IEnumerable<T>, as you pointed out with several examples like the file enumerator.  However, exposing a read-only collection isn't a reason for making it an IEnumerable<T>.
There are complexity guarantees documented on the various interfaces here.  Exposing an IEnumerable<T> means that a large number of operations consumers are going to want to perform are going to have O(N) complexity.  LINQ may "cheat" and give you better performance, but the complexity that consumers MUST assume is O(N).  Now, expose a ReadOnlyCollection<T>, and most of those same operations are now O(1).  A hypothetical IReadOnlyCollection would provide the same complexity guarantees you have with ReadOnlyCollection<T>, but without the existense of mutating methods, making for a "cleaner" design.  Again, though, in practice the current design is "good enough", and certainly preferable to IEnumerable<T>.
TommyCarlier
TommyCarlier
I want my scalps!

Actually, I didn't say (or mean) that I wanted IEnumerable<T> to have a Count-property: IEnumerable<T> is perfectly fine the way it is. I myself have recently writen a tokenizer that has a method ReadAll which returns an IEnumerable<T> that lazily reads from a TextReader and produces tokens. In this case, a Count-property would mean that all the tokens would have to be read before anything can be done.

What I want is that there was an interface IReadOnlyCollection<T> or ICountEnumerable<T> the my classes can expose in cases where my classes know the number of elements. I never indicated index-based random access. In fact, read-only index-based random access could also be exposed as an interface IRandomAccessEnumerable<T>. Adding these 2 additional interfaces would not break existing code (because the existing code would not know about them). The interface hierarchy would then look like this:

public interface IEnumerable<T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}

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

public interface IRandomAccessEnumerable<T> : ICountEnumerable<T>
{
T this[int index] { get; }
}

public interface ICollection<T> : ICountEnumerable<T>
{
void Add(T item);
void Clear();
bool Contains(T item);
void CopyTo(T[] array, int arrayIndex);
bool Remove(T item);

bool IsReadOnly { get; }
}

public interface IList<T> : ICollection<T>, IRandomAccessEnumerable<T>
{
int IndexOf(T item);
void Insert(int index, T item);
void RemoveAt(int index);

T this[int index] { get; set; }
}
TommyCarlier
TommyCarlier
I want my scalps!

Actually, now that I think about it further: it could be a good idea to move all the members of ICollection<T> and IList<T> that can be performed on read-only collections to interfaces IReadOnlyCollection<T> and IReadOnlyList<T>. It would allow for richer API's without additional overhead. The current collection classes would not have to be changed. The only thing that changes is the addition of these interfaces and the modification of ICollection<T> and IList<T> (so they implement IReadOnlyCollection<T> and IReadOnlyList<T>).

The interface hierarchy would look like this:
public interface IEnumerable<T> : IEnumerable
{
  IEnumerator<T> GetEnumerator();
}

public interface IReadOnlyCollection<T> : IEnumerable<T>
{
  int Count { get; }
  bool Contains(T item);
  void CopyTo(T[] array, int arrayIndex);
}

public interface IReadOnlyList<T> : IReadOnlyCollection<T>
{
  T this[int index] { get; }
  int IndexOf(T item);
}

public interface ICollection<T> : IReadOnlyCollection<T>
{
  void Add(T item);
  void Clear();
  bool Remove(T item);
  bool IsReadOnly { get; }
}

public interface IList<T> : ICollection<T>, IReadOnlyList<T>
{
  void Insert(int index, T item);
  void RemoveAt(int index);
  T this[int index] { get; set; }
}
page 1 of 3
Comments: 56 | Views: 1410
Microsoft Communities