中Iterators的改进与实现原理浅析
分类:高并发

C#语言从VB中吸取了一个非常实用的foreach语句。对所有支持IEnumerable接口的类的实例,foreach语句使用统一的接口遍历其子项,使得以前冗长的for循环中繁琐的薄记工作完全由编译器自动完成。支持IEnumerable接口的类通常用一个内嵌类实现IEnumerator接口,并通过IEnumerable.GetEnumerator函数,允许类的使用者如foreach语句完成遍历工作。
这一特性使用起来非常方便,但需要付出一定的代价。Juval Lowy发表在MSDN杂志2004年第5期上的Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes一文中,较为详细地介绍了C# 2.0中迭代支持和其他新特性。

yield是C#为了简化遍历操作实现的语法糖,我们知道如果要要某个类型支持遍历就必须要实现系统接口IEnumerable,这个接口后续实现比较繁琐要写一大堆代码才能支持真正的遍历功能。举例说明

六.枚举集合

又是两个以rable和rator结尾的两个接口,每次看到这种定义我就会很头疼,很容易搞混掉,所以这次我要将自己的学习记录下来,慢慢的把它们给啃掉,好记性不如烂笔头,也方便今后自己的复习。

首先,因为IEnumerator.Current属性是一个object类型的值,所以值类型(value type)集合在被foreach语句遍历时,每个值都必须经历一次无用的box和unbox操作;就算是引用类型(reference type)集合,在被foreach语句使用时,也需要有一个冗余的castclass指令,保障枚举出来的值进行类型转换的正确性。

图片 1

  在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中元素的个数。foreach语句使用一个枚举器。foreach会调用实现了IEnumerable接口的集合类中的GetEumerator()方法。GetEumerator()方法返回一个实现IEnumerator接口的对象枚举。foreach语句就可以使用IEnumerable接口迭代集合了。

通常在编程中我们常常面临这样一个问题就是对一个集合的遍历通常也就是一个数组或是一个链表,比如说一个保存了整个班级名单的变量还是一个Person类型的数组,那我们该怎么去做。当然在C#中我们可以使用foreach语句来轻松的完成遍历:

以下为引用:

**using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

  GetEumerator()方法在IEnumerable接口中定义。

 

using System.Collections;

namespace**

  1.IEnumerator接口

foreach (Person item in personArray)
 {
      //...do something
 }

public class Tokens : IEnumerable
{
...
Tokens f = new Tokens(...);

**{
    class Program
    {
        static void Main(string[] args)
        {
            HelloCollection helloCollection = new HelloCollection();
            foreach (string s in helloCollection)
            {
                Console.WriteLine(s);
            }

  foreach语句使用IEnumerator接口的方法和属性,迭代集合中所有元素。IEnumerator接口定义了Current属性,来返回光标所在的元素,该接口的MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合不再有更多的元素,该方法就返回false.

 

foreach (string item in f)
{
Console.WriteLine(item);
}
...
}

            Console.ReadKey();
        }
    }

  这个接口的泛型版本IEnumerator<T>派生自接口IDisposable,因此定义了Dispose()方法,来清理枚举器占用的资源。

但这里有个前提条件就是personArray对象类型它必须实现IEnumerable才能够使用foreach语句来进行遍历,看看IEnumerable接口的定义,它只有一个方法:

上面的简单代码被自动转换为

    //public class HelloCollection : IEnumerable
    //{
    //    public IEnumerator GetEnumerator()
    //    {
    //        yield return "Hello";
    //        yield return "World";
    //    }
    //}**

