C # Collection-enumeration (enumeration)

Source: Internet
Author: User
Tags mscorlib

In the domain of computers there are many kinds of collections, from simple data structures such as arrays, linked lists, to complex data structures such as red-black trees, hash tables. Although the internal implementation of these data structures is quite different from the external features, the traversal of the contents of the collection is a common requirement. The. NET framework implements traversal collection functionality through the IEnumerable and IEnumerator interfaces.

Non-generic Generic Note
IEnumerator Ienumerator<t>
IEnumerable Ienumerable<t> can only traverse
ICollection Icollection<t> Traversal, Statistical collection elements
IDictionary
IList
Idictionary<tkey,tvalue>
Ilist<t>
Have more features

IEnumerable and IEnumerator

The IEnumerator interface defines the traversal protocol-in which the elements in the collection use a forward-way traversal. It declares the following:

public interface ienumerator{       bool MoveNext ();       Object current {get;}    void Reset ();}

MoveNext moves the current element or pointer to the next position and returns False if there is no element in the next position. Current returns the element at the position of the present value. Before you can get the first element of a collection, you must call the MoveNext method-which is also true for empty collections. The Reset method, which moves to the initial position, allows the collection to traverse again. Reset is much more designed for COM interop: You should try to avoid calling this method directly because it is not universally supported (calling this method directly is not necessary because it is easier to create a new enumeration instance).

Collections generally do not implement enumerations, instead they provide enumerations via the Ienurable interface

public interface ienumerable{       IEnumerator GetEnumerator ();}

By defining a single return enumeration method, the IEnumerable interface provides more flexibility so that the logic for iterating through the collection of each implementation class can be the same for each part. This means that each user of the collection can create their own method to iterate through the collection without affecting each other. IEnumerable can be seen as a ienumeratorprovider, which is an interface that all collection classes must implement.

The following code shows how to use IEnumerable and IEnumerator:

string s = "Hello";//Ienumeratorienumerator Rator = S.getenumerator (); while (Rator. MoveNext ())    Console.Write (rator. Current + "."); Console.WriteLine ();//Ienumerableforeach (char C in s)    Console.Write (C + ".");

In general, the GetEnumerator method is rarely called to get the IEnumerator interface, because C # provides the foreach syntax (the foreach syntax is automatically called GetEnumerator to iterate through the collection), which makes the code more concise.

Ienumerable<t> and Ienumerator<t>

The IEnumerator and IEnumerable corresponding generic interfaces are defined as follows:

