1. 程式人生 > >C#學習筆記(三)—–C#高階特性:列舉型別和迭代

C#學習筆記(三)—–C#高階特性:列舉型別和迭代

C#學習筆記(三)—–try語句和異常

列舉型別

  • enumerator是隻讀的、只能在序列的值上向前移動的遊標。一個enumerator是一個實現了下列任一介面的物件:
    System.Collections.IEnumerator
    System.Collections.Generic.IEnumerator<T>
    技術上講,任何一個實現了MoveNext方法和Current屬性的物件都可以被看作是一個enumerator(這就叫做鴨子型別,duck type)。這個寬泛的概念是在C#1.0的時候被引進的,目的是不用裝箱和拆箱就可以對值型別的元素進行列舉。但是在C#2.0中,由於泛型的引入,這個功能變得多餘了(這個優點在泛化的概念出現後就不存在了,而且實際上C#2.0已經不支援了)。
  • foreach語句可以用來在可列舉的物件上執行迭代操作,一個可列舉的物件是一個序列的邏輯上的表現。它本身不是一個遊標,但他自身會產生遊標(S)。一個可列舉物件可以是:
    ①:實現了IEnumerable or IEnumerable<T>介面
    ②:具有一個GetEnumerator方法並且返回一個enumerator
    提示IEnumerator 和 IEnumerable 定義在 System.Collections名稱空間中,IEnumerator<T> 和 IEnumerable<T> 定義在System.Collections.Generic名稱空間中

  • 列舉模式如下所示:

class Enumerator // 通常實現了IEnumerator或IEnumerator<T>
{
public IteratorVariableType Current { get {...} }
public bool MoveNext() {...}
}
class Enumerable // 通常實現了IEnumerable 或 IEnumerable<T>
{
public Enumerator GetEnumerator() {...}
}
  • 下面是用高階方法即foreach語句實現對單詞beer的迭代:
foreach (char
c in "beer") Console.WriteLine (c);
  • 下面使用低階方法即不用foreach語句,實現對單詞beer的迭代:
using (var enumerator = "beer".GetEnumerator())
while (enumerator.MoveNext())
{
var element = enumerator.Current;
Console.WriteLine (element);
}

如果enumerator實現了IDisposable,那麼foreach語句也實現了using語句的作用,可以隱式釋放enumerator物件。

集合初始化方法

  • 可以通過一個簡單的步驟建立和填充一個可列舉的物件:
using System.Collections.Generic;
...
List<int> list = new List<int> {1, 2, 3};

編譯器將這部分翻譯為下面的程式碼:

using System.Collections.Generic;
...
List<int> list = new List<int>();
list.Add (1);
list.Add (2);
list.Add (3);

這要求可列舉的物件實現System.Collections.IEnumerable介面,並且有add方法以及適合的引數來被呼叫。

迭代器

  • 和foreach是可列舉物件的使用者相對應的是迭代器是可列舉物件的生產者。本例中我們使用迭代器返回斐波那契數列表(斐波那契數列的定義是每個數字是前兩個數字之和):
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
foreach (int fib in Fibs(6))
Console.Write (fib + " ");
}
static IEnumerable<int> Fibs (int fibCount)
{
for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
{
yield return prevFib;
int newFib = prevFib+curFib;
prevFib = curFib;
curFib = newFib;
}
}
}

鑑於return語句表達的是“這是你要求我從方法返回的值”的意思,yield return語句表達的是“這是你要求我從enumerator產生的下一個元素”,在每一個yield語句中,控制返回呼叫者(caller),但是被呼叫者(callee)的狀態被保持以便呼叫者(caller)想要列舉下一個值的時候立刻繼續執行。這個狀態的生命週期被繫結到enumerator上,以便當呼叫者結束列舉時狀態可以被及時釋放。
提示:編譯器將迭代方法轉換為實現了IEnumerable<T> 與/或 IEnumerator<T>.的私有類,迭代器的邏輯在編譯器寫的列舉類中,被反轉並連線MoveNEext方法和Current屬性,這表明當呼叫迭代方法時,所做的只是例項化編譯器寫的類,編寫的程式碼並不真正執行,編寫的程式碼只有在開始遍歷結果序列時才會真正執行,典型的如foreach語句。下面是Fibs方法在後臺生成的類:

[CompilerGenerated]
private sealed class <Fibs>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
// Fields
private int <>1__state;
private int <>2__current;
public int <>3__fibCount;
private int <>l__initialThreadId;
public int <curFib>5__3;
public int <i>5__1;
public int <newFib>5__4;
public int <prevFib>5__2;
public int fibCount;
// Methods
[DebuggerHidden]
public <Fibs>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<i>5__1 = 0;
this.<prevFib>5__2 = 1;
this.<curFib>5__3 = 1;
while (this.<i>5__1 < this.fibCount)
{
this.<>2__current = this.<prevFib>5__2;
this.<>1__state = 1;
return true;
Label_0057:
this.<>1__state = -1;
this.<newFib>5__4 = this.<prevFib>5__2 + this.<curFib>5__3;
this.<prevFib>5__2 = this.<curFib>5__3;
this.<curFib>5__3 = this.<newFib>5__4;
this.<i>5__1++;
}
break;
case 1:
goto Label_0057;
}
return false;
}
[DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
Program.<Fibs>d__0 d__;
if ((Environment.CurrentManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
{
this.<>1__state = 0;
d__ = this;
}
else
{
d__ = new Program.<Fibs>d__0(0);
}
Page 1 of 2
about:blank 2017-05-22
d__.fibCount = this.<>3__fibCount;
return d__;
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator() =>
this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
}
// Properties
int IEnumerator<int>.Current =>
this.<>2__current;
object IEnumerator.Current =>
this.<>2__current;
}
Collapse Methods
Page 2 of 2
about:blank 2017-05-22

迭代器語義

  • 迭代器是包含一個或多個yield語句的方法、屬性、或索引器。迭代器必須返回下列四個介面之一(否則,編譯器會報錯):
    //IEnumerable介面
    System.Collections.IEnumerable
    System.Collections.Generic.IEnumerable<T>
    //IEnumerator介面
    System.Collections.IEnumerator
    System.Collections.Generic.IEnumerator<T>
    返回enumerable和返回enumerator的介面具有不同語義,我們將在後續的筆記中做詳細的介紹。
  • 允許使用多個yield語句:
class Test
{
static void Main()
{
foreach (string s in Foo())
Console.WriteLine(s); // Prints "One","Two","Three"
}
static IEnumerable<string> Foo()
{
yield return "One";
yield return "Two";
yield return "Three";//使用多個yield語句
}
}
  • yield break語句:該語句表明迭代器不返回後面的元素而提前結束。我們可以把Foo修改成下面的示例:
static IEnumerable<string> Foo (bool breakEarly)
{
yield return "One";
yield return "Two";
if (breakEarly)
yield break;
yield return "Three";
}

在迭代器塊中使用return語句是不合法的,應該用yield return來代替。

迭代器和try/catch/finally

  • yield語句不能出現在帶catch塊的try語句塊中。
IEnumerable<string> Foo()
{
try { yield return "One"; } // 不合法
catch { ... }
}

yield return也不能出現在catch和finally語句塊中,出現這些限制的原因是編譯器必須將迭代器轉換為帶有state、current、movenext和dispose成員的普通類。而且轉換普通類可能會大大增加程式碼的複雜性。
但是,可以在只帶有finally塊的try語句塊中使用yield return:

IEnumerable<string> Foo()
{
try { yield return "One"; } // OK
finally { ... }
}

當列舉器到達序列末尾或者呼叫dispose時候,finally塊中程式碼就會執行了,如果你提前使用了break語句,foreach語句會隱式的中斷列舉器。這是一種正確的使用列舉器的方法。當顯式的使用一個列舉器時,有一個陷阱是需要注意的:在沒有關閉它的情況下提前放棄了列舉器。這使得finally語句塊失去了作用。你可以顯式的通過使用using語句來規避這一風險。

string firstElement = null;
var sequence = Foo();
using (var enumerator = sequence.GetEnumerator())
if (enumerator.MoveNext())
firstElement = enumerator.Current;

組合序列

  • 迭代器具有高度可組合性:我們擴充套件示例,這次只輸出斐波那契數列中的偶數(注意讀中文版的同學這裡的翻譯又開始嚴重的抽抽了,我詛咒這幫王八蛋):
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
foreach (int fib in EvenNumbersOnly (Fibs(6)))
Console.WriteLine (fib);
}
static IEnumerable<int> Fibs (int fibCount)
{
for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
{
yield return prevFib;
int newFib = prevFib+curFib;
prevFib = curFib;
curFib = newFib;
}
}
static IEnumerable<int> EvenNumbersOnly (IEnumerable<int> sequence)
{
foreach (int x in sequence)
if ((x % 2) == 0)
yield return x;
}
}