  2.foreach语句

 

以下为引用:

**    public class HelloCollection : IEnumerable
    {
        public IEnumerator GetEnumerator()
        {
            Enumerator enumerator = new Enumerator(0);
            return enumerator;
        }

  C#中foreach语句不会解析为IL代码中的foreach语句。C#编译器会把foreach语句转换为IEnumerator接口的方法和属性。
  Person[] persons = {
    new Person { FirstName="Damon", LastName="Hill" },
    new Person { FirstName="Niki", LastName="Lauda" },
    new Person { FirstName="Ayrton", LastName="Senna" },
    new Person { FirstName="Graham", LastName="Hill" }
  };
  foreach (Person p in persons)
  {
    Console.WriteLine(p);
  }

public interface IEnumerable
{
      IEnumerator GetEnumerator();
}

Tokens f = new Tokens(...);

        public class Enumerator : IEnumerator, IDisposable
        {
            private int state;
            private object current;

  foreach语句会解析为下面的代码:
  IEnumerator<Person> enumerator = persons.GetEumerator();
  while(enumerator.MoveNext())
  {
    Person p = enumerator.Current;
    Console.WriteLine(p);
  }

 

IEnumerator enum = f.GetEnumerator();
try
{
do {
string item = (string)enum.get_Current(); // 冗余转换

            public Enumerator(int state)
            {
                this.state = state;
            }

  3.yield语句

而且该方法的返回类型正是IEnumerator接口,为什么要返回这么一个接口呢,同样看下它的定义:

Console.WriteLine(item);
} while(enum.MoveNext());
}
finally
{
if(enum is IDisposable) // 需要验证实现IEnumerator接口的类是否支持IDisposable接口
{
((IDisposable)enum).Dispose();
}
}

            public bool MoveNext()
            {
                switch (state)
                {
                    case 0:
                        current = "Hello";
                        state = 1;
                        return true;
                    case 1:
                        current = "World";
                        state = 2;
                        return true;
                    case 2:
                        break;
                }
                return false;
            }

  在C#2.0之前,foreach语句可以轻松的迭代集合,但创建枚举器需要做大量的工作。C#2.0添加了yield语句,以便创建枚举器。
  yield return 语句返回集合的一个元素,并移动到下一个元素。yield break可停止迭代。

 

好在C# 2.0中支持了泛型(generic)的概念,提供了强类型的泛型版本IEnumerable定义,伪代码如下:

            public void Reset()
            {
                throw new NotSupportedException();
            }

  下面的例子实现返回两个字符串:
  public class HelloCollection
  {
    public IEnumerator<string> GetEnumerator()
    {
    yield return "Hello";
    yield return "World";
    }
  }
  客户端代码:
  var helloCollection = new HelloCollection();
  foreach (string s in helloCollection)
  {
    Console.WriteLine(s);
  }

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

以下为引用:

            public object Current
            {
                get { return current; }
            }**

  包含yield语句的方法或属性也称为迭代块。迭代块必须声明为返回IEnumerator或IEnumerable接口,或者这些接口的泛型版本。这个块可以包含多条yield return语句或yield break语句,但不能包含return语句。

 

namespace System.Collections.Generic
{
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator : IDisposable
{
ItemType Current{get;}
bool MoveNext();
}
}

            public void Dispose()
            {
            }
        }
    }
}

  使用迭代块,编译器会生成一个yield类型,其中包含一个状态机,如下面代码所示:

 

这样一来即保障了遍历集合时的类型安全,又能够对集合的实际类型直接进行操作,避免冗余转换,提高了效率。

图片 2

  yield类型实现IEnumerator和IDisposable接口的方法和属性。下面的例子可以把yield类型看作内部类Enumerator。外部类的GetEnumerator()方法实例化并返回一个新的yield类型。在yield类型中,变量state定义了迭代的当前位置,每次调用MoveNext()时,当前位置都会改变。MoveNext()封装了迭代块的代码,并设置了current变量的值,从而使Current属性根据位置返回一个对象。
  public class HelloCollection
  {
    public IEnumerator<string> GetEnumerator()
    {
      return new Enumerator(0);
    }

   这个接口有三个方法,最后一个方法暂时先不管,主要看前面两个,似乎有点明白了,从方法名字上来看MoveNext:Boolean)移动到下一个元素,如果有下一个元素就是返回true否则就是false。比如当前遍历到了某个同学,要是他不是最后一个说明还可以继续遍历返回true,如果是最后一个了说明不能在往下遍历,再往下可能就是别的班的同学了,此时返回一个false。在看下一个Current,它不是一个方法而是属性,只有一个get方法说明它是只读的,返回值是object可以是任意的对象。那么它的作用就显而易见了就是获取当前遍历位置下的元素了。从以上可以看出真正的遍历实现是由IEnumerator来实现的,IEnumerable规定了该类是可用foreach遍历的,且返回一个遍历的具体实现类IEnumerator。就好像学校过来检查要点名了,任课老师(IEnumerable)懒的自己一个一个点就吩咐记录委员(IEnumerator)来点名,记录委员就不停的MoveNext:Boolean)、MoveNext:Boolean)…点到的同学呢(Current)就喊一声到,一直点到最后一个同学(返回false)才停止。这时假如任课老师觉得人数不对来的人也太少了怎么就全到了呢,就要求重点(这里就相当于Reset)了),无奈职责所在只好在foreach一遍…