Public interface Ienumerator<out t>: IDisposable, ienumerator{           new T-current {        get;     }}
Public interface Ienumerable<out t>: ienumerable{ new ienumerator<t> GetEnumerator ();

Generic's current and GetEnumerator, which increases the type safety of interface ienumerable<t> and ienumerator<t>, avoids boxing operations on value types, making it more convenient for users of the collection. Note that the Ienumerable<t> interface is implemented by default for numeric types.

Thanks to the implementation of the type-safe interface, the method Test2 (ARR) will error at compile time:

static void Main (string[] args) {    char[] arr = new char[] {' 1 ', ' 2 ', ' 3 '};    Test1 (arr);  OK    Test2 (arr);//Complie-error:cannot convert from char[] to ienumerable[]    console.readline ();} static void Test1 (IEnumerable numbers) {    foreach (object I in numbers)        Console.Write (i + ",");} static void Test2 (ienumerable<int> numbers) {    foreach (object I in numbers)        Console.Write (i + ",");}

Note that the Ienumerable<t> interface is implemented by default in array, so it must implement the IEnumerable interface at the same time. Although char[] cannot be converted to IENUMRABLE<INT>, it can be converted to ienumeable, so Test1 can be compiled, and TEST2 cannot be compiled (type conversion failure error)

For collection classes, exposing ienumerable<t> is a standard practice and requires a display to implement the IEnumerable interface, thus hiding non-generic IEnumerable. At this point, you call GetEnumerator again, you will get ienumerator<t>. But sometimes, in order to be compatible with non-generic collections, we can not abide by this rule. The best example is the array collection, where the array must return non-generic IEnumerator to avoid conflicts with earlier code. In this case, in order to obtain the IENUMERATOR<T>, you must first convert the array display to the generic interface, and then obtain:

char[] arr = new char[] {' 1 ', ' 2 ', ' 3 '};var rator = ((ienumerable<char>) arr). GetEnumerator ();

Fortunately, you seldom need to write such a code, thanks to the foreach statement.

Ienumerable<t> and IDisposable

Ienumerator<t> inherited the IDisposable. This allows the enumeration to have references to resources, such as database connections, to ensure that the resources are freed when the traversal is complete. The foreach statement recognizes this feature, for example, the following foreach statement

ilist<char> chars =new list<char> () {' A ', ' B ', ' C '}; foreach (char c in chars)     Console.Write (c);

The compiled code is:

. method private Hidebysig static void  Main (string[] args) cil managed{  ... il_0026:  callvirt   instance class [Mscorlib]system.collections.generic.ienumerator ' 1<!0> class [ Mscorlib]system.collections.generic.ienumerable ' 1<char>::getenumerator ()  IL_002b:  stloc.3  . Try  {   ... System.Collections.Generic.IEnumerator ' 1<char>::get_current () ...    il_0036:  call       void [Mscorlib]system.console::write (char) ...    il_003d:  callvirt   instance bool [Mscorlib]system.collections.ienumerator::movenext ()  ...  }  End. Try  finally  {    ...    il_0055:  callvirt   instance void [mscorlib]system.idisposable::D ispose () ...  }  End handler ...  }//End of method Program::main

Therefore, if the Ienumable<t> interface is implemented, when foreach is executed, it is converted to call Getenumerator<t> After the traversal is complete, the ienumerator<t> is released.

Implementing an Enumeration interface

You need to implement IEnumerable or ienumerable<t> when one or more of the following conditions are met

    1. In order to support the foreach statement
    2. In order to implement a collection other than the standard set is interoperable
    3. To satisfy a complex set of interfaces
    4. To support collection initialization

and realize ienumerable/ienumerable<t> you have to provide a list of the following three ways you can implement

    • If the class contains a different collection, you need to return the enumeration of the contained collection
    • Using yield return inside an iterative traversal
    • Implementation of instantiated Ienumerator/ienumerator<t>

1) Example ienumerator/ienumerator<t>

The enumeration that returns another collection is the GetEnumerator that invokes the internal collection. However, this only happens in a simple scenario where the elements in the internal collection have been met. Another more flexible way is to generate an iterator from the yield return statement. An iterator (ITERAOTR) is a C # language attribute that is used for a secondary production collection, as well as a foreach that can be used with iterator to traverse a collection. An iterator automatically handles the implementation of IEnumerable and IEnumerator. Here's a simple example.

Internal class mycollection:ienumerable{    int[] Data ={1, 2, 3};    Public IEnumerator GetEnumerator ()    {        foreach (int. i in data)            yield return i;}    }

Note that GetEnumerator does not return an enumeration at all. Depending on the statement after the yield return is parsed, the compiler writes a hidden inline-enumeration class, and then reconstructs the GetEnumerator implementation instantiation, and finally returns the class. Iterations are both powerful and simple.

With the IL code, we can see that an inline enumeration class is actually produced

On the basis of the above code, we make some changes to Mycollecton, so that it not only realizes IEnumerable, but also realizes ienumerable<t>

Internal class mycollection:ienumerable<int>{    int[] Data ={1, 2, 3};            Public ienumerator<int> GetEnumerator ()    {        foreach (int. i in data)            yield return i;    IEnumerator Ienumerable.getenumerator ()    {        return GetEnumerator ();    }}

Because Ienumerable<t> inherits the IEnumerable, we have to implement generic's getenumerator and non-generic GetEnumerator. In accordance with the standard practice, we have achieved the generic GetEnumerator. So for non-generic getenumerator, we call generic's GetEnumerator directly, because Ienumerable<t> inherits Ienumerbale.

The corresponding IL code is as follows: ( note the compiler implements the Ienumerator<int32> interface, not the Ienumerator<object> interface )

2) return to ienumerable<t> using yield return

The class mycollection we create can be used as a basic implementation of complex collection classes. However, if you do not need to implement IENUMERABLE<T>, then you should be able to implement a ienumerable<t> through the yield return statement, rather than writing a class such as mycollection. This means that you can migrate the iterative logic to a method that returns ienumerable<t>, and then let the compiler do the rest for you.

Class program{    static void Main (string[] args)    {        foreach (int i in Getsomeintegers ())            Console.WriteLine (i);        Console.ReadLine ();    }    Static ienumerable<int> getsomeintegers ()    {        int[] data = {1, 2, 3};         foreach (int i in data)            yield return i;    }       }

The IL code corresponding to it

From the IL code, we can see that the compiler also produces an internal class that implements the Ienumerator<int32> interface.

3) If the class contains a different collection, you need to return the enumeration of the contained collection

The last way to do this is to write a class that implements the IEnumerator interface directly. This is actually what the compiler did before. In practice, you don't need to do this.

First, we will implement the non-generic IEnumerator.

Internal class mycollection:ienumerable{    int[] Data ={1, 2, 3};            Public IEnumerator GetEnumerator ()    {        Return to new Enumerator (this);    }    Private class Enumerator:ienumerator    {        mycollection collection;        int index;        Public Enumerator (mycollection collection)        {            this.collection = collection;            index =-1;        }        Public object Current        {            get {return collection.data[index];}        }        public bool MoveNext ()        {            if (Index < collection.data.length-1)            {                index++;                return true;            }            return false;        }        public void Reset ()        {            index =-1;        }    }}

Then, on the basis of the above code, we implement the generic IEnumerator

Internal class mycollection:ienumerable<int32>{int[] data = {1, 2, 3}; Implement Ienumerable<t> public ienumerator<int32> GetEnumerator () {return new enumerator (thi    s);    }//Implement IEnumerable IEnumerator Ienumerable.getenumerator () {return GetEnumerator ();        } private class Enumerator:ienumerator<int32> {mycollection collection;        int index;                Public Enumerator (mycollection collection) {this.collection = collection;            index =-1; } #region implement ienumerator<t> public int current {get {return col Lection.data[index];            }} public void Dispose () {} public bool MoveNext ()                    {if (Index < collection.data.length-1) {index++;   return true;             } return false;            } public void Reset () {index =-1;  } #endregion//Implement IEnumerator object IEnumerator.Current {get {return Current; }        }    }}

The generic version of IEnumerator is more efficient than non-generic ienumberator because it does not need to convert int to object, thus reducing the cost of boxing. Let's take a look at the corresponding IL code at this point:

Obviously, we can see that we manually create the enumerator with the compiler-generated enumerator as well

Also, when we use the second approach, if we have multiple ienumerable<t> methods, then the compiler produces multiple classes that implement the Ienumerator<t>

Class program{    static void Main (string[] args)    {        foreach (int i in Getsomeintegers ())            Console.WriteLine (i);        foreach (int i in Getsomeodds ())            Console.WriteLine (i);        Console.ReadLine ();    }    Static ienumerable<int32> getsomeintegers ()    {        int[] collection = {1, 2, 3, 4, 5};        foreach (int i in collection)            yield return i;    }    Static ienumerable<int32> Getsomeodds ()    {        int[] collection = {1, 2, 3, 4, 5};        foreach (int i in collection)            if (i%2==1)                yield return i;    }          }

The corresponding IL code can be seen with two internal ienumerator<t> classes

And the following code will only produce a ienumerator<t> class

Class program{    static void Main (string[] args)    {        foreach (int i in Getsomeintegers ())            Console.WriteLine (i);        foreach (int i in Getsomeodds ())            Console.WriteLine (i);        Console.ReadLine ();    }    Static ienumerable<int32> getsomeintegers ()    {        return getdetails ();    }    Static ienumerable<int32> Getsomeodds ()    {        return getdetails (TRUE);    }    private static ienumerable<int32> getdetails (bool isodd = false)    {        int[] collection = {1, 2, 3, 4, 5};
   int index = 0;        foreach (int i in collection)        {            if (isodd && i% 2 = = 1)                yield return i;            if (!isodd)                yield return collection[index];                        index++;}}}   

Similarly, the following code will only produce a ienumerator<t> class

..... static ienumerable<int32> Getsomeintegers () {    foreach (int i in Getdetails ())        yield return i; Static ienumerable<int32> Getsomeodds () {    foreach (int i in Getdetails (true))        yield return i; ....

From this, we can find that when implementing IEnumerable, especially when there are multiple implementations, you need to be careful to minimize the number of classes that the compiler generates IEnumerator. I guess internally that the compiler should determine the number of IEnumerator classes based on the iterator of a really different yield return. In my sample code, when two IEnumerator classes are produced, the iterator of yield return for getsomeintegers and Getsomeodds are different, and when a IEnumerator class is produced, They all point to Getdetails's yield return corresponding to the iterator.

Finally, let's take a look at IEnumerator and Iterator .

On the Internet, there is no clear distinction between the two, perhaps I confuse the two concepts should not be confused. Here is my own opinion, if not correct, please correct me:

1) Implementation of IEnumerator is used to implement IEnumerable, which is associated with GetEnumerator methods, so that foreach can be used, and once the traversal (MoveNext) method is determined in a class, Then there is only one way to traverse the collection. The IEnumerator of most collections in the. NET framework iterate through the collection by default in a forward-only read-only manner.

2) iterator is used to traverse the collection, there can be multiple implementations, the only requirement is to return ienumerator<t> In a sense, iterator is IEnumerator. The difference between the two is that once the former is determined, it can only be used to iterate through the collection and return a IEnumerator, while the latter can traverse the collection in several ways and return different IEnumerator. (I think the difference is similar to the difference between IComparable and IComparer).

C # Collection-enumeration (enumeration)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.