每一個元素直到最後才會被計算–當通過MoveNext()進行操作的時候。下圖顯示了隨時間變化的資料請求和輸出:
這裡寫圖片描述
提示:為什麼我在這裡放一張英文的原圖來代替翻譯後的圖,因為中文版的翻譯這個圖上本來就是錯的。我沒辦法,只有將原圖找來了。還是那句話。我從來沒見過一本書會被翻譯的如此不負責任。多麼好的一本書。徹底毀了。

迭代器模式中的可組合性在linq中是非常有用的,這將在後續的筆記中陸續進行討論。

相關推薦

C#學習筆記—–C#高階特性列舉型別

C#學習筆記(三)—–try語句和異常 列舉型別 enumerator是隻讀的、只能在序列的值上向前移動的遊標。一個enumerator是一個實現了下列任一介面的物件: ①System.Collections.IEnumerator ②System.C

C#學習筆記—–C#高階特性dynamic

C#高階特性:動態繫結 動態繫結 動態繫結將型別繫結(型別解析、成員和操作過程)從編譯時推遲到了執行時。在編譯時,如果程式設計師知道某個特定函式、成員的存在而編譯器不知道,那麼這種操作是非常有用的,這種情況通常出現在操作動態語言和COM,如果不適用動態

C#學習筆記—–C#高階特性擴充套件方法

C#高階特性:擴充套件方法 擴充套件方法允許為一個類定義新的方法但是不用操作這個類。擴充套件方法就是一個靜態類的靜態方法,這個方法的第一個引數的型別就是要擴充套件的這個類的型別(加this): public static class StringHe

C#學習筆記—–C#高階特性中的委託與事件

C#高階特性中的委託與事件(中) 事件 委託本身又是一個更大的模式(pattern)的基本單位,這個模式稱為publish-subscribe(釋出——訂閱)。委託的使用及其對publish-subscribe模式的支援是本章的重點。本章描述的所有內容幾乎

python學習筆記--python高階特性

python的高階特性有切片、迭代、列表生成式、生成器、迭代器等,下面來介紹這幾種高階特性:1、切片:切片特別簡單,用中括號表示範圍,包前不包後。就是中括號裡邊的範圍前面的數值被包括在內,後面的數值不被包括在內。L = ['a','b','c','d']L[0:3] 索

C++學習筆記記憶體模型、名稱空間、物件

    1、標頭檔案中通常包含的內容:函式原型、使用#define和const定義的符號常量、結構宣告、類宣告、模板宣告、行內函數     2、同樣可以使用條件編譯     3、靜態持續變數:外部連結性、內部連結性、無連結性靜態持續變數:外部連結性、內部連結性、無連結性         外部連結性:在程式碼塊

C++學習筆記字串

一、C風格字串1、字串常量 “Hello World”:C++中所有字串常量都由編譯器自動在末尾新增一個null字元。2、末尾添加了'\0'的字元陣列eg.char s1[] = "Hello"; char s2[] = {'H','e','l','l','o'}; char

C++學習筆記--函式引數,陣列函式,指標const,二維陣列函式,遞迴,函式指標