以下为引用:

 

  public class Enumerator:IEnumerator<string>,IEnumerator,IDisposable
  {
    private int state;
    private string current;

         具体代码如下:

using System.Collections.Generic;

    上面注释的部分引用了"yield return”,其功能相当于下面所有代码!可以看到如果不适用yield需要些很多代码来支持遍历操作。

    public Enumerator(int state)
    {
      this.state = state;
    }

 

public class Tokens : IEnumerable
{
... // 实现 IEnumerable 接口

    yield return 表示在迭代中下一个迭代时返回的数据,除此之外还有yield break, 其表示跳出迭代,为了理解二者的区别我们看下面的例子

    bool System.Collections.IEnumerator.MoveNext()
    {
      switch(state)
      {
        case 0:

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Person[] array = { new Person { Name = "叶华斌", Age = 23 },
                               new Person { Name = "王昌文", Age = 22 },
                               new Person { Name = "吴朝剑", Age = 21 }};

Tokens f = new Tokens(...);

图片 3

          current="hello";
          state =1;
          return true;
        case 1:

            PersonArray pa = new PersonArray(array);

foreach (string item in f)
{
Console.WriteLine(item);
}
}

**class A : IEnumerable
{
    private int[] array = new int[10];

          current="world";
          state =2;
          return true;
        case 2:
          break;
      }

            foreach (Person item in pa)
            {
                Console.WriteLine(string.Format("姓名:{0},年龄{1}", item.Name, item.Age));
            }
 
        }
    }

上面的代码被自动转换为

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < 10; i++)
        {
            yield return array[i];
        }
    }
}**

      return false;
    }

    public class PersonArray : IEnumerable
    {
        Person[] array;
        public PersonArray(Person[] array)
        {
            this.array = array;
        }
  
        public IEnumerator GetEnumerator()
        {
            return new PersonEnumerator(array);
        }

以下为引用:

图片 4

    void System.Collection>IEnumerator.Reset()
    {
      throw new NotSupportedException();
    }

    }

Tokens f = new Tokens(...);

 

    string System.Collections.Generic.IEnumerator<string>.Current
    {
      get
      {
        return current;
      }
    }

    public class PersonEnumerator : IEnumerator
    {
        Person[] array;
        int position;
        public PersonEnumerator(Person[] array)
        {
            this.array = array;
            this.position = -1;
        }
        public object Current
        {
            get
            {
                if (-1 < position && position < array.Length)
                {
                    return array[position];
                }
                else
                {
                    throw new IndexOutOfRangeException();
                }
            }
        }

IEnumerator enum = f.GetEnumerator();
try
{
do {
string item = enum.get_Current(); // 无需转换

    如果你只想让用户访问ARRAY的前8个数据,则可做如下修改.这时将会用到yield break,修改函数如下

    object System.Collections.IEnumerator.Current
    {
      get
      {
        return current;
      }
    }

        public bool MoveNext()
        {
            position++;
            return position < array.Length;
        }

Console.WriteLine(item);
} while(enum.MoveNext());
}
finally
{
if(enum) // 无需验证实现IEnumerator接口的类是否支持IDisposable接口,
// 因为所有由编译器自动生成的IEnumerator接口实现类都支持
{
((IDisposable)enum).Dispose();
}
}

图片 5

    void IDisposable.Dispose()
    {}
  }
}

        public void Reset()
        {
            position = -1;
        }
    }
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

除了遍历时的冗余转换降低性能外,C#现有版本另一个不爽之处在于实现IEnumerator接口实在太麻烦了。通常都是由一个内嵌类实现IEnumerator接口,而此内嵌类除了get_Current()函数外,其他部分的功能基本上都是相同的,如

public IEnumerator GetEnumerator()
{
    for (int i = 0; i < 10; i++)
    {
        if (i < 8)
        {
            yield return array[i];
        }
        else
        {
            yield break;
        }
    }
}

  yield语句会产生一个枚举器,而不仅仅生成一个包含的项的列表。这个枚举器通过foreach语句调用。从foreach中依次访问每一项,就会访问枚举器。这样就可以迭代大量的数据,而无需一次把所有的数据都读入内存。
    (1).迭代集合的不同方式
    可以使用yield return语句,以不同方式迭代集合。

 

以下为引用:

图片 6

    类MusicTitles可以用默认方式通过GetEnumerator()方法迭代标题,该方法不必在代码中编写,也可以用Reverse()逆序迭代标题,用Subset()方法迭代子集合:
    public class MusicTitles
    {
      string[] names = {
      "Tubular Bells", "Hergest Ridge",
      "Ommadawn", "Platinum" };

 

public class Tokens : IEnumerable
{
public string[] elements;

 

      public IEnumerator<string> GetEnumerator()
      {
        for (int i = 0; i < 4; i++)
        {
          yield return names[i];
        }
      }

输出结果:

Tokens(string source, char[] delimiters)
{
// Parse the string into tokens:
elements = source.Split(delimiters);
}

    这样,则只会返回前8个数据.

      public IEnumerable<string> Reverse()
      {
        for (int i = 3; i >= 0; i--)
        {
          yield return names[i];
        }
      }

 图片 7

public IEnumerator GetEnumerator()
{
return new TokenEnumerator(this);
}

      public IEnumerable<string> Subset(int index, int length)
      {
        for (int i = index; i < index + length;i++)
        {
          yield return names[i];
        }
      }
    }
    客户端代码:
    var titles = new MusicTitles();
    foreach (var title in titles)
    {

值得一看的是反编译之后它的具体实现如何,来看下:

// Inner class implements IEnumerator interface:
private class TokenEnumerator : IEnumerator
{
private int position = -1;
private Tokens t;

      Console.WriteLine(title);
    }
    Console.WriteLine();

 

public TokenEnumerator(Tokens t)
{
this.t = t;
}

    Console.WriteLine("reverse");
    foreach (var title in titles.Reverse())
    {

Person[] array;
    PersonArray pa;
    Person item;
    Person <>g__initLocal0;
    Person <>g__initLocal1;
    Person <>g__initLocal2;
    Person[] CS$0$0000;
    IEnumerator CS$5$0001;
    bool CS$4$0002;
    IDisposable CS$0$0003;
    CS$0$0000 = new Person[3];
    <>g__initLocal0 = new Person();
    <>g__initLocal0.Name = "叶华斌";
    <>g__initLocal0.Age = 0x17;
    CS$0$0000[0] = <>g__initLocal0;
    <>g__initLocal1 = new Person();
    <>g__initLocal1.Name = "王昌文";
    <>g__initLocal1.Age = 0x16;
    CS$0$0000[1] = <>g__initLocal1;
    <>g__initLocal2 = new Person();
    <>g__initLocal2.Name = "吴朝剑";
    <>g__initLocal2.Age = 0x15;
    CS$0$0000[2] = <>g__initLocal2;
    array = CS$0$0000;
    pa = new PersonArray(array);
    //以上做些变量声明的工作

// Declare the MoveNext method required by IEnumerator:
public bool MoveNext()
{
if (position < t.elements.Length - 1)
{
position++;
return true;
}
else
{
return false;
}
}

      Console.WriteLine(title);
    }
    Console.WriteLine();

    CS$5$0001 = pa.GetEnumerator();//①先调用IEnumerable中得方法返回一个迭代器IEnumerator也就是PersonEnumerator
Label_0084:
    try
    {
        goto Label_00B6;//②使用goto语句跳到下面Label_00B6:处
    Label_0086:
        item = (Person) CS$5$0001.Current;//④存在就通过Current属性返回当前值
        Console.WriteLine(string.Format("姓名:{0},年龄{1}", item.Name, (int) item.Age));
    Label_00B6:
        if (CS$5$0001.MoveNext() != null)//③移动到下一个元素判断是否存在
        {
            goto Label_0086;
        }
        goto Label_00E2;//⑤不存在就跳出foreach语句
    }
    finally
    {
    Label_00C5:
        CS$0$0003 = CS$5$0001 as IDisposable;
        if ((CS$0$0003 == null) != null)
        {
            goto Label_00E1;
        }
        CS$0$0003.Dispose();
    Label_00E1:;
    }
Label_00E2:
return;

// Declare the Reset method required by IEnumerator:
public void Reset()
{
position = -1;
}

    Console.WriteLine("subset");
    foreach (var title in titles.Subset(2, 2))
    {

 

// Declare the Current property required by IEnumerator:
public object Current
{
get // get_Current函数
{
return t.elements[position];
}
}
}
...
}

      Console.WriteLine(title);
    }

 

内嵌类TokenEnumerator的position和Tokens实际上是每个实现IEnumerator接口的类共有的,只是Current属性的get函数有所区别而已。这方面C# 2.0做了很大的改进,增加了yield关键字的支持,允许代码逻辑上的重用。上面冗长的代码在C# 2.0中只需要几行,如

    (2).用yield return 返回枚举器

大概执行顺序是这样的:

1.       执行foreach语句前先调用IEnumerable.GetEnumorator()返回一个IEnumerator类型的枚举器。

2.       调用IEnumerator.MoveNext()从前一个元素的位置移动到下一个

3.       判断是否是最后一个元素,是跳出循环,否往下执行

4.       调用IEnumerator.Current属性返回当前的元素

5.       执行foreach语句块内的内容

6.       跳至第2步

以下为引用:

      

