Simple IEnumerator use (with example)
Asked Answered
B

5

89

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.

Balkhash answered 5/9, 2011 at 16:10 Comment(1)
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<int>, 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?Relegate
M
123

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();
}
Maye answered 5/9, 2011 at 16:20 Comment(7)
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).Star
Enumerators are read-only, correct? How can I return a type of enumerator containing my modifications using such a pattern?Balkhash
@Balkhash IEnumerator is read only, however, IEnumerable can usually be cast back to its mutable type (e.g. ((List<string>)foo.ReadOnlyValues).Add("Hahaha! So much for read only sucker!")) - so be careful when assuming that IEnumerable protects you from this.Anjanetteanjela
@JonathanDickinson Eh... actually, that undoes all the benefits of using IEnumerable. 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.Moradabad
Don't forget to Dispose() the IEnumerator!Giule
@Giule And why I need to dispose it?Sulfanilamide
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 {}).Giule
S
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 (*).

Sedimentation answered 5/9, 2011 at 16:21 Comment(5)
Interesting pattern with the LINQ. I'll have to research that. Thank you all for your (rapid) help!Balkhash
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. :)Receptionist
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 :-)Sedimentation
@chris: calling .AsEnumerable() doesn't do much: #17968969 :-)Sedimentation
@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)).Receptionist
N
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";
    }
}
Nd answered 5/9, 2011 at 16:18 Comment(4)
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?Balkhash
@Balkhash 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)Star
Yes, it is syntactic sugar, it tells the compiler to turn this into an iterator block. See here msdn.microsoft.com/en-us/library/9k7k7cf0.aspxNd
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.Cele
C
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 :)

Conlin answered 5/9, 2011 at 16:15 Comment(3)
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!Balkhash
@BlackVegetable: List<T> is an IEnumerable<T>. But yes, there are better ways to do this (using yield).Star
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.Receptionist
M
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

Monogenetic answered 5/9, 2011 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.