88

I am having trouble remembering how (but not why) to use IEnumerators in C#. I am used to Java with its wonderful documentation that explains everything to beginners quite nicely. So please, bear with me.

I have tried learning from other answers on these boards to no avail. Rather than ask a generic question that has already been asked before, I have a specific example that would clarify things for me.

Suppose I have a method that needs to be passed an IEnumerable<String> object. All the method needs to do is concatenate the letters roxxors to the end of every String in the iterator. It then will return this new iterator (of course the original IEnumerable object is left as it was).

How would I go about this? The answer here should help many with basic questions about these objects in addition to me, of course.

Pooven
  • 1,744
  • 1
  • 25
  • 44
BlackVegetable
  • 12,594
  • 8
  • 50
  • 82
  • So, the idea behind using this is getting a modified copy of the original object(s)? Let´s say the original object was a `List`, with values from 1 to 10, and the new `IEnumerator` was added 1 to each item, then the new List has values from 2 to 11. Is it as simple as that? – carloswm85 Jul 13 '22 at 21:40

5 Answers5

121

Here is the documentation on IEnumerator. They are used to get the values of lists, where the length is not necessarily known ahead of time (even though it could be). The word comes from enumerate, which means "to count off or name one by one".

IEnumerator and IEnumerator<T> is provided by all IEnumerable and IEnumerable<T> interfaces (the latter providing both) in .NET via GetEnumerator(). This is important because the foreach statement is designed to work directly with enumerators through those interface methods.

So for example:

IEnumerator enumerator = enumerable.GetEnumerator();

while (enumerator.MoveNext())
{
    object item = enumerator.Current;
    // Perform logic on the item
}

Becomes:

foreach(object item in enumerable)
{
    // Perform logic on the item
}

As to your specific scenario, almost all collections in .NET implement IEnumerable. Because of that, you can do the following:

public IEnumerator Enumerate(IEnumerable enumerable)
{
    // List implements IEnumerable, but could be any collection.
    List<string> list = new List<string>(); 

    foreach(string value in enumerable)
    {
        list.Add(value + "roxxors");
    }
    return list.GetEnumerator();
}
Paul Walls
  • 5,884
  • 2
  • 22
  • 23
  • 1
    Reset() is not called by foreach. It's meant for COM interop, and not all enumerators support it (in fact most enumerators *don't* support it). – R. Martinho Fernandes Sep 05 '11 at 16:22
  • Enumerators are read-only, correct? How can I return a type of enumerator containing my modifications using such a pattern? – BlackVegetable Sep 05 '11 at 16:24
  • 9
    @BlackVegetable `IEnumerator` is read only, however, `IEnumerable` can usually be cast back to its mutable type (e.g. `((List)foo.ReadOnlyValues).Add("Hahaha! So much for read only sucker!")`) - so be careful when assuming that `IEnumerable` protects you from this. – Jonathan Dickinson Sep 05 '11 at 16:33
  • @JonathanDickinson Eh... actually, that undoes [all the benefits of using IEnumerable](https://en.wikipedia.org/wiki/Lazy_evaluation). The typical pattern is to return a new IEnumerable with the new values. This allows the client code to decide when to evaluate each element of the enumerable, instead of forcing them all to happen all at once. – jpaugh Aug 29 '19 at 20:53
  • 2
    Don't forget to `Dispose()` the IEnumerator! – Bip901 Mar 10 '21 at 14:40
  • @Bip901 And why I need to dispose it? – OmerS Jun 29 '23 at 13:13
  • `IEnumerator` is an interface. The underlying implementor of `IEnumerator` could do actions that require cleaning up, for example opening a file or a socket and returning values based on the read data. Calling `Dispose` provides the implementor an opportunity to close these files/sockets gracefully. (An implementor that *doesn't* require disposal would just run an empty code block `{}`). – Bip901 Jun 30 '23 at 08:32
19
public IEnumerable<string> Appender(IEnumerable<string> strings)
{
  List<string> myList = new List<string>();
  foreach(string str in strings)
  {
      myList.Add(str + "roxxors");
  }
  return myList;
}

or

public IEnumerable<string> Appender(IEnumerable<string> strings)
{
  foreach(string str in strings)
  {
      yield return str + "roxxors";
  }
}

using the yield construct, or simply

var newCollection = strings.Select(str => str + "roxxors"); //(*)

or

var newCollection = from str in strings select str + "roxxors"; //(**)

where the two latter use LINQ and (**) is just syntactic sugar for (*).

Rune
  • 8,340
  • 3
  • 34
  • 47
  • Interesting pattern with the LINQ. I'll have to research that. Thank you all for your (rapid) help! – BlackVegetable Sep 05 '11 at 16:25
  • I seem to recall that you should try not to return a List in a method declared as IEnumerable. The reason being that people may "cheat" and notice its actually a list and start doing list things with it. List (and indeed all IEnumerables) does have an AsEnumerable() method on it that I think should be called and returned. Otherwise top answer for the yield and particularly linq responses. :) – Chris Sep 05 '11 at 16:29
  • If you plan to do substantial amounts of development in C# you will definitely want to invest some time in LINQ - it will save you a lot of time and make you feel better about your code :-) – Rune Sep 05 '11 at 16:31
  • @chris: calling .AsEnumerable() doesn't do much: http://stackoverflow.com/questions/17968469/whats-the-differences-between-tolist-asenumerable-asqueryable :-) – Rune Dec 10 '14 at 13:30
  • 1
    @rune: That is a bit of a blast from the past. :) You are right that AsEnumerable method on IEnumerables does nothing useful. My reasoning is still sound(ish) but requires a different method to stop being able to cast back as a List (eg `.Selectg(x=>x)`). – Chris Dec 10 '14 at 14:02