好了到这一步我们需要的功能也都已经完成了,但总感觉一路走来有点艰辛呀,为什么呢,因为这个PersonEnumerator类让我们的实现变得复杂了许多,接下来再来看一种简洁的形式:

using System.Collections.Generic;

public class GameMoves
  {
    private IEnumerator cross;
    private IEnumerator circle;

    public GameMoves()
    {
      cross = Cross();
      circle = Circle();
    }

    private int move = 0;
    const int MaxMoves = 9;

    public IEnumerator Cross()
    {
      while (true)
      {
        Console.WriteLine("Cross, move {0}", move);
        if (++move >= MaxMoves)
          yield break;
        yield return circle;
      }
    }

    public IEnumerator Circle()
    {
      while (true)
      {
        Console.WriteLine("Circle, move {0}", move);
        if (++move >= MaxMoves)
          yield break;
        yield return cross;
      }
    }
  }

 

public class Tokens : IEnumerable
{
public IEnumerator GetEnumerator()
{
for(int i = 0; i yield elements[i];
}
...
}

 

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Person[] array = { new Person { Name = "叶华斌", Age = 23 },
                               new Person { Name = "王昌文", Age = 22 },
                               new Person { Name = "吴朝剑", Age = 21 }};

GetEnumerator函数是一个C# 2.0支持的迭代块(iterator block),通过yield告诉编译器在什么时候返回什么值,再由编译器自动完成实现IEnumerator接口的薄记工作。而yield break语句支持从迭代块中直接结束,如

    客户端代码:
    var game = new GameMoves();

            PersonArray pa = new PersonArray(array);

以下为引用:

    IEnumerator enumerator = game.Cross();
    while (enumerator.MoveNext())
    {
      enumerator = enumerator.Current as IEnumerator;
    }

            foreach (Person item in pa)
            {
                Console.WriteLine(string.Format("姓名:{0},年龄{1}", item.Name, item.Age));
            }

public IEnumerator GetEnumerator()
{
for(int i = 1;i< 5;i++)
{
yield return i;
if(i > 2)
yield break; // i > 2 时结束遍历
}
}

    这样会交替调用Cross()和Circle()方法。

        }
    }

这样一来,很容易就能实现IEnumerator接口,并可以方便地支持在一个类中提供多种枚举方式,如

七.元组(Tuple)

    public class PersonArray : IEnumerable
    {
        Person[] array;
        public PersonArray(Person[] array)
        {
            this.array = array;
        }

以下为引用:

  元组可以合并不同类型的对象。元组起源于函数编程语言,如F#。在.NET Framework中,元组可用于所有的.Net语言。
  .NET Framework定义了8个泛型Tuple类和一个静态Tuple类,它们用作元组的工厂。不同的泛型Tuple类支持不同数量的元素。如,Tuple<T1>包含一个元素,Tuple<T1,T2>包含两个元素。
  Tuple<string, string> name = new Tuple<string, string>("Jochen", "Rindt");

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < array.Length; i++)
            {
                yield return array[i];
            } 
        }

public class CityCollection
{
string[] m_Cities = {"New York","Paris","London"};
public IEnumerable Reverse
{
get
{
for(int i=m_Cities.Length-1; i>= 0; i--)
yield m_Cities[i];
}
}
}

  元组也可以用静态Tuple类的静态Create()方法创建。Create()方法的泛型参数定了要实例化的元组类型:
  public static Tuple<int, int> Divide(int dividend, int divisor)
  {
    int result = dividend / divisor;
    int reminder = dividend % divisor;

    }

接下来我们看看如此方便的语言特性背后,编译器为我们做了哪些工作。以上面那个支持IEnumerable接口的Tokens类为例,GetEnumerator函数的代码被编译器用一个类包装起来,伪代码如下

    return Tuple.Create<int, int>(result, reminder);
  }

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

以下为引用:

  可以用属性Item1和Item2访问元组的项:
  var result = Divide(5, 2);
  Console.WriteLine("result of division: {0}, reminder: {1}", result.Item1, result.Item2);

 

