如果在匿名函數内部捕獲到外部的變量,
被捕獲到的是變量,而不是建立委托執行個體時該變量的值。在匿名方法外對變量的更改在匿名方法内部是可見的,反之亦然。
如果隻是建立一個委托執行個體,不會讀取變量并将它的值存儲到某個地方。 作用:簡單來說,捕獲變量能
簡化避免專門建立一些類來儲存一個委托需要處理的資訊(除了作為參數傳遞的資訊之外)。
記憶體:如果一個值類型在匿名函數被捕獲,那麼它就不在棧上了。編譯器建立了一個額外的類來容納變量,CreateDelegateInstance方法擁有對該類的一個執行個體的引用,是以它能使用外部的變量。另外,委托也會建立一個執行個體方法作為委托的操作。
由于非常複雜的捕獲嵌套,如果用或不用捕獲會更加簡單,那就不要用比較好。
ArrayList, List<T>List是泛型,編譯器會檢查類型,并且沒有裝箱操作。ArrayList則有裝箱操作,它會把加入的值類型轉化為引用類型。
ArrayList中的執行個體,有可能需要
強制類型轉換的操作,這事實上事告訴編譯器“
我比你知道的多一點”,但這又意味着你可能是錯誤的。
三種對清單進行排序的方法:
委托,lambda表達式和擴充方法:
// 使用委托進行排序 而不必重寫IComparer方法
List<Product> products = Product.GetSampleProducts();
products.Sort(delegate(Product x, Product y)
{return x.Name.CompareTo(y.Name)}
);
// 使用lambda表達式進行排序
List<Product> products = Product.GetSampleProducts();
products.Sort((x, y) => x.Name.CompareTo(y.Name));
// 使用擴充方法
foreach (Product product in products.OrderBy(p=>p.Name))
{
Console.WriteLine(product);
}
擴充方法并不是List的一個方法,這裡也不是對List進行排序,而是按照特定的順序擷取清單的内容 。有時,需要更改實際的清單,有時則不需要。
// 使用委托
Predicate<Product> test = delegate(Product p){return p.Price > 10m;};
List<Product> matches = products.FindAll(test);
Action<Product> pritn = Console.WriteLine;
matches.ForEach(print);
// 一行
products.FindAll(delegate(Product p){return p.Price > 10;}).ForEach(Console.WriteLine);
// lamda表達式
foreach(Product product in products.Where(p => p.Price >10))
{
Console.WriteLine(product);
}
值類型和引用類型的誤區
- 結構體是"輕量級"的類
結構體是值類型。
例如Vector和DeltaTime等, 它們作為值類型是很有道理的,因為它們非常适合數字或字元的一個基本機關來使用。另外,它也理應被賦予對它的值執行計算的能力。
值類型優勢:不需要被垃圾回收,不會因類型辨別而産生開銷,也不需要解引用。
引用類型優勢:傳遞參數,指派,将值傳回,隻需複制指針(4或8位元組),而不是複制全部資料。
2.
引用儲存在堆上(正确),值類型儲存在棧上(錯誤)。
前半句正确,後半句錯誤。
假定一個類中有一個int類型執行個體變量,那麼在這個類的任何對象中,該變量的值和對象的其它資料在一起,也就是在棧上。
隻有局部變量和方法參數在棧上。
3.
對象在C#預設是通過引用傳遞的說這句話的人知道C#實際的行為是什麼,但不知道引用傳遞(pass by reference)的真正意思是什麼。引用類型變量的值是引用,而不是對象本身。不需要按照引用來傳遞參數本身,就可以更改該參數引用的那個對象的内容。
void AppendHello(StringBuilder builder)
{
builder.Append("Hello")
}
這個例子中,參數值(對StringBuilder的一個引用)是以值傳遞方式進行傳遞的。如果想在方法内部更改builder的變量的值,隻是更改的builder的一個副本。無論是值傳遞還是引用傳遞,
永遠不會傳遞對象本身。
4.
裝箱和拆箱為一個值調用ToString, Equals, GetHashCode()等方法,如果該類型沒有覆寫這些方法,也會發生裝箱。當調用值類型的GetType()方法總會伴随着裝箱過程,因為它不能被重載。s使用TypeOf不會有這個問題。
泛型相關
為什麼需要泛型主要是因為強制類型轉換非常糟糕,有問題就無法通過編譯,這樣就非常安全。而試圖證明自己比編譯器知道的更多,往往是比較危險的。
各種特性 TODO
同C++模闆相比
- 對于普通C++來說,會為一套特定的模版編譯代碼,好像模版實參本來就在源代碼中一樣。編譯是一次完成的,而不是像C#一樣, 先編譯成IL, 再由JIT編譯成本地代碼 。一個C++程式以十種不同的方式使用一個标準模版,就會在程式中 包含代碼十次 。而在C#中,根本就不會包含泛型代碼,它隻是引用一下泛型。執行時,需要多少個不同的版本, JIT就會編譯多少個 。
- C++的模版比C#做的好的之一, 實參不要求必須是類型名,變量名,函數名,常量表達式也是允許的 。例如緩沖區類型,buffer<int, 20>是包含20個int值的緩沖區,它将緩沖區大小作為實參之一。這個功能對于 模闆元程式設計 (metaprogramming)是至關重要的。
- C++模版在其他方面更加靈活。C#除了最簡單的算數運算,其他運算都是顯示使用Math類。C#還缺乏C風格的typedef(使用typedef定義了一個資料表示之後,就可以在一個程式的各個地方使用它,并可以輕松更改。)而C++沒有這個限制。
空類型相關
為什麼值類型不能為null?
值類型的變量的值是它本身的資料。一個非空引用值提供了通路一個對象的途徑,然而null相當于一個特殊的值,它意味着我不用引用任何對象。
值類型變量直接在Stack中儲存了資料,是以在生命周期結束前資料不能被任何形式的銷毀,而引用類型變量在Heap中儲存資料,是以指派null其實是将對應在Heap中的資料銷毀而不是結束變量的生命周期。
C#表示空值的四種方式:
- 魔值 犧牲一個值用來表示空,例如:(DateTime.MinValue)
- 引用類型包裝, 使用Obj作為變量類型或定義一個新的類型(不展開講了),但是會導緻GC Alloc
- 額外的布爾标志. 使用一個普通值類型的值,用另外一個值表示該類型是真正存在的。
- System.Nullable<T>和 System.Nullable
System.Nullable<T>和 System.Nullable
Nullable<T>是一個結構,
一個值類型。具體不介紹了。
Yield用法
yield是一個文法糖,官方文檔裡的解釋是:
如果你在語句中使用 yield 上下文關鍵字,則意味着它在其中出現的方法、運算符或 get 通路器是疊代器。通過使用 yield 定義疊代器,可在實作自定義集合類型的 IEnumerator<T> 和 IEnumerable 模式時無需其他顯式類(保留枚舉狀态的類,有關示例,請參閱 IEnumerator)。
- yield return 一次傳回一個元素
- yield break 中止疊代
疊代器方法和 get 通路器
疊代器的聲明必須滿足以下要求:
- 傳回類型必須為 IEnumerable、IEnumerable<T>、IEnumerator 或 IEnumerator<T>。
- 聲明不能有任何 in、ref 或 out 參數。
傳回
yield
或 IEnumerable 的疊代器的 IEnumerator 類型為
object
。 如果疊代器傳回 IEnumerable<T> 或 IEnumerator<T>,則必須将
yield return
語句中的表達式類型隐式轉換為泛型類型參數。
以下情形中不能包含
yield return
或
yield break
語句:
- Lambda 表達式和匿名方法。
- 包含不安全的塊的方法。 有關詳細資訊,請參閱 unsafe。
異常處理
不能将
yield return
語句置于 try-catch 塊中。 可将
yield return
語句置于 try-finally 語句的 try 塊中。
可将
yield break
語句置于 try 塊或 catch 塊中,但不能将其置于 finally 塊中。
如果
foreach
主體(在疊代器方法之外)引發異常,則将執行疊代器方法中的
finally
塊。
技術實作
以下代碼從疊代器方法傳回
IEnumerable<string>
,然後周遊其元素。
C#複制
IEnumerable<string> elements = MyIteratorMethod();
foreach (string element in elements)
{
...
}
調用
MyIteratorMethod
并不執行該方法的主體。 相反,該調用會将
IEnumerable<string>
傳回到
elements
變量中。
在
foreach
循環疊代時,将為 MoveNext 調用
elements
方法。 此調用将執行
MyIteratorMethod
的主體,直至到達下一個
yield return
語句。
yield return
語句傳回的表達式不僅決定了循環體使用的
element
變量值,還決定了
elements
的 Current 屬性(它是
IEnumerable<string>
)。
在
foreach
循環的每個後續疊代中,疊代器主體的執行将從它暫停的位置繼續,直至到達
yield return
語句後才會停止。 在到達疊代器方法的結尾或
foreach
語句時,
yield break
循環便已完成
注意:盡管使用yield語句,看起來像是順序執行的方法,但實際上本質是一個狀态機。調用者每次隻想擷取一個元素,是以傳回上一個值時需要保留目前的工作狀态。