Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I just read an interesting article called Getting too cute with c# yield return

It made me wonder what the best way is to detect whether an IEnumerable is an actual enumerable collection, or if it's a state machine generated with the yield keyword.

For example, you could modify DoubleXValue (from the article) to something like:

private void DoubleXValue(IEnumerable<Point> points)
{
    if(points is List<Point>)
      foreach (var point in points)
        point.X *= 2;
    else throw YouCantDoThatException();
}

Question 1) Is there a better way to do this?

Question 2) Is this something I should worry about when creating an API?

share|improve this question
1  
You should use an interface rather than a concrete and also cast lower down, ICollection<T> would be a better choice as not all collections are List<T>. For example, arrays Point[] implement IList<T> but are not List<T>. –  Trevor Pilley Jan 21 '13 at 16:33
2  
Welcome to the problem with mutable types. Ienumerable is implicitly supposed to be an immutable data type, so mutating implementations are violation of LSP. This article is a perfect explanation of the dangers of side effects, so practice writing your C# types to be as side effect free as possible and you never need worry about this. –  Jimmy Hoffa Jan 21 '13 at 17:42
1  
@JimmyHoffa IEnumerable is immutable in the sense that you can't modify a collection (add/remove) while enumerating it, however if the objects in the list are mutable, it's perfectly reasonable to modify those while enumerating the list. –  Trevor Pilley Jan 21 '13 at 18:02
    
@TrevorPilley I disagree. If the collection is expected to be immutable, the expectation from a consumer is that the members will not be mutated by outside actors, that would be called a side effect and violates the immutablility expectation. Violates POLA and implictly LSP. –  Jimmy Hoffa Jan 21 '13 at 18:07
    
How about using ToList? msdn.microsoft.com/en-us/library/bb342261.aspx It doesn't detect whether it was generated by yield, but instead it makes it irrelevant. –  luiscubal Jan 21 '13 at 19:17
add comment

3 Answers

up vote 20 down vote accepted

Your question, as I understand it, seems to be based on an incorrect premise. Let me see if I can reconstruct the reasoning:

  • The linked-to article describes how automatically-generated sequences exhibit a "lazy" behaviour, and shows how this can lead to a counter-intuitive result.
  • Therefore I can detect whether a given instance of IEnumerable is going to exhibit this lazy behaviour by checking to see if it is automatically generated.
  • How do I do that?

The problem is that the second premise is false. Even if you could detect whether or not a given IEnumerable was the result of an iterator block transformation (and yes, there are ways to do that) it wouldn't help because the assumption is wrong. Let's illustrate why.

class M { public int P { get; set; } }
class C
{
  public static IEnumerable<M> S1()
  {
    for (int i = 0; i < 3; ++i) 
      yield return new M { P = i };
  }

  private static M[] ems = new M[] 
  { new M { P = 0 }, new M { P = 1 }, new M { P = 2 } };
  public static IEnumerable<M> S2()
  {
    for (int i = 0; i < 3; ++i)
      yield return ems[i];
  }

  public static IEnumerable<M> S3()
  {
    return new M[] 
    { new M { P = 0 }, new M { P = 1 }, new M { P = 2 } };
  }

  private class X : IEnumerable<M>
  {
    public IEnumerator<X> GetEnumerator()
    {
      return new XEnum();
    }
    // Omitted: non generic version
    private class XEnum : IEnumerator<X>
    {
      int i = 0;
      M current;
      public bool MoveNext()
      {
        current = new M() { P = i; }
        i += 1;
        return true;
      }
      public M Current { get { return current; } }
      // Omitted: other stuff.
    }
  }

  public static IEnumerable<M> S4()
  {
    return new X();
  }

  public static void Add100(IEnumerable<M> items)
  {
    foreach(M item in items) item.P += 100;
  }
}

All right, we have four methods. S1 and S2 are automatically generated sequences; S3 and S4 are manually generated sequences. Now suppose we have:

var items = C.Sn(); // S1, S2, S3, S4
S.Add100(items);
Console.WriteLine(items.First().P);

The result for S1 and S4 will be 0; every time you enumerate the sequence, you get a fresh reference to an M created. The result for S2 and S3 will be 100; every time you enumerate the sequence, you get the same reference to M you got the last time. Whether the sequence code is automatically generated or not is orthogonal to the question of whether the objects enumerated have referential identity or not. Those two properties -- automatic generation and referential identity -- actually have nothing to do with each other. The article you linked to conflates them somewhat.

Unless a sequence provider is documented as always proffering up objects that have referential identity, it is unwise to assume that it does so.

share|improve this answer
2  
We all know you will have the correct answer here, that's a given; but if you could put a little more definition into the term "referential identity" it might be good, I am reading it as "immutable" but that's because I conflate immutable in C# with new object returned every get or value type, which I think is the same thing you're referring to. Though granted immutability can be had in a variety of other ways, it's the only rational way in C# (that I'm aware of, you may well know ways I'm unaware of). –  Jimmy Hoffa Jan 21 '13 at 19:14
2  
@JimmyHoffa: Two object references x and y have "referential identity" if Object.ReferenceEquals(x, y) returns true. –  Eric Lippert Jan 21 '13 at 19:31
    