10

If i understand you correctly then in c# the yield return compiler magic is all you need i think.

e.g.

IEnumerable<string> myMethod(IEnumerable<string> sequence)
{
    foreach(string item in sequence)
    {
         yield return item + "roxxors";
    }
}
Ben Robinson
  • 21,601
  • 5
  • 62
  • 79
  • So yield in this case will return a single (modified) String from the list each time it is run? Or will it return some sort of IEnumerable Object? – BlackVegetable Sep 05 '11 at 16:23
  • @BlackVegetable Yes, it will return an `IEnumerable` object that will build a string each time `MoveNext` is called on it (either directly or indirectly with foreach) – R. Martinho Fernandes Sep 05 '11 at 16:25
  • Yes, it is syntactic sugar, it tells the compiler to turn this into an iterator block. See here http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx – Ben Robinson Sep 05 '11 at 16:25
  • 1
    This is the right method and should go up. The accepted answer has to build the whole list before it returns, not following IEnumerable design. – Thibault D. Oct 12 '15 at 09:03
5

I'd do something like:

private IEnumerable<string> DoWork(IEnumerable<string> data)
{
    List<string> newData = new List<string>();
    foreach(string item in data)
    {
        newData.Add(item + "roxxors");
    }
    return newData;
}

Simple stuff :)

Mike
  • 661
  • 5
  • 10
  • This was my first thought. However, other methods that I interact with require me to return an IEnumerable type or somesuch. Thank you for the response though! – BlackVegetable Sep 05 '11 at 16:22
  • @BlackVegetable: `List` is an `IEnumerable`. But yes, there are better ways to do this (using `yield`). – R. Martinho Fernandes Sep 05 '11 at 16:24
  • 3
    I seem to recall that you should try not to return a List in a method declared as IEnumerable. The reason being that people may "cheat" and notice its actually a list and start doing list things with it. List (and indeed all IEnumerables) does have an AsEnumerable() method on it that I think should be called and returned. – Chris Sep 05 '11 at 16:28
4

Also you can use LINQ's Select Method:

var source = new[] { "Line 1", "Line 2" };

var result = source.Select(s => s + " roxxors");

Read more here Enumerable.Select Method

Slava Kovalchuk
  • 523
  • 4
  • 7