You don't use IEnumerable "over" foreach. Implementing IEnumerable makes using foreachpossible.
When you write code like:
foreach (Foo bar in baz)
{
...
}
it's functionally equivalent to writing:
IEnumerator bat = baz.GetEnumerator();
while (bat.MoveNext())
{
Foo bar = (Foo)bat.Current;
...
}
By "functionally equivalent," I mean that's actually what the compiler turns the code into. You can't use foreach on baz in this example unlessbaz implements IEnumerable.
IEnumerable means that baz implements the method
IEnumerator GetEnumerator()
The IEnumerator object that this method returns must implement the methods
bool MoveNext()
and
Object Current()
The first method advances to the next object in the IEnumerable object that created the enumerator, returning false if it's done, and the second returns the current object.
Anything in .NET that you can iterate over implements IEnumerable. If you're building your own class, and it doesn't already inherit from a class that implements IEnumerable, you can make your class usable in foreach statements by implementing IEnumerable (and by creating an enumerator class that its new GetEnumerator method will return).
// Garage contains a set of Car objects.
public class Garage
{
private Car[] carArray = new Car[4];
// Fill with some Car objects upon startup.
public Garage()
{
carArray[0] = new Car("Rusty", 30);
carArray[1] = new Car("Clunker", 55);
carArray[2] = new Car("Zippy", 30);
carArray[3] = new Car("Fred", 30);
}
}
// This seems reasonable ...
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n");
Garage carLot = new Garage();
// Hand over each car in the collection?
foreach (Car c in carLot)
{
Console.WriteLine("{0} is going {1} MPH",
c.PetName, c.CurrentSpeed);
}
Console.ReadLine();
}
}
// This interface allows the caller to
// obtain a container's subitems.
public interface IEnumerator
{
bool MoveNext (); // Advance the internal position of the cursor.
object Current { get;} // Get the current item (read-only property).
void Reset (); // Reset the cursor before the first member.
}
// Manually work with IEnumerator.
IEnumerator i = carLot.GetEnumerator();
i.MoveNext();
Car myCar = (Car)i.Current;
Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);
To begin examining the process of implementing existing .NET interfaces, let’s first look at the role of IEnumerable and IEnumerator. Recall that C# supports a keyword named foreach that allows you to iterate over the contents of any array type:
// Iterate over an array of items.
int[] myArrayOfInts = {10, 20, 30, 40};
foreach(int i in myArrayOfInts)
{
Console.WriteLine(i);
}
While it might seem that only array types can make use of this construct, the truth of the matter is any type supporting a method named GetEnumerator() can be evaluated by the foreach construct.To illustrate, follow me!
Suppose we have a Garage class:
// Garage contains a set of Car objects.
public class Garage
{
private Car[] carArray = new Car[4];
// Fill with some Car objects upon startup.
public Garage()
{
carArray[0] = new Car("Rusty", 30);
carArray[1] = new Car("Clunker", 55);
carArray[2] = new Car("Zippy", 30);
carArray[3] = new Car("Fred", 30);
}
}
Ideally, it would be convenient to iterate over the Garage object’s subitems using the foreach construct, just like an array of data values:
// This seems reasonable ...
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n");
Garage carLot = new Garage();
// Hand over each car in the collection?
foreach (Car c in carLot)
{
Console.WriteLine("{0} is going {1} MPH",
c.PetName, c.CurrentSpeed);
}
Console.ReadLine();
}
}
Sadly, the compiler informs you that the Garage class does not implement a method named GetEnumerator(). This method is formalized by the IEnumerable interface, which is found lurking within the System.Collections namespace. Classes or structures that support this behavior advertise that they are able to expose contained subitems to the caller (in this example, the foreach keyword itself). Here is the definition of this standard .NET interface:
// This interface informs the caller
// that the object's subitems can be enumerated.
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
As you can see, the GetEnumerator() method returns a reference to yet another interface named System.Collections.IEnumerator. This interface provides the infrastructure to allow the caller to traverse the internal objects contained by the IEnumerable-compatible container:
// This interface allows the caller to
// obtain a container's subitems.
public interface IEnumerator
{
bool MoveNext (); // Advance the internal position of the cursor.
object Current { get;} // Get the current item (read-only property).
void Reset (); // Reset the cursor before the first member.
}
If you want to update the Garage type to support these interfaces, you could take the long road and implement each method manually. While you are certainly free to provide customized versions of GetEnumerator(), MoveNext(), Current, and Reset(), there is a simpler way. As the System.Array type (as well as many other collection classes) already implements IEnumerable and IEnumerator, you can simply delegate the request to the System.Array as follows:
using System.Collections;
...
public class Garage : IEnumerable
{
// System.Array already implements IEnumerator!
private Car[] carArray = new Car[4];
public Garage()
{
carArray[0] = new Car("FeeFee", 200);
carArray[1] = new Car("Clunker", 90);
carArray[2] = new Car("Zippy", 30);
carArray[3] = new Car("Fred", 30);
}
public IEnumerator GetEnumerator()
{
// Return the array object's IEnumerator.
return carArray.GetEnumerator();
}
}
After you have updated your Garage type, you can safely use the type within the C# foreach construct. Furthermore, given that the GetEnumerator() method has been defined publicly, the object user could also interact with the IEnumerator type:
// Manually work with IEnumerator.
IEnumerator i = carLot.GetEnumerator();
i.MoveNext();
Car myCar = (Car)i.Current;
Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);
However, if you prefer to hide the functionality of IEnumerable from the object level, simply make use of explicit interface implementation:
By doing so, the casual object user will not find the Garage’s GetEnumerator() method, while the foreach construct will obtain the interface in the background when necessary.
foreach (Passenger passenger in Plane)
// the airline hostess is now at the front of the plane
// and slowly making her way towards the back
// when she get to a particular passenger she gets some information
// about the passenger and then immediately heads to the cabin
// to let the captain decide what to do with it
{ // <---------- Note the curly bracket that is here.
// we are now cockpit of the plane with the captain.
// the captain wants to give the passenger free
// champaign if they support manchester city
if (passenger.supports_mancestercity())
{
passenger.getFreeChampaign();
}
else
{
// you get nothing! GOOD DAY SIR!
}
} // <---- Note the curly bracket that is here!
// the hostess has delivered the information
// to the captain and goes to the next person
// on the plane (if she has not reached the
// end of the plane)
Analogy: Imagine you are a detective on an aeroplane . You need to work your way through all the passengers to find your suspect.
An aeroplane can only do this, if it is:
countable, and
if it has a counter.
What does countable mean?
If an airline is "countable", this means that there MUST be a flight attendant present on the plane, whose sole job is to count:
The counter/flight attendant MUST start before the first passenger 2. (i.e. the flight attendant) MUST "move next" up the aisle to the first seat.
He/she is to then record: (i) who the person is in the seat, and (ii) their current location in the aisle.
The counter keeps going till he reaches the end of the plane.
Let's tie this with the IEnumerables
foreach (Passenger passenger in Plane)
// the airline hostess is now at the front of the plane
// and slowly making her way towards the back
// when she get to a particular passenger she gets some information
// about the passenger and then immediately heads to the cabin
// to let the captain decide what to do with it
{ // <---------- Note the curly bracket that is here.
// we are now cockpit of the plane with the captain.
// the captain wants to give the passenger free
// champaign if they support manchester city
if (passenger.supports_mancestercity())
{
passenger.getFreeChampaign();
}
else
{
// you get nothing! GOOD DAY SIR!
}
} // <---- Note the curly bracket that is here!
// the hostess has delivered the information
// to the captain and goes to the next person
// on the plane (if she has not reached the
// end of the plane)
Summary
In other words, something is countable if it has a counter. And counter must (basically): (i) remember its place (state), (ii) be able to move next, (iii) and know about the current person he is dealing with.
IEnumerable implements GetEnumerator. When called, that method will return an IEnumerator which implements MoveNext, Reset and Current.
Thus when your class implements IEnumerable, you are saying that you can call a method (GetEnumerator) and get a new object returned (an IEnumerator) you can use in a loop such as foreach.
Implementing IEnumerable enables you to get an IEnumerator for a list.
IEnumerator allows foreach style sequential access to the items in the list, using the yield keyword.
Before foreach implementation (in Java 1.4, for example), the way to iterate a list was to get an enumerator from the list, then ask it for the "next" item in the list, for as long as the value returned as the next item is not null. Foreach simply does that implicitly as a language feature, in the same way that lock() implements the Monitor class behind the scenes.
I expect foreach works on lists because they implement IEnumerable.
IEnumerable is the most common interface you would see in the majority of the code out there. It enables the foreach loop, generators (think yield) and because of its tiny interface, it's used to create tight abstractions. IEnumerable depends on IEnumerator.
IEnumerator, on the other hand, provides a slightly lower level iteration interface. It's referred to as the explicit iterator which gives the programmer more control over the iteration cycle.
IEnumerable
IEnumerable is a standard interface that enables iterating over collections that supports it (in fact, all collection types I can think of today implements IEnumerable). Compiler support allows language features like foreach. In general terms, it enables this implicit iterator implementation.
foreach Loop
foreach (var value in list)
Console.WriteLine(value);
I think foreach loop is one of the main reasons for using IEnumerable interfaces. foreach has a very succinct syntax and very easy to understand compared to classic C style for loops where you need to check the various variables to see what it was doing.
yield Keyword
Probably a lesser known feature is that IEnumerable also enables generators in C# with the use of yield return and yield break statements.
IEnumerable<Thing> GetThings() {
if (isNotReady) yield break;
while (thereIsMore)
yield return GetOneMoreThing();
}
Abstractions
Another common scenario in practice is using IEnumerable to provide minimalistic abstractions. Because it is a minuscule and read-only interface, you are encouraged to expose your collections as IEnumerable (rather than List for example). That way you are free to change your implementation without breaking your client's code (change List to a LinkedList for instance).
Gotcha
One behaviour to be aware of is that in streaming implementations (e.g. retrieving data row by row from a database, instead of loading all the results in memory first) you cannot iterate over the collection more than once. This is in contrast to in-memory collections like List, where you can iterate multiple times without problems. ReSharper, for example, has a code inspection for Possible multiple enumeration of IEnumerable.
IEnumerator
IEnumerator, on the other hand, is the behind the scenes interface which makes IEnumerble-foreach-magic work. Strictly speaking, it enables explicit iterators.
var iter = list.GetEnumerator();
while (iter.MoveNext())
Console.WriteLine(iter.Current);
In my experience IEnumerator is rarely used in common scenarios due to its more verbose syntax and slightly confusing semantics (at least to me; e.g. MoveNext() returns a value as well, which the name doesn't suggest at all).
Use case for IEnumerator
I only used IEnumerator in particular (slightly lower level) libraries and frameworks where I was providing IEnumerable interfaces. One example is a data stream processing library which provided series of objects in a foreach loop even though behind the scenes data was collected using various file streams and serialisations.
Client code
foreach(var item in feed.GetItems())
Console.WriteLine(item);
IEnumerator 有一个名为 Current 的属性和两个方法:Reset() 和 MoveNext()(这对于了解列表中项目的当前位置很有用)。
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
Differences between IEnumerable and IEnumerator :
IEnumerable uses IEnumerator internally.
IEnumerable doesn't know which item/object is executing.
Whenever we pass IEnumerator to another function, it knows the current position of item/object.
Whenever we pass an IEnumerable collection to another function, it doesn't know the current position of item/object (doesn't know which item its executing)
IEnumerable have one method GetEnumerator()
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
IEnumerator has one property called Current and two methods, Reset() and MoveNext() (which is useful for knowing the current position of an item in a list).
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
Implementing IEnumerable essentially means that the object can be iterated over. This doesn't necessarily mean it is an array as there are certain lists that can't be indexed but you can enumerate them.
IEnumerator is the actual object used to perform the iterations. It controls moving from one object to the next in the list.
Most of the time, IEnumerable & IEnumerator are used transparently as part of a foreach loop.
public class Customer
{
public String Name { get; set; }
public String City { get; set; }
public long Mobile { get; set; }
public double Amount { get; set; }
}
public class CustomerList : IEnumerable
{
Customer[] customers = new Customer[4];
public CustomerList()
{
customers[0] = new Customer { Name = "Bijay Thapa", City = "LA", Mobile = 9841639665, Amount = 89.45 };
customers[1] = new Customer { Name = "Jack", City = "NYC", Mobile = 9175869002, Amount = 426.00 };
customers[2] = new Customer { Name = "Anil min", City = "Kathmandu", Mobile = 9173694005, Amount = 5896.20 };
customers[3] = new Customer { Name = "Jim sin", City = "Delhi", Mobile = 64214556002, Amount = 596.20 };
}
public int Count()
{
return customers.Count();
}
public Customer this[int index]
{
get
{
return customers[index];
}
}
public IEnumerator GetEnumerator()
{
return customers.GetEnumerator(); // we can do this but we are going to make our own Enumerator
return new CustomerEnumerator(this);
}
}
现在我们将创建自己的自定义枚举器,如下所示。 因此,我们必须实现 MoveNext 方法。
public class CustomerEnumerator : IEnumerator
{
CustomerList coll;
Customer CurrentCustomer;
int currentIndex;
public CustomerEnumerator(CustomerList customerList)
{
coll = customerList;
currentIndex = -1;
}
public object Current => CurrentCustomer;
public bool MoveNext()
{
if ((currentIndex++) >= coll.Count() - 1)
return false;
else
CurrentCustomer = coll[currentIndex];
return true;
}
public void Reset()
{
// we dont have to implement this method.
}
}
现在我们可以对我们的集合使用 foreach 循环,如下所示;
class EnumeratorExample
{
static void Main(String[] args)
{
CustomerList custList = new CustomerList();
foreach (Customer cust in custList)
{
Console.WriteLine("Customer Name:"+cust.Name + " City Name:" + cust.City + " Mobile Number:" + cust.Amount);
}
Console.Read();
}
}
IEnumerable is a box that contains Ienumerator. IEnumerable is base interface for all the collections. foreach loop can operate if the collection implements IEnumerable. In the below code it explains the step of having our own Enumerator. Lets first define our Class of which we are going to make the collection.
public class Customer
{
public String Name { get; set; }
public String City { get; set; }
public long Mobile { get; set; }
public double Amount { get; set; }
}
Now we will define the Class which will act as a collection for our class Customer. Notice that it is implementing the interface IEnumerable. So that we have to implement the method GetEnumerator. This will return our custom Enumerator.
public class CustomerList : IEnumerable
{
Customer[] customers = new Customer[4];
public CustomerList()
{
customers[0] = new Customer { Name = "Bijay Thapa", City = "LA", Mobile = 9841639665, Amount = 89.45 };
customers[1] = new Customer { Name = "Jack", City = "NYC", Mobile = 9175869002, Amount = 426.00 };
customers[2] = new Customer { Name = "Anil min", City = "Kathmandu", Mobile = 9173694005, Amount = 5896.20 };
customers[3] = new Customer { Name = "Jim sin", City = "Delhi", Mobile = 64214556002, Amount = 596.20 };
}
public int Count()
{
return customers.Count();
}
public Customer this[int index]
{
get
{
return customers[index];
}
}
public IEnumerator GetEnumerator()
{
return customers.GetEnumerator(); // we can do this but we are going to make our own Enumerator
return new CustomerEnumerator(this);
}
}
Now we are going to create our own custom Enumerator as follow. So, we have to implement method MoveNext.
public class CustomerEnumerator : IEnumerator
{
CustomerList coll;
Customer CurrentCustomer;
int currentIndex;
public CustomerEnumerator(CustomerList customerList)
{
coll = customerList;
currentIndex = -1;
}
public object Current => CurrentCustomer;
public bool MoveNext()
{
if ((currentIndex++) >= coll.Count() - 1)
return false;
else
CurrentCustomer = coll[currentIndex];
return true;
}
public void Reset()
{
// we dont have to implement this method.
}
}
Now we can use foreach loop over our collection like below;
class EnumeratorExample
{
static void Main(String[] args)
{
CustomerList custList = new CustomerList();
foreach (Customer cust in custList)
{
Console.WriteLine("Customer Name:"+cust.Name + " City Name:" + cust.City + " Mobile Number:" + cust.Amount);
}
Console.Read();
}
}
At a high level the iterator pattern can be used to provide a standard way of iterating through collections of any type. We have 3 participants in the iterator pattern, the actual collection (client), the aggregator and the iterator. The aggregate is an interface/abstract class that has a method that returns an iterator. Iterator is an interface/abstract class that has methods allowing us to iterate through a collection.
In order to implement the pattern we first need to implement an iterator to produce a concrete that can iterate over the concerned collection (client) Then the collection (client) implements the aggregator to return an instance of the above iterator.
Here is the UML diagram
So basically in c#, IEnumerable is the abstract aggregate and IEnumerator is the abstract Iterator. IEnumerable has a single method GetEnumerator that is responsible for creating an instance of IEnumerator of the desired type. Collections like Lists implement the IEnumerable.
Example. Lets suppose that we have a method getPermutations(inputString) that returns all the permutations of a string and that the method returns an instance of IEnumerable<string>
In order to count the number of permutations we could do something like the below.
int count = 0;
var permutations = perm.getPermutations(inputString);
foreach (string permutation in permutations)
{
count++;
}
The c# compiler more or less converts the above to
using (var permutationIterator = perm.getPermutations(input).GetEnumerator())
{
while (permutationIterator.MoveNext())
{
count++;
}
}
If you have any questions please don't hesitate to ask.
static void EnumerableVsEnumeratorStateTest()
{
IList<int> numList = new List<int>();
numList.Add(1);
numList.Add(2);
numList.Add(3);
numList.Add(4);
numList.Add(5);
numList.Add(6);
Console.WriteLine("Using Enumerator - Remembers the state");
IterateFrom1to3(numList.GetEnumerator());
Console.WriteLine("Using Enumerable - Does not Remembers the state");
IterateFrom1to3Eb(numList);
Console.WriteLine("Using Enumerable - 2nd functions start from the item 1 in the collection");
}
static void IterateFrom1to3(IEnumerator<int> numColl)
{
while (numColl.MoveNext())
{
Console.WriteLine(numColl.Current.ToString());
if (numColl.Current > 3)
{
// This method called 3 times for 3 items (4,5,6) in the collection.
// It remembers the state and displays the continued values.
IterateFrom3to6(numColl);
}
}
}
static void IterateFrom3to6(IEnumerator<int> numColl)
{
while (numColl.MoveNext())
{
Console.WriteLine(numColl.Current.ToString());
}
}
static void IterateFrom1to3Eb(IEnumerable<int> numColl)
{
foreach (int num in numColl)
{
Console.WriteLine(num.ToString());
if (num>= 5)
{
// The below method invokes for the last 2 items.
//Since it doesnot persists the state it will displays entire collection 2 times.
IterateFrom3to6Eb(numColl);
}
}
}
static void IterateFrom3to6Eb(IEnumerable<int> numColl)
{
Console.WriteLine();
foreach (int num in numColl)
{
Console.WriteLine(num.ToString());
}
}
A Minor contribution.
As many of them explain about 'when to use' and 'use with foreach'. I thought of adding Another States Difference here as requested in question about the difference between both IEnumerable an IEnumerator.
I created the below code sample based on the below discussion threads.
Enumerator preserves the state (iteration position) between function calls while iterations the other hand Enumerable does not.
Here is the tested example with comments to understand.
Experts please add/correct me.
static void EnumerableVsEnumeratorStateTest()
{
IList<int> numList = new List<int>();
numList.Add(1);
numList.Add(2);
numList.Add(3);
numList.Add(4);
numList.Add(5);
numList.Add(6);
Console.WriteLine("Using Enumerator - Remembers the state");
IterateFrom1to3(numList.GetEnumerator());
Console.WriteLine("Using Enumerable - Does not Remembers the state");
IterateFrom1to3Eb(numList);
Console.WriteLine("Using Enumerable - 2nd functions start from the item 1 in the collection");
}
static void IterateFrom1to3(IEnumerator<int> numColl)
{
while (numColl.MoveNext())
{
Console.WriteLine(numColl.Current.ToString());
if (numColl.Current > 3)
{
// This method called 3 times for 3 items (4,5,6) in the collection.
// It remembers the state and displays the continued values.
IterateFrom3to6(numColl);
}
}
}
static void IterateFrom3to6(IEnumerator<int> numColl)
{
while (numColl.MoveNext())
{
Console.WriteLine(numColl.Current.ToString());
}
}
static void IterateFrom1to3Eb(IEnumerable<int> numColl)
{
foreach (int num in numColl)
{
Console.WriteLine(num.ToString());
if (num>= 5)
{
// The below method invokes for the last 2 items.
//Since it doesnot persists the state it will displays entire collection 2 times.
IterateFrom3to6Eb(numColl);
}
}
}
static void IterateFrom3to6Eb(IEnumerable<int> numColl)
{
Console.WriteLine();
foreach (int num in numColl)
{
Console.WriteLine(num.ToString());
}
}
A. We iterate the list in different way, foreach can be used for IEnumerable and while loop for IEnumerator.
B. IEnumerator can remember the current index when we pass from one method to another (it start working with current index) but IEnumerable can't remember the index and it reset the index to beginning. More in this video https://www.youtube.com/watch?v=jd3yUjGc9M0
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Enudemo
{
class Person
{
string name = "";
int roll;
public Person(string name, int roll)
{
this.name = name;
this.roll = roll;
}
public override string ToString()
{
return string.Format("Name : " + name + "\t Roll : " + roll);
}
}
class Demo : IEnumerable
{
ArrayList list1 = new ArrayList();
public Demo()
{
list1.Add(new Person("Shahriar", 332));
list1.Add(new Person("Sujon", 333));
list1.Add(new Person("Sumona", 334));
list1.Add(new Person("Shakil", 335));
list1.Add(new Person("Shruti", 336));
}
IEnumerator IEnumerable.GetEnumerator()
{
return list1.GetEnumerator();
}
}
class Program
{
static void Main(string[] args)
{
Demo d = new Demo(); // Notice here. it is simple object but for
//IEnumerator you can get the collection data
foreach (Person X in d)
{
Console.WriteLine(X);
}
Console.ReadKey();
}
}
}
/*
Output :
Name : Shahriar Roll : 332
Name : Sujon Roll : 333
Name : Sumona Roll : 334
Name : Shakil Roll : 335
Name : Shruti Roll : 336
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Enudemo
{
class Person
{
string name = "";
int roll;
public Person(string name, int roll)
{
this.name = name;
this.roll = roll;
}
public override string ToString()
{
return string.Format("Name : " + name + "\t Roll : " + roll);
}
}
class Demo : IEnumerable
{
ArrayList list1 = new ArrayList();
public Demo()
{
list1.Add(new Person("Shahriar", 332));
list1.Add(new Person("Sujon", 333));
list1.Add(new Person("Sumona", 334));
list1.Add(new Person("Shakil", 335));
list1.Add(new Person("Shruti", 336));
}
IEnumerator IEnumerable.GetEnumerator()
{
return list1.GetEnumerator();
}
}
class Program
{
static void Main(string[] args)
{
Demo d = new Demo(); // Notice here. it is simple object but for
//IEnumerator you can get the collection data
foreach (Person X in d)
{
Console.WriteLine(X);
}
Console.ReadKey();
}
}
}
/*
Output :
Name : Shahriar Roll : 332
Name : Sujon Roll : 333
Name : Sumona Roll : 334
Name : Shakil Roll : 335
Name : Shruti Roll : 336
*/
发布评论
评论(16)
您不能在“foreach”之上使用“IEnumerable”。 实现
IEnumerable
使得使用foreach
成为可能。当您编写如下代码时:
它在功能上等效于编写:
通过“功能等效”,我的意思是,这实际上就是编译器将代码转换成的内容。 在此示例中,您不能在
baz
上使用foreach
,除非baz
实现IEnumerable
。IEnumerable
表示baz
实现了该方法该方法返回的
IEnumerator
对象必须实现该方法并且
第一个方法前进到该方法中的下一个对象创建枚举器的 IEnumerable 对象,如果完成则返回 false,第二个对象返回当前对象。
.NET 中任何可以迭代的东西都实现了
IEnumerable
。 如果您正在构建自己的类,并且它尚未从实现IEnumerable
的类继承,则可以通过实现foreach
语句使您的类可在foreach
语句中使用>IEnumerable(并通过创建其新的GetEnumerator
方法将返回的枚举器类)。You don't use
IEnumerable
"over"foreach
. ImplementingIEnumerable
makes usingforeach
possible.When you write code like:
it's functionally equivalent to writing:
By "functionally equivalent," I mean that's actually what the compiler turns the code into. You can't use
foreach
onbaz
in this example unlessbaz
implementsIEnumerable
.IEnumerable
means thatbaz
implements the methodThe
IEnumerator
object that this method returns must implement the methodsand
The first method advances to the next object in the
IEnumerable
object that created the enumerator, returningfalse
if it's done, and the second returns the current object.Anything in .NET that you can iterate over implements
IEnumerable
. If you're building your own class, and it doesn't already inherit from a class that implementsIEnumerable
, you can make your class usable inforeach
statements by implementingIEnumerable
(and by creating an enumerator class that its newGetEnumerator
method will return).IEnumerable 和 IEnumerator 接口
要开始检查现有 .NET 接口的实现过程,让我们首先看看
IEnumerable 和 IEnumerator。 回想一下,C# 支持名为 foreach 的关键字,它允许您
迭代任何数组类型的内容:
虽然看起来只有数组类型可以使用此构造,但事实是
任何支持名为 GetEnumerator() 的方法的类型都可以通过 foreach 构造进行评估。
说明一下,跟我来!
假设我们有一个 Garage 类:
理想情况下,使用 foreach 迭代 Garage 对象的子项会很方便
构造,就像数据值数组一样:
遗憾的是,编译器通知您 Garage 类没有实现名为的方法
GetEnumerator()。 此方法由 IEnumerable 接口形式化,该接口潜伏在 System.Collections 命名空间中。
支持此行为的类或结构宣称它们能够公开包含的
调用者的子项(在本例中为 foreach 关键字本身)。 下面是这个标准 .NET 接口的定义:
如您所见,GetEnumerator() 方法返回对另一个名为
System.Collections.IEnumerator。 此接口提供了基础结构,允许调用者遍历 IEnumerable 兼容容器包含的内部对象:
如果您想更新 Garage 类型以支持这些接口,您可以采取很长的路
手动实现每个方法。 虽然您当然可以自由地提供定制版本
GetEnumerator()、MoveNext()、Current 和 Reset(),还有更简单的方法。 由于 System.Array 类型(以及许多其他集合类)已经实现了 IEnumerable 和 IEnumerator,因此您可以简单地将请求委托给 System.Array,如下所示:
更新 Garage 类型后,您可以安全地使用其中的类型C# foreach 构造。 此外,鉴于 GetEnumerator() 方法已公开定义,对象用户还可以与 IEnumerator 类型进行交互:
但是,如果您希望从对象级别隐藏 IEnumerable 的功能,只需使
使用显式接口实现:
通过这样做,普通对象用户将找不到 Garage 的 GetEnumerator() 方法,而
foreach 构造会在必要时在后台获取接口。
改编自Pro C# 5.0 和 .NET 4.5 Framework
The IEnumerable and IEnumerator Interfaces
To begin examining the process of implementing existing .NET interfaces, let’s first look at the role of
IEnumerable and IEnumerator. Recall that C# supports a keyword named foreach that allows you to
iterate over the contents of any array type:
While it might seem that only array types can make use of this construct, the truth of the matter is
any type supporting a method named GetEnumerator() can be evaluated by the foreach construct.To
illustrate, follow me!
Suppose we have a Garage class:
Ideally, it would be convenient to iterate over the Garage object’s subitems using the foreach
construct, just like an array of data values:
Sadly, the compiler informs you that the Garage class does not implement a method named
GetEnumerator(). This method is formalized by the IEnumerable interface, which is found lurking within the System.Collections namespace.
Classes or structures that support this behavior advertise that they are able to expose contained
subitems to the caller (in this example, the foreach keyword itself). Here is the definition of this standard .NET interface:
As you can see, the GetEnumerator() method returns a reference to yet another interface named
System.Collections.IEnumerator. This interface provides the infrastructure to allow the caller to traverse the internal objects contained by the IEnumerable-compatible container:
If you want to update the Garage type to support these interfaces, you could take the long road and
implement each method manually. While you are certainly free to provide customized versions of
GetEnumerator(), MoveNext(), Current, and Reset(), there is a simpler way. As the System.Array type (as well as many other collection classes) already implements IEnumerable and IEnumerator, you can simply delegate the request to the System.Array as follows:
After you have updated your Garage type, you can safely use the type within the C# foreach construct. Furthermore, given that the GetEnumerator() method has been defined publicly, the object user could also interact with the IEnumerator type:
However, if you prefer to hide the functionality of IEnumerable from the object level, simply make
use of explicit interface implementation:
By doing so, the casual object user will not find the Garage’s GetEnumerator() method, while the
foreach construct will obtain the interface in the background when necessary.
Adapted from the Pro C# 5.0 and the .NET 4.5 Framework
实现 IEnumerable 意味着您的类返回一个 IEnumerator 对象:
实现 IEnumerator 意味着您的类返回迭代的方法和属性:
无论如何,这就是区别。
Implementing IEnumerable means your class returns an IEnumerator object:
Implementing IEnumerator means your class returns the methods and properties for iteration:
That's the difference anyway.
通过类比解释 + 代码演练
类比:想象你是飞机上的侦探。 你需要从所有乘客中找出嫌疑人。
飞机只能做到这一点,如果它是:
可数是什么意思?
如果一家航空公司是“可数”的,这意味着飞机上必须有一名空乘人员,其唯一的工作就是数数:
计数器一直持续到他到达飞机末端。
让我们将其与 IEnumerables 联系起来
摘要
换句话说,如果某个东西有一个计数器,那么它就是可数的。 计数器必须(基本上):(i) 记住它的位置(状态),(ii) 能够移动下一步,(iii) 并了解当前正在与他打交道的人。
可枚举只是“可数”的一个花哨词。
Explanation via Analogy + Code Walkthrough
Analogy: Imagine you are a detective on an aeroplane . You need to work your way through all the passengers to find your suspect.
An aeroplane can only do this, if it is:
What does countable mean?
If an airline is "countable", this means that there MUST be a flight attendant present on the plane, whose sole job is to count:
The counter keeps going till he reaches the end of the plane.
Let's tie this with the IEnumerables
Summary
In other words, something is countable if it has a counter. And counter must (basically): (i) remember its place (state), (ii) be able to move next, (iii) and know about the current person he is dealing with.
Enumerable is just a fancy word for "countable".
IEnumerable 实现 GetEnumerator。 调用时,该方法将返回一个 IEnumerator 它实现移动下一个、重置和当前。
因此,当您的类实现 IEnumerable 时,您是说您可以调用方法 (GetEnumerator) 并获取返回的新对象(IEnumerator),您可以在 foreach 等循环中使用。
IEnumerable implements GetEnumerator. When called, that method will return an IEnumerator which implements MoveNext, Reset and Current.
Thus when your class implements IEnumerable, you are saying that you can call a method (GetEnumerator) and get a new object returned (an IEnumerator) you can use in a loop such as foreach.
实现 IEnumerable 使您能够获取列表的 IEnumerator。
IEnumerator 允许使用yield 关键字进行foreach 样式顺序访问列表中的项目。
在 foreach 实现之前(例如,在 Java 1.4 中),迭代列表的方法是从列表中获取一个枚举器,然后向其询问列表中的“下一个”项目,只要返回的值是下一个项目不为空。 Foreach 只是作为一种语言功能隐式地执行此操作,就像 lock() 在幕后实现 Monitor 类一样。
我希望 foreach 适用于列表,因为它们实现了 IEnumerable。
Implementing IEnumerable enables you to get an IEnumerator for a list.
IEnumerator allows foreach style sequential access to the items in the list, using the yield keyword.
Before foreach implementation (in Java 1.4, for example), the way to iterate a list was to get an enumerator from the list, then ask it for the "next" item in the list, for as long as the value returned as the next item is not null. Foreach simply does that implicitly as a language feature, in the same way that lock() implements the Monitor class behind the scenes.
I expect foreach works on lists because they implement IEnumerable.
将可枚举对象视为列表、堆栈、树。
Think of enumerable objects as of lists, stacks, trees.
IEnumerable 和 IEnumerator(及其通用对应物 IEnumerable和 IEnumerator)是 迭代器 .Net Framework 类库集合 中的实现。
IEnumerable 是您在大多数代码中看到的最常见的接口。 它支持 foreach 循环、生成器(想想yield),并且由于其微小的接口,它用于创建紧密的抽象。 IEnumerable 依赖于 IEnumerator。
另一方面,IEnumerator 提供了一个稍低级别的迭代接口。 它被称为显式迭代器,它使程序员可以更好地控制迭代周期。
IEnumerable
IEnumerable 是一个标准接口,可以迭代支持它的集合(事实上,我今天能想到的所有集合类型都实现了 IEnumerable)。 编译器支持允许诸如
foreach
之类的语言功能。 一般来说,它支持隐式迭代器实现。foreach 循环
我认为 foreach 循环是使用 IEnumerable 接口的主要原因之一。 与经典的 C 风格的 for 循环相比,
foreach
具有非常简洁的语法,并且非常容易理解,在经典的 for 循环中,您需要检查各种变量以了解它在做什么。可能一个鲜为人知的功能
是 IEnumerable 还启用 C# 中的生成器,使用
yield return
和yield break
语句。抽象
实践中的另一个常见场景是使用IEnumerable来提供简约的抽象。 因为它是一个很小的只读接口,所以鼓励您将集合公开为 IEnumerable(而不是 List)。 这样您就可以自由地更改您的实现,而不会破坏客户端的代码(例如将 List 更改为 LinkedList)。
需要
注意的一个行为是,在流实现中(例如,从数据库中逐行检索数据,而不是首先将所有结果加载到内存中),您不能多次迭代集合。 这与 List 等内存中集合形成鲜明对比,您可以在其中迭代多次而不会出现问题。 例如,ReSharper,对 IEnumerable 的可能多重枚举进行代码检查。
IEnumerator
另一方面,IEnumerator 是使 IEnumerble-foreach-magic 工作的幕后接口。 严格来说,它支持显式迭代器。
根据我的经验,IEnumerator 很少在常见场景中使用,因为它的语法更冗长,语义稍显混乱(至少对我来说是这样;例如 MoveNext() 也返回一个值,这个名字根本没有暗示)。
IEnumerator 的用例
我仅在提供 IEnumerable 接口的特定(级别稍低)的库和框架中使用了 IEnumerator。 一个例子是数据流处理库,它在 foreach 循环中提供一系列对象,尽管幕后数据是使用各种文件流和序列化收集的。
客户端代码
库
IEnumerable and IEnumerator (and their generic counterparts IEnumerable<T> and IEnumerator<T>) are base interfaces of iterator implementations in .Net Framework Class Libray collections.
IEnumerable is the most common interface you would see in the majority of the code out there. It enables the foreach loop, generators (think yield) and because of its tiny interface, it's used to create tight abstractions. IEnumerable depends on IEnumerator.
IEnumerator, on the other hand, provides a slightly lower level iteration interface. It's referred to as the explicit iterator which gives the programmer more control over the iteration cycle.
IEnumerable
IEnumerable is a standard interface that enables iterating over collections that supports it (in fact, all collection types I can think of today implements IEnumerable). Compiler support allows language features like
foreach
. In general terms, it enables this implicit iterator implementation.foreach Loop
I think
foreach
loop is one of the main reasons for using IEnumerable interfaces.foreach
has a very succinct syntax and very easy to understand compared to classic C style for loops where you need to check the various variables to see what it was doing.yield Keyword
Probably a lesser known feature is that IEnumerable also enables generators in C# with the use of
yield return
andyield break
statements.Abstractions
Another common scenario in practice is using IEnumerable to provide minimalistic abstractions. Because it is a minuscule and read-only interface, you are encouraged to expose your collections as IEnumerable (rather than List for example). That way you are free to change your implementation without breaking your client's code (change List to a LinkedList for instance).
Gotcha
One behaviour to be aware of is that in streaming implementations (e.g. retrieving data row by row from a database, instead of loading all the results in memory first) you cannot iterate over the collection more than once. This is in contrast to in-memory collections like List, where you can iterate multiple times without problems. ReSharper, for example, has a code inspection for Possible multiple enumeration of IEnumerable.
IEnumerator
IEnumerator, on the other hand, is the behind the scenes interface which makes IEnumerble-foreach-magic work. Strictly speaking, it enables explicit iterators.
In my experience IEnumerator is rarely used in common scenarios due to its more verbose syntax and slightly confusing semantics (at least to me; e.g. MoveNext() returns a value as well, which the name doesn't suggest at all).
Use case for IEnumerator
I only used IEnumerator in particular (slightly lower level) libraries and frameworks where I was providing IEnumerable interfaces. One example is a data stream processing library which provided series of objects in a
foreach
loop even though behind the scenes data was collected using various file streams and serialisations.Client code
Library
IEnumerable 和 IEnumerator 之间的差异:
每当我们将 IEnumerable 集合传递给另一个函数时,它
不知道项目/对象的当前位置(不知道正在执行哪个项目)
IEnumerable 有一个方法 GetEnumerator()
IEnumerator 有一个名为 Current 的属性和两个方法:Reset() 和 MoveNext()(这对于了解列表中项目的当前位置很有用)。
Differences between IEnumerable and IEnumerator :
Whenever we pass an IEnumerable collection to another function, it
doesn't know the current position of item/object (doesn't know which item its executing)
IEnumerable have one method GetEnumerator()
IEnumerator has one property called Current and two methods, Reset() and MoveNext() (which is useful for knowing the current position of an item in a list).
实现 IEnumerable 本质上意味着可以迭代该对象。 这并不一定意味着它是一个数组,因为某些列表无法索引,但您可以枚举它们。
IEnumerator
是用于执行迭代的实际对象。 它控制从一个对象移动到列表中的下一个对象。大多数时候,
IEnumerable
&IEnumerator
透明地用作foreach
循环的一部分。Implementing
IEnumerable
essentially means that the object can be iterated over. This doesn't necessarily mean it is an array as there are certain lists that can't be indexed but you can enumerate them.IEnumerator
is the actual object used to perform the iterations. It controls moving from one object to the next in the list.Most of the time,
IEnumerable
&IEnumerator
are used transparently as part of aforeach
loop.IEnumerable 是一个包含 Ienumerator 的框。 IEnumerable 是所有集合的基本接口。 如果集合实现 IEnumerable,则 foreach 循环可以运行。 在下面的代码中,它解释了拥有我们自己的枚举器的步骤。 让我们首先定义我们要创建集合的类。
现在我们将定义一个类,它将充当我们的类 Customer 的集合。 请注意,它正在实现 IEnumerable 接口。 所以我们必须实现GetEnumerator方法。 这将返回我们的自定义枚举器。
现在我们将创建自己的自定义枚举器,如下所示。 因此,我们必须实现 MoveNext 方法。
现在我们可以对我们的集合使用 foreach 循环,如下所示;
IEnumerable is a box that contains Ienumerator. IEnumerable is base interface for all the collections. foreach loop can operate if the collection implements IEnumerable. In the below code it explains the step of having our own Enumerator. Lets first define our Class of which we are going to make the collection.
Now we will define the Class which will act as a collection for our class Customer. Notice that it is implementing the interface IEnumerable. So that we have to implement the method GetEnumerator. This will return our custom Enumerator.
Now we are going to create our own custom Enumerator as follow. So, we have to implement method MoveNext.
Now we can use foreach loop over our collection like below;
了解迭代器模式将对您有所帮助。 我建议阅读相同的内容。
迭代器模式
在高层次上,迭代器模式可用于提供标准的迭代方式通过任何类型的集合。
迭代器模式有 3 个参与者:实际集合(客户端)、聚合器和迭代器。 聚合是一个接口/抽象类,具有返回迭代器的方法。 迭代器是一个接口/抽象类,它具有允许我们迭代集合的方法。
为了实现该模式,我们首先需要实现一个迭代器来生成一个可以迭代相关集合(客户端)的具体对象
然后集合(客户端)实现聚合器以返回上述迭代器的实例。
这是 UML 图
所以基本上在 C# 中,IEnumerable 是抽象聚合,IEnumerator 是抽象迭代器。 IEnumerable 有一个 GetEnumerator 方法,负责创建所需类型的 IEnumerator 实例。 像列表这样的集合实现了 IEnumerable。
例子。
假设我们有一个方法
getPermutations(inputString)
,它返回字符串的所有排列,并且该方法返回IEnumerable
的实例,以便计算我们可以做如下的排列数。
c# 编译器或多或少将上述内容转换为
如果您有任何问题,请随时询问。
An understanding of the Iterator pattern will be helpful for you. I recommend reading the same.
Iterator Pattern
At a high level the iterator pattern can be used to provide a standard way of iterating through collections of any type.
We have 3 participants in the iterator pattern, the actual collection (client), the aggregator and the iterator. The aggregate is an interface/abstract class that has a method that returns an iterator. Iterator is an interface/abstract class that has methods allowing us to iterate through a collection.
In order to implement the pattern we first need to implement an iterator to produce a concrete that can iterate over the concerned collection (client)
Then the collection (client) implements the aggregator to return an instance of the above iterator.
Here is the UML diagram
So basically in c#, IEnumerable is the abstract aggregate and IEnumerator is the abstract Iterator. IEnumerable has a single method GetEnumerator that is responsible for creating an instance of IEnumerator of the desired type. Collections like Lists implement the IEnumerable.
Example.
Lets suppose that we have a method
getPermutations(inputString)
that returns all the permutations of a string and that the method returns an instance ofIEnumerable<string>
In order to count the number of permutations we could do something like the below.
The c# compiler more or less converts the above to
If you have any questions please don't hesitate to ask.
一个小小的贡献。
正如他们中的许多人解释的“何时使用”和“与 foreach 一起使用”。
我想根据有关 IEnumerable 和 IEnumerator 之间差异的问题的要求,在此处添加另一个状态差异。
我根据以下讨论线程创建了以下代码示例。
IEnumerable、IEnumerator 与 foreach,何时使用
IEnumerator 和 IEnumerable 之间有什么区别?
枚举器保留函数调用之间的状态(迭代位置),而迭代则相反,Enumerable 则不然。
这是经过测试的示例,带有注释以供理解。
请专家补充/指正。
A Minor contribution.
As many of them explain about 'when to use' and 'use with foreach'.
I thought of adding Another States Difference here as requested in question about the difference between both IEnumerable an IEnumerator.
I created the below code sample based on the below discussion threads.
IEnumerable , IEnumerator vs foreach, when to use what
What is the difference between IEnumerator and IEnumerable?
Enumerator preserves the state (iteration position) between function calls while iterations the other hand Enumerable does not.
Here is the tested example with comments to understand.
Experts please add/correct me.
我注意到这些差异:
A. 我们以不同的方式迭代列表,foreach 可用于 IEnumerable,而 while 循环可用于 IEnumerator。
B. 当我们从一种方法传递到另一种方法时,IEnumerator 可以记住当前索引(它开始使用当前索引),但 IEnumerable 无法记住索引,并且会将索引重置为开头。 更多内容尽在本视频https://www.youtube.com/watch?v=jd3yUjGc9M0
I have noticed these differences:
A. We iterate the list in different way, foreach can be used for IEnumerable and while loop for IEnumerator.
B. IEnumerator can remember the current index when we pass from one method to another (it start working with current index) but IEnumerable can't remember the index and it reset the index to beginning. More in this video https://www.youtube.com/watch?v=jd3yUjGc9M0
IEnumerable
和IEnumerator
都是 C# 中的接口。IEnumerable
是一个接口,定义了返回IEnumerator
接口的单个方法GetEnumerator()
。这适用于对实现可与
foreach
语句一起使用的IEnumerable
的集合的只读访问。IEnumerator
有两个方法:MoveNext
和Reset
。 它还有一个名为Current
的属性。下面展示了 IEnumerable 和 IEnumerator 的实现。
IEnumerable
andIEnumerator
both are interfaces in C#.IEnumerable
is an interface defining a single methodGetEnumerator()
that returns anIEnumerator
interface.This works for read-only access to a collection that implements that
IEnumerable
can be used with aforeach
statement.IEnumerator
has two methods,MoveNext
andReset
. It also has a property calledCurrent
.The following shows the implementation of IEnumerable and IEnumerator.