Always nice to get a response straight from the source. Thanks Eric. –  ConditionRacer Jan 22 '13 at 3:01
add comment

I personally think the problem highlighted in that article stems from overuse of the IEnumerable interface by many C# developers these days. It seems to have become the default type when you're requiring a collection of objects - but there's a lot of information that IEnumerable simply doesn't tell you... crucially: whether or not it has been reified*.

If you find that you need to detect whether an IEnumerable is truly an IEnumerable or something else, the abstraction has leaked and you're probably in a situation where your parameter type is a little too loose. If you require the extra information that IEnumerable alone doesn't provide, make that requirement explicit by choosing one of the other interfaces such as ICollection or IList.

Edit for the downvoters Please go back and read the question carefully. The OP is asking for a way to ensure that IEnumerable instances have been materialized before being passed to his API method. My answer addresses that question. There is a wider issue regarding the proper way to use IEnumerable, and certainly the expectations of the code the OP pasted are based on a misunderstanding of that wider issue, but the OP did not ask how to correctly use IEnumerable, or how to work with immutable types. It's not fair to downvote me for not answering a question which was not posed.

* I use the term reify, others use stabilize or materialize. I don't think a common convention has been adopted by the community just yet.

share|improve this answer
2  
One problem with ICollection and IList is that they are mutable (or at least look that way). IReadOnlyCollection and IReadOnlyList from .Net 4.5 solve some of those problems. –  svick Jan 21 '13 at 16:49
    
Please explain the downvote? –  MattDavey Jan 21 '13 at 18:03
1  
I disagree that you should change the types you're using. Ienumerable is a great type and it can often have the benefit of immutability. I think the real problem is people don't understand how to work with immutable types in C#. Not surprising, it's an extremely stateful language, so it's a new concept to many C# devs. –  Jimmy Hoffa Jan 21 '13 at 18:19
    
Only ienumerable allows deferred execution, so disallowing it as a parameter removes one whole feature of C# –  Jimmy Hoffa Jan 21 '13 at 18:31
2  
I down voted it because I think it's wrong. I think that is in the spirit of SE, to ensure people don't put stock in incorrect information it is up to all of us to down vote those answers. Maybe I'm wrong, totally possible that I'm wrong and you're right. That's up to the broader community to decide by voting. Sorry you're taking it personally, if it's any consolation I've had many negative voted answers I've written and summarily deleted because I accept the community thinks it's wrong so I'm unlikely right. –  Jimmy Hoffa Jan 21 '13 at 19:17
show 5 more comments

I think the key concept is that you should treat any IEnumerable<T> collection as immutable: you shouldn't modify the objects from it, you should create a new collection with new objects:

private IEnumerable<Point> DoubleXValue(IEnumerable<Point> points)
{
    foreach (var point in points)
        yield return new Point(point.X * 2, point.Y);
}

(Or write the same more succinctly using LINQ Select().)

If you actually want to modify the items in the collection, don't use IEnumerable<T>, use IList<T> (or possibly IReadOnlyList<T> if you don't want to modify the collection itself, only items in it, and you're on .Net 4.5):

private void DoubleXValue(IList<Point> points)
{
    foreach (var point in points)
        point.X *= 2;
}

This way, if someone attempts to use your method with IEnumerable<T>, it will fail at compile time.

share|improve this answer
1  
The real key is like you said, return a new ienumerable with the modifications when you want to change something. Ienumerable as a type is perfectly viable and has no reason to be changed, it's just the technique for using it needs to be understood as you said. +1 for mentioning how to work with immutable types. –  Jimmy Hoffa Jan 21 '13 at 18:00
1  
Somewhat related - you should also treat every IEnumerable as if it is done via deferred execution. What may be true at the time you retrieve your IEnumerable reference may not be true when you actually enumerate through it. For example, returning a LINQ statement inside a lock can result in some interesting an unexpected results. –  Dan Lyons Jan 21 '13 at 18:22
    
@JimmyHoffa: I agree. I go a step further and try to avoid iterating over an IEnumerable more than once. The need to do so more than once can be obviated by calling ToList() and iterating over the list, though in the specific case shown by the OP I prefer svick's solution of having DoubleXValue also return an IEnumerable. Of course, in real code I would probably be using LINQ to generate this type of IEnumerable. However, svick's choice of yield makes sense in the context of the OP's question. –  Brian Jan 21 '13 at 20:27
    
@Brian Yeah, I didn't want to change the code too much to make my point. In real code, I would have used Select(). And regarding multiple iterations of IEnumerable: ReSharper is helpful with that, because it can warn you when you do that. –  svick Jan 21 '13 at 20:34
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.