public class Tokens : IEnumerable
{
private sealed class GetEnumerator$00000000__IEnumeratorImpl
: IEnumerator, IEnumerator, IDisposable
{
private int $PC = 0;
private string $_current;
private Tokens ;
public int i$00000001 = 0;

  如果元组包含的项超过8个,就可以使用带8个参数的Tuple类定义。最后一个模板参数是TRest,表示必须给它传递一个元组。这样,就可以创建带任意个参数的元组了。
  var tuple = Tuple.Create<string, string, string, int, int, int, double, Tuple<int, int>>(
  "Stephanie", "Alina", "Nagel", 2009, 6, 2, 1.37, Tuple.Create<int, int>(52, 3490));

 

// 实现 IEnumerator 接口
string IEnumerator.get_Current()
{
return $_current;
}

八.结构比较

通过yield return关键字就可以省去编写PersonEnumerator,通过反编译工具看到PersonArray实现代码:

bool IEnumerator.MoveNext()
{
switch($PC)
{
case 0:
{
$PC = -1;
i$00000001 = 0;
break;
}
case 1:
{
$PC = -1;
i$00000001++;
break;
}
default:
{
return false;
}
}

  数组和元组都实现接口IStructuralEquatable和IStructuralComparable。这两个接口不仅可以比较引用,还可以比较内容。这些接口都是显式实现的,所以在使用时需要把数组和元组强制转换为这个接口。

 

if(i$00000001 < .elements.Length)
{
$_current = .elements[i$00000001];
$PC = 1;

  IStructuralEquatable接口用于比较两个元组或数组是否有相同的内同,IStructuralComparable接口用于给元组或数组排序。

public class PersonArray : IEnumerable
{
    // Fields
    private Person[] array;

return true;
}
else
{
return false;
}
}

  IStructuralEquatable接口示例:

    // Methods
    public PersonArray(Person[] array);
    public IEnumerator GetEnumerator();

// 实现 IEnumerator 接口
void IEnumerator.Reset()
{
throw new Exception();
}

  编写实现IEquatable接口的Person类,IEquatable接口定义了一个强类型化的Equals()方法,比较FirstName和LastName的值:

    // Nested Types
    [CompilerGenerated]
    private sealed class <GetEnumerator>d__0 : IEnumerator<object>, IEnumerator, IDisposable
    {
        // Fields
        private int <>1__state;
        private object <>2__current;
        public PersonArray <>4__this;
        public int <i>5__1;

string IEnumerator.get_Current()
{
return $_current;
}

    

        // Methods
        [DebuggerHidden]
        public <GetEnumerator>d__0(int <>1__state);
        private bool MoveNext();
        [DebuggerHidden]
        void IEnumerator.Reset();
        void IDisposable.Dispose();

bool IEnumerator.MoveNext()
{
return IEnumerator.MoveNext(); // 调用 IEnumerator 接口的实现
}

public class Person : IEquatable<Person>
  {
    public int Id { get; private set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override string ToString()
    {
      return String.Format("{0}, {1} {2}", Id, FirstName, LastName);
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return base.Equals(obj);
        return Equals(obj as Person);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }

    #region IEquatable<Person> Members

    public bool Equals(Person other)
    {
      if (other == null)
        return base.Equals(other);

      return this.FirstName == other.FirstName && this.LastName == other.LastName;
    }

    #endregion
  }

        // Properties
        object IEnumerator<object>.Current { [DebuggerHidden] get; }
        object IEnumerator.Current { [DebuggerHidden] get; }
    }
}

// 实现 IDisposable 接口
void Dispose()
{
}
}

  创建两个包含相同内容的Person类型的数组:
  var janet = new Person { FirstName = "Janet", LastName = "Jackson" };
  Person[] persons1 = { new Person { FirstName = "Michael", LastName = "Jackson" }, janet };
  Person[] persons2 = { new Person { FirstName = "Michael", LastName = "Jackson" }, janet };

 

public IEnumerator GetEnumerator()
{
GetEnumerator$00000000__IEnumeratorImpl impl = new GetEnumerator$00000000__IEnumeratorImpl();

  由于两个变量引用两个不同数组,所以!=返回True:
  if (persons1 != persons2)
    Console.WriteLine("not the same reference");

原来编译器通过内嵌类来帮我们实现了PersonEnumerator,只是名字不同叫做<GetEnumerator>d__0罢啦。

impl. = this;

  对于IStructuralEquatable接口定义的Equals方法,第一个参数是object类型,第二个参数是IEqualityComparer类型。调用这个方法时,通过传递一个实现了EqualityComparer<T>的对象,就可以定义如何进行比较。通过EqualityComparer<T>类完成IEqualityComparer的一个默认实现。这个实现检查T类型是否实现了IEquatable接口,并调用IEquatable.Equals()方法。如果该类没有实现IEquatable接口,就调用Object基类中Equals()方法:
  if ((persons1 as IStructuralEquatable).Equals(persons2, EqualityComparer<Person>.Default))
  {
    Console.WriteLine("the same content");
  }

最后再来一个泛型版本的:

return impl;
}
}