C++ Primer Plus學習筆記之三 每一塊寫了一個詳細闡釋的demo,具體使用方法以及注意事項在程式碼裡都有備註 第七章函式--C++的模組程式設計,總計分為以下幾塊-- 函式引數

C++學習筆記標準庫vector型別bitset型別

1. vector物件的定義和初始化 標頭檔案 < vector> 幾種初始化vector物件的方式: vector<T> v1; //vector儲存型別為T的物件,預設建構函式v1為空 vector<T> v

C#學習筆記邏輯關系運算符if語句

同學 判斷 請問 登陸 tasks 不同 入學 根據 重要 條件語句 分支語句和循環語句是程序裏最重要的邏輯。 IF語句、分支語句、循環語句 using System; using System.Collections.Generic; using System

C++學習筆記6——C++運算子表示式

C++程式是由各式各樣的語句組成的,而語句又是由表示式轉化而來的。C++表示式是任何值或任何有效值與運算子的組合。本篇筆記總結C++程式的基本構成元素之一——表示式以及構成它所需的運算子。 一、表示式 需要明確任何值或任何有效值與運算子的組合都是表示式。同理,每個表示式都

c++學習筆記-- c語言基礎

c語言基礎 課程來源:coursera:程學設計與演算法(Peking University,北京大學) 課程連結:程式設計與演算法 本篇簡介:這一部分是系列課第一節的後半部分,主要總結了c語言的資料型別、運算成分和控制結構。 一. 從現實問題到計算機程式 1.1 結構化程式

C++學習筆記C++中友元friend的用法應用例項

C++中的友元機制允許類的非公有成員被一個類或者函式訪問,友元按型別分為三種: (1)普通非類成員函式作為友元 (2)類的成員函式作為友元 (3)類作為友元。 友元包括友元的宣告以及友元的定義。友元

C++ 學習筆記28C++ thread_pool

程式碼來自網路。只做私人記錄,參考用 #include <iostream> #include <vector> #include <queue> #include <string> #include <function

ActiveMQ學習筆記14----Destination高階特性

1. Visual Destinations   1.1 概述   虛擬Destination用來建立邏輯Destinations,客戶端可以通過它來產生和消費訊息,它會把訊息對映到物理Destinations。ActiveMQ支援兩種方式:   1. 虛擬主題(Virtual Topics)   2

ActiveMQ學習筆記19----Consumer高階特性

1. Exclusive Consumer   獨有消費者:Queue中的訊息是按照順序被分發到consumer的,然而,當你有多個consumers同時從相同的queue中提取訊息時,你將失去這個保證。因為這些訊息是被多個執行緒併發的處理。有的時候,保證訊息按照順序處理是很重要的。例如:你可能不希望在插入

Scala學習筆記12—— scala 高階特性

1 高階函式 Scala混合了面向物件和函式式的特性,通常將可以做為引數傳遞到方法中的表示式叫做函式。在函數語言程式設計語言中,函式是“頭等公民”,高階函式包含:作為值的函式、匿名函式、閉包、柯里化等等。 1.1 作為值的函式 可以像任何其他資料型別一樣被傳遞和

[XML]學習筆記——DTD屬性的宣告及型別

一、屬性的宣告: a)        任何標記的屬性都必須在DTD文件中進行宣告,都要通過關鍵字 <!ATTLIST elem_nameattr_name attr_type default_value> 定義。 b)        所有標記的屬性,即便是同名屬

C#學習筆記12——種方法操作XML

結點 記得 ext 應用程序 eval 資源 特性 pla cells 說明(2017-7-11 16:56:13): 原文地址: C#中常用的幾種讀取XML文件的方法 XML文件是一種常用的文件格式,例如WinForm裏面的app.config以及Web程序中的web.c

C#可擴展編程之MEF學習筆記導出類的方法屬性

學習 說了 如何 mod ati dem ont num imp 前面說完了導入和導出的幾種方法,如果大家細心的話會註意到前面我們導出的都是類,那麽方法和屬性能不能導出呢???答案是肯定的,下面就來說下MEF是如何導出方法和屬性的。   還是前面的代碼,第二篇中已經提供了下