  元组示例:

 

从上面的伪代码中我们可以看到,C# 2.0编译器实际上维护了一个和我们前面实现IEnumerator接口的TokenEnumerator类非常类似的内部类,用来封装IEnumerator接口的实现。而这个内嵌类的实现逻辑,则根据GetEnumerator定义的yield返回地点决定。
我们接下来看一个较为复杂的迭代块的实现,支持递归迭代(Recursive Iterations),代码如下:

  Tuple<>类提供了两个Epuals()方法:一个重写了Object基类中的Epuals方法,并把object作为参数,第二个由IStructuralEquatable接口定义,并把object和IEqualityComparer作为参数。

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Person[] array = { new Person { Name = "叶华斌", Age = 23 },
                               new Person { Name = "王昌文", Age = 22 },
                               new Person { Name = "吴朝剑", Age = 21 }};

以下为引用:

  var t1 = Tuple.Create<int, string>(1, "Stephanie");
  var t2 = Tuple.Create<int, string>(1, "Stephanie");
  if (t1 != t2)
  Console.WriteLine("not the same reference to the tuple");

            MyList<Person> pa = new MyList<Person>(array);

using System;
using System.Collections.Generic;

  这个方法使用EqualityComparer<object>.Default获取一个ObjectEqualityComparer<object>,以进行比较。这样就会调用Object.Equals()方法比较元组的每一项:
  if (t1.Equals(t2))
    Console.WriteLine("equals returns true");

            foreach (Person item in pa)
            {
                Console.WriteLine(string.Format("姓名:{0},年龄{1}", item.Name, item.Age));
            }
 
        }
    }

class Node
{
public Node LeftNode;
public Node RightNode;
public T Item;
}

  还可以使用TupleComparer类创建一个自定义的IEqualityComparer
  TupleComparer tc = new TupleComparer();

    public class MyList<T> :IEnumerable<T>
    {
        T[] array;
        public MyList(T[] array)
        {
            this.array = array;
        }
       
        public IEnumerator<T> GetEnumerator()
        {
            return new MyListEnumerator<T>(array);
            
        }

public class BinaryTree
{
Node m_Root;

  if ((t1 as IStructuralEquatable).Equals(t2, tc))
  {
    Console.WriteLine("yes, using TubpleComparer");
  }

        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

public void Add(params T[] items)
{
foreach(T item in items)
Add(item);
}

  class TupleComparer : IEqualityComparer
  {
    #region IEqualityComparer Members

    public class MyListEnumerator<T> : IEnumerator<T>
    {
        T[] array;
        int position;
        public MyListEnumerator(T[] array)
        {
            this.array = array;
            this.position = -1;
        }
         object IEnumerator.Current
        {
            get
            {
                if (-1 < position && position < array.Length)
                {
                    return array[position];
                }
                else
                {
                    throw new IndexOutOfRangeException();
                }
            }
        }

public void Add(T item)
{
// ...
}

    public new bool Equals(object x, object y)
    {
      bool result = x.Equals(y);
      return result;
    }

        public bool MoveNext()
        {
            position++;
            return position < array.Length;
        }

public IEnumerable InOrder
{
get
{
return ScanInOrder(m_Root);
}
}

    public int GetHashCode(object obj)
    {
      return obj.GetHashCode();
    }

        public void Reset()
        {
            position = -1;
        }

IEnumerable ScanInOrder(Node root)
{
if(root.LeftNode != null)
{
foreach(T item in ScanInOrder(root.LeftNode))
{
yield item;
}
}

    #endregion
  }

        T IEnumerator<T>.Current
        {
            get
            {
                if (-1 < position && position < array.Length)
                {
                    return array[position];
                }
                else
                {
                    throw new IndexOutOfRangeException();
                }
            }
        }

yield root.Item;

 

        public void Dispose()
        {
            
        }
    }

if(root.RightNode != null)
{
foreach(T item in ScanInOrder(root.RightNode))
{
yield item;
}
}
}
}

如果觉得鄙文有用,请给杯咖啡钱:

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

BinaryTree提供了一个支持IEnumerable接口的InOrder属性,通过ScanInOrder函数遍历整个二叉树。因为实现IEnumerable接口的不是类本身,而是一个属性,所以编译器首先要生成一个内嵌类支持IEnumerable接口。伪代码如下

  图片 8

 

以下为引用:

  

public class BinaryTree
{
private sealed class ScanInOrder$00000000__IEnumeratorImpl
: IEnumerator, IEnumerator, IDisposable
{
BinaryTree ;
Node root;

从上面的代码我们也可以对类库中得List、List<T>类有个大概的了解了,上面的MyList<T>的类相当于一个简略版本List<T>,它仅仅提供对列表的遍历,没有实现对元素操作罢了。

// ...
}

 

private sealed class ScanInOrder$00000000__IEnumerableImpl
: IEnumerable, IEnumerable
{
BinaryTree ;
Node root;

IEnumerator IEnumerable.GetEnumerator()
{
ScanInOrder$00000000__IEnumeratorImpl impl = new ScanInOrder$00000000__IEnumeratorImpl();

impl. = this.;
impl.root = this.root;

return impl;
}

IEnumerator IEnumerable.GetEnumerator()
{
ScanInOrder$00000000__IEnumeratorImpl impl = new ScanInOrder$00000000__IEnumeratorImpl();

impl. = this.;
impl.root = this.root;

return impl;
}
}

IEnumerable ScanInOrder(Node root)
{
ScanInOrder$00000000__IEnumerableImpl impl = new ScanInOrder$00000000__IEnumerableImpl();

impl. = this;
impl.root = root;

return impl;
}
}

因为ScanInOrder函数内容需要用到root参数,故而IEnumerable和IEnumerator接口的包装类都需要有一个root字段,保存传入ScanInOrder函数的参数,并传递给最终的实现函数。
实现IEnumerator接口的内嵌包装类ScanInOrder$00000000__IEnumeratorImpl实现原理与前面例子里的大致相同,不同的是程序逻辑大大复杂化,并且需要用到IDisposable接口完成资源的回收。

以下为引用:

public class BinaryTree
{
private sealed class GetEnumerator$00000000__IEnumeratorImpl
: IEnumerator, IEnumerator, IDisposable
{
private int $PC = 0;
private string $_current;
private Tokens ;
public int i$00000001 = 0;

public IEnumerator __wrap$00000003;
public IEnumerator __wrap$00000004;
public T item$00000001;
public T item$00000002;
public Node root;

// 实现 IEnumerator 接口
string IEnumerator.get_Current()
{
return $_current;
}

bool IEnumerator.MoveNext()
{
switch($PC)
{
case 0:
{
$PC = -1;
if(root.LeftNode != null)
{
__wrap$00000003 = .ScanInOrder(root.LeftNode).GetEnumerator();

goto ScanLeft;
}
else
{
goto GetItem;
}
}
case 1:
{
return false;
}
case 2:
{
goto ScanLeft;
}
case 3:
{
$PC = -1;
if(root.RightNode != null)
{
__wrap$00000004 = .ScanInOrder(root.RightNode).GetEnumerator();

goto ScanRight;
}
else
{
return false;
}
break;
}
case 4:
{
return false;
}
case 5:
{
goto ScanRight;
}
default:
{
return false;
}
ScanLeft:
$PC = 1;

if(__wrap$00000003.MoveNext())
{
$_current = item$00000001 = __wrap$00000003.get_Current();
$PC = 2;
return true;
}

GetItem:
$PC = -1;
if(__wrap$00000003 != null)
{
((IDisposable)__wrap$00000003).Dispose();
}
$_current = root.Item;
$PC = 3;
return true;

ScanRight:
$PC = 4;

if(__wrap$00000004.MoveNext())
{
$_current = $item$00000002 = __wrap$00000004.get_Current();
$PC = 5;
return true;
}
else
{
$PC = -1;
if(__wrap$00000004 != null)
{
((IDisposable)__wrap$00000004).Dispose();
}
return false;
}
}
// 实现 IDisposable 接口
void Dispose()
{
switch($PC)
{
case 1:
case 2:
{
$PC = -1;
if(__wrap$00000003 != null)
{
((IDisposable)__wrap$00000003).Dispose();
}
break;
}
case 4:
case 5:
{
$PC = -1;
if(__wrap$00000004 != null)
{
((IDisposable)__wrap$00000004).Dispose();
}
break;
}
}
}
}
}

通过上面的伪代码,我们可以看到,C# 2.0实际上是通过一个以$PC为自变量的有限状态机完成的递归迭代块,这可能是因为有限状态机可以很方便地通过程序自动生成吧。而Dispose()函数则负责处理状态机的中间变量。

有兴趣进一步了解迭代特性的朋友,可以到Grant Ri的BLog上阅读Iterators相关文章。
在了解了Iterators的实现原理后,再看那些讨论就不会被其表象所迷惑了 :D

本文由10bet手机官网发布于高并发,转载请注明出处:中Iterators的改进与实现原理浅析

上一篇:0语言规范匿名方法,中的多播委托笔记总结【10bet体育中文官网】 下一篇:没有了
猜你喜欢
热门排行
精彩图文