2011年7月15日 星期五

Effective C# (Covers C# 4.0): 50 Specific Ways to Improve Your C# (2nd Edition)

Bear在這裡買的
amazon的參考

C#高效編程(改進C#代碼的50個行之有效的辦法第2版)/圖靈程序設計叢書
作者:(美)瓦格納|譯者:陳黎夫
出版社:人民郵電
ISBN:9787115240415
出版日期:2010/12/01
頁數:268
人民幣:RMB 49 元




第1章 C#語言習慣
Item 1: Use Properties Instead of Accessible Data Members 使用屬性而不是可訪問的數據成員
Item 2: Prefer readonly to const 用運行時常量(readonly)而不是編譯期常量(const)
Item 3: Prefer the is or as Operators to Casts 推薦使用is或as操作符而不是強制類型轉換
Item 4: Use Conditional Attributes Instead of #if 使用Conditional特性而不是#if條件編譯
Item 5: Always Provide ToString() 為類型提供ToString()方法
Item 6: Understand the Relationships Among the Many Different Concepts of Equality 理解幾個等同性判斷之間的關係
Item 7: Understand the Pitfalls of GetHashCode() 理解GetHashCode()的陷阱
Item 8: Prefer Query Syntax to Loops 推薦使用查詢語法而不是循環
Item 9: Avoid Conversion Operators in Your APIs 避免在API中使用轉換操作符
Item 10: Use Optional Parameters to Minimize Method Overloads 使用可選參數減少方法重載的數量
Item 11: Understand the Attraction of Small Functions 理解短小方法的優勢
第2章 .NET資源管理
Item 12: Prefer Member Initializers to Assignment Statements 推薦使用成員初始化器而不是賦值語句
Item 13: Use Proper Initialization for Static Class Members 正確地初始化靜態成員變數
Item 14: Minimize Duplicate Initialization Logic 盡量減少重複的初始化邏輯
Item 15: Utilize using and try/finally for Resource Cleanup 使用using和try/finally清理資源
Item 16: Avoid Creating Unnecessary Objects 避免創建非必要的對象
Item 17: Implement the Standard Dispose Pattern 實現標準的銷毀模式
Item 18: Distinguish Between Value Types and Reference Types 區分值類型和引用類型
Item 19: Ensure That 0 Is a Valid State for Value Types 保證0為值類型的有效狀態
Item 20: Prefer Immutable Atomic Value Types 保證值類型的常量性和原子性
第3章 使用C#表達設計
Item 21: Limit Visibility of Your Types 限制類型的可見性
Item 22: Prefer Defining and Implementing Interfaces to Inheritance 通過定義並實現介面替代繼承
Item 23: Understand How Interface Methods Differ from Virtual Methods 理解介面方法和虛方法的區別
Item 24: Express Callbacks with Delegates 用委託實現回調
Item 25: Implement the Event Pattern for Notifications 用事件模式實現通知
Item 26: Avoid Returning References to Internal Class Objects 避免返回對內部類對象的引用
Item 27: Prefer Making Your Types Serializable 讓類型支持序列化
Item 28: Create Large-Grain Internet Service APIs 提供粗粒度的網際網路服務API
Item 29: Support Generic Covariance and Contravariance 支持泛型協變和逆變
第4章 使用框架
Item 30: Prefer Overrides to Event Handlers 使用覆寫而不是事件處理函數
Item 31: Implement Ordering Relations with IComparable and IComparer 使用IComparable和IComparer實現順序關係
Item 32: Avoid ICloneable 避免使用ICloneable介面
Item 33: Use the new Modifier Only to React to Base Class Updates 僅用new修飾符處理基類更新
Item 34: Avoid Overloading Methods Defined in Base Classes 避免重載基類中定義的方法
Item 35: Learn How PLINQ Implements Parallel Algorithms PLINQ如何實現並行演算法
Item 36: Understand How to Use PLINQ for I/O Bound Operations 理解PLINQ在I/O密集場景中的應用
Item 37: Construct Parallel Algorithms with Exceptions in Mind 注意並行演算法中的異常
第5章 C#中的動態編程
Item 38: Understand the Pros and Cons of Dynamic 理解動態類型的優劣
Item 39: Use Dynamic to Leverage the Runtime Type of Generic Type Parameters 使用動態類型表達泛型類型參數的運行時類型
Item 40: Use Dynamic for Parameters That Receive Anonymous Types 將接受匿名類型的參數聲明為dynamic
Item 41: Use DynamicObject or IDynamicMetaObjectProvider for Data-Driven Dynamic Types 用DynamicObject或IDynamicMetaObjectProvider實現數據驅動的動態類型
Item 42: Understand How to Make Use of the Expression API 如何使用表達式API
Item 43: Use Expressions to Transform Late Binding into Early Binding 使用表達式將延遲綁定轉換為預先綁定
Item 44: Minimize Dynamic Objects in Public APIs 盡量減少在公有API中使用動態對象
第6章 雜項
Item 45: Minimize Boxing and Unboxing 盡量減少裝箱和拆箱
Item 46: Create Complete Application-Specific Exception Classes 為應用程序創建專門的異常類
Item 47: Prefer the Strong Exception Guarantee 使用強異常安全保證
Item 48: Prefer Safe Code 盡量使用安全的代碼
Item 49: Prefer CLS-Compliant Assemblies 實現與CLS兼容的程序集
Item 50: Prefer Smaller, Cohesive Assemblies 實現小尺寸、高內聚的程序集



P.3(原文3~4)
屬性可以為虛的(virtual)

public class Customer
{
public virtual string Name
{
get;
set;
}
}

P.4(原文P.4)
雖然其語法和隱式屬性完全相同,但是編譯器卻不會自動地生成任何實現。

You can extend properties to be abstract and define properties as part of an interface definition, using similar syntax to implicit properties. The example below shows a property definition in a generic interface. Note that while the syntax is consistent with implicit properties, the interface definition below does not include any implementation. It defines a contract that must be satisfied by any type that implements this interface.

P.5(原文P.5)
C#中支持多維數組,類似地,我們也可以創建多維索引器,每一個維度上可以使用同樣或不同的類型。

In keeping with the multidimensional arrays in C#, you can create multidimensional indexers, with similar or different types on each axis:

P.5(原文P.6)
C#不支持為索引器命名。因此,類型中每個不同的索引器都必須有不同的參數列表,以免混淆。

Notice that all indexers are declared with the this keyword. You cannot name an indexer in C#. Therefore, every different indexer in a type must have distinct parameter lists to avoid ambiguity.

P.6(原文P.6-7)
雖然屬性和數據成員在源代碼層次上是兼容的,不過在二進制層面上卻是大相徑庭。這也就意味者,若將某個公有的數據成員改成了與之等同的共有屬性,那麼就必須重新編譯所有用到該公有數據成員的代碼。

Although properties and data members are source compatible, they are not binary compatible. In the obvious case, this means that when you change from a public data member to the equivalent public property, you must recompile all code that uses the public data member. C# treats binary assemblies as first-class citizens. One goal of the language is that you can release a single updated assembly without upgrading the entire application.

而這個將數據成員改為屬性的簡單操作卻破壞掉了二進制兼容型,也就會讓更新單一程序集變得非常困難。

The simple act of changing a data member to a property breaks binary compatibility. It makes upgrading single assemblies that have been deployed much more difficult.

P.7(原文P.7)
所有的數據成員都應該是私有的,沒有任何例外。這樣一來你就能立即得到數據綁定的支持,也便於日後對方法實現的各種修改。

Whenever you expose data in your type’s public or protected interfaces, use properties. Use an indexer for sequences or dictionaries. All data members should be private, without exception. You immediately get support for data binding, and you make it much easier to make any changes to the implementation of the methods in the future. The extra typing to encapsulate any variable in a property amounts to one or two minutes of your day. Finding that you need to use properties later to correctly express your designs will take hours. Spend a little time now, and save yourself lots of time later.

P.8(原文P.8-9)
編譯期常量僅能用於基本類型(內建的整數和浮點類型)、列舉或字符串。

This distinction places several restrictions on when you are allowed to use either type of constant. Compile-time constants can be used only for primitive types (built-in integral and floating-point types), enums, or strings. These are the only types that enable you to assign meaningful constant values in initializers. These primitive types are the only ones that can be replaced with literal values in the compiler-generated IL. The following construct does not compile.

即使要初始化的常量類型屬於值類型,也無法在C#中使用new操作符來初始化編譯常量。

You cannot initialize a compile-time constant using the new operator, even when the type being initialized is a value type:

// Does not compile, use readonly instead:
private const DateTime classCreation = new DateTime(2000, 1, 1, 0, 0, 0);

P.8(原文P.9)
運行時常量(readonly)也是一常量,因為在構造函數執行後不能被再次修改。

Compile-time constants are limited to numbers and strings. Read-only values are also constants, in that they cannot be modified after the constructor has executed. But read-only values are different in that they are assigned at runtime. You have much more flexibility in working with runtime constants. For one thing, runtime constants can be any type.

運行時常量必須在構造函數或初始化器中初始化。你可以讓某個readonly值為一個DateTime結構,而不能指定某個const為DateTime。

You must initialize them in a constructor, or you can use an initializer. You can make readonly values of the DateTime structures; you cannot create DateTime values with const.

P.9(原文P.10)
若想修改所有使用readonly常量的客戶代碼的行為,只要簡單地更新一個Infrastructure程序集就夠了。相反,更改一個公有的編譯期常量的值應該被看做是對類型接口的修改,你必須重新編譯所有引用該常量的代碼。而更改只讀常量的值卻僅僅算作是對類型實現的修改,它與其客戶代碼二進制層次上是兼容的。

In fact, you get no output at all. The loop now uses the value 105 for its start and 10 for its end condition. The C# compiler placed the const value of 10 into the Application assembly instead of a reference to the storage used by EndValue. Contrast that with the StartValue value. It was declared as readonly: It gets resolved at runtime. Therefore, the Application assembly makes use of the new value without even recompiling the Application assembly; simply installing an updated version of the Infrastructure assembly is enough to change the behavior of all clients using that value. Updating the value of a public constant should be viewed as an interface change. You must recompile all code that references that constant. Updating the value of a read-only constant is an implementation change; it is binary compatible with existing client code.

P.11(原文P.)
可選參數的默認值將被放置於調用端,就像編譯期常量(用const聲明)的默認值一樣。

You’ll encounter similar tradeoffs between runtime and compile-time processing of constant values when you use named and optional parameters. The default values for optional parameters are placed in the call site just like the default value for compile-time constants (those declared with const). Like working with readonly and const values, you’ll want to be careful with changes to the values of optional parameters. (See Item 10.)

在編譯期必須得到確定數值時一定要使用const。例如特性(attribute)的參數和枚舉的定義等,還有那些在各個版本發布之間不會變化的值。在除此之外的所有情況下,都應該盡量選擇更加靈活的readonly常量。

const must be used when the value must be available at compile time: attribute parameters and enum definitions, and those rare times when you mean to define a value that does not change from release to release. For everything else, prefer the increased flexibility of readonly constants.

P.16(原文P.18)
若你準備使用as進行類型轉換,那麼is檢查就毫無必要。只需檢查返回值是否為null即可,這樣更加簡單。

That’s inefficient and redundant. If you’re about to convert a type using as, the is check is simply not necessary. Check the return from as against null; it’s simpler.

P.(原文P.)
foreach使用強制轉型將對象轉成循環中將要使用的類型。

foreach uses a cast operation to perform conversions from an object to the type used in the loop.

P.17(原文P.18)
不過因為其中使用了強制轉型,所以foreach循環中可能會拋出InvalidCastException異常。

foreach needs to use casts to support both value types and reference types. By choosing the cast operator, the foreach statement exhibits the same behavior, no matter what the destination type is. However, because a cast is used, foreach loops can cause an InvalidCastException to be thrown.

P.(原文P.)
當然,調用 System.Object 的 GetType() 方法也能得到一個對象的運行時類型。

Finally, sometimes you want to know the exact type of an object, not just whether the current type can be converted to a target type. The is operator returns true for any type derived from the target type. The GetType() method gets the runtime type of an object. It is a more strict test than the is or as statement provides. GetType() returns the type of the object and can be compared to a specific type.

P.23(原文P.26)
這種方式創建的方法甚至可以依賴於多個環境變量。在應用多個conditional特性時,它們之間的組合關係將為"或"(OR)。

You can also create methods that depend on more than one environment variable. When you apply multiple conditional attributes, they are combined with OR. For example, this version of CheckState would be called when either DEBUG or TRACE is true:

P.24(原文P.27)
Conditional特性只可以應用在整個方法上。另外需要注意的是,任何一個使用Conditional特性的方法都只能返回void類型。

The Conditional attribute can be applied only to entire methods. In addition, any method with a Conditional attribute must have a return type of void. You cannot use the Conditional attribute for blocks of code inside methods or with methods that return values. Instead, create carefully constructed conditional methods and isolate the conditional behavior to those functions. You still need to review those conditional methods for side effects to the object state, but the Conditional attribute localizes those points much better than #if/#endif. With #if and #endif blocks, you can mistakenly remove important method calls or assignments.

P.25(原文P.27)
或許你已經注意到了,上面的每個使用了Conditional特性的方法都返回void類型,且不接受任何參數。這個規則我們必須遵守。

You may have noticed that every method shown with the Conditional attribute has been a method that has a void return type and takes no parameters. That’s a practice you should follow. The compiler enforces that conditional methods must have the void return type. However, you could create a method that takes any number of reference type parameters. That can lead to practices where an important side effect does not take place. Consider this snippet of code:

P.41(原文P.45)
對於我們創建的大多數類型,最好的做法就是完全避免實現 GetHashCode() 。

In most types that you create, the best approach is to avoid the existence of GetHashCode() entirely.

P.48(原文P.52)
.NET BCL已經為List提供了一個ForAll的實現。

The .NET BCL has a ForAll implementation in List. It’s just as simple to create one for IEnumerable:

P.55(原文P.60)
用轉換操作符返回對象內部的字段也會帶來一些問題,這樣將破壞掉類的封裝。因為如果將我們的類型強制轉換為其他類型,類的使用者就可以訪問類的內部變量。你應該竭力避免這種情況。

Conversion operators that return fields inside your objects will not exhibit this behavior. They have other problems. You’ve poked a serious hole in the encapsulation of your class. By casting your type to some other object, clients of your class can access an internal variable. That’s best avoided for all the reasons discussed in Item 26.

P.58(原文P.64)
對於程序集的第一次發布,可以隨意使用可選參數和具名參數,並任意給出你想提供的重載。而在進行後續發布時,必須為額外的參數創建重載。這樣才能保證現有的程序仍能正常運行。此外,在任何的後續發中,都要避免修改參數的名稱,因為參數名稱現在已經成為了公有接口的一部份。

Now, after that explanation, the guidance should be clearer. For your initial release, use optional and named parameters to create whatever combination of overloads your users may want to use. However, once you start creating future releases, you must create overloads for additional parameters. That way, existing client applications will still function. Furthermore, in any future release, avoid changing parameter names. They are now part of your public interface.

P.(原文P.)
在第一次調用BuildMsg時,if-else的兩個分支都將被JIT編譯。而實際上僅需要編譯其中的一個。而若是像這樣編寫BuildMsg的話

The first time BuildMsg gets called, both paths are JITed. Only one is needed. But suppose you rewrote the function this way:

public string BuildMsg2(bool takeFirstPath)
{
if (takeFirstPath)
{
return FirstPath();
}
else
{
return SecondPath();
}
}

因為兩個分支內部的代碼被拆分到了各自的方法中,所以這兩個方法可以根據需要再JIT編譯,而不必在第一次調用BuildMsg時進行。

Because the body of each clause has been factored into its own function,
that function can be JITed on demand rather than the first time BuildMsg
is called.

P.68(原文P.75)
在如下的3種情況中,你應該避免使用初始化器語法: 第1種情況是,當你想要初始化對象為0或null時。

Using initializers is the simplest way to avoid uninitialized variables in your types, but it’s not perfect. In three cases, you should not use the initializer syntax. The first is when you are initializing the object to 0, or null.

第2種應該避免使用初始化語法的情況是,你需要對同一個變量執行不同的初始化方式。使用初始化器語法的前提是,所有的構造函數都會將該變量設置為同樣的值。

The second inefficiency comes when you create multiple initializations for the same object. You should use the initializer syntax only for variables that receive the same initialization in all constructors.

P.70(原文P.75)
最後一個將初始化代碼放在構造函數中的合理理由是,這樣做可以方便異常處理。

The final reason to move initialization into the body of a constructor is to facilitate exception handling.

初始化器無法用try包裹。對象初始化器執行的過程中發生的所有異常都會傳遞到對象之外。在類的內部你無法嘗試修復。此時應該將這部份初始化代碼放在構造函數中,這樣才能實現必要的恢復性代碼,以創建類型並用更加友好的方式處理異常。

You cannot wrap the initializers in a try block. Any exceptions that might be generated during the construction of your member variables get propagated outside your object. You cannot attempt any recovery inside your class. You should move that initialization code into the body of your constructors so that you implement the proper recovery code to create your type and gracefully handle the exception (see Item 47).

P.71(原文P.78-79)
在應用程序作用域(AppDomain)內,在你的類型被第一次訪問之前,CLR會自動調用你的靜態構造函數。你只能定義一個靜態構造函數,且絕不能接受任何參數。因為靜態構造函數是由CLR調用的,所以你必須小心處理其中可能會拋出的異常。若某個異常被靜態構造函數拋出,那CLR麼將終止你的程序。而若是在內部吞下了異常,那麼情況或許會更加糟糕。創建該類型的代碼將以失敗告終,直到該AppDomain停止。CLR無法通過靜態構造函數初始化該類型,而且也不會再次嘗試,導致該類型並沒有被正確初始化。這樣,根據該類型(及其派生類型)創建的對象也沒有完全構造。

The CLR calls your static constructor automatically before your type is first accessed in an application space (an AppDomain). You can define only one static constructor, and it must not take any arguments. Because static constructors are called by the CLR, you must be careful about exceptions generated in them. If you let an exception escape a static constructor, the CLR will terminate your program. The situation where the caller catches the exception is even more insidious. Code that tries to create the type will fail until that AppDomain is unloaded. The CLR could not initialize the type by executing the static constructor. It won’t try again, and yet the type did not get initialized correctly. Creating an object of that type (or any type derived from it) would not be well defined. Therefore, it is not allowed.

P.72(原文P.79)
使用靜態構造函數而不是靜態初始化器的最常見理由就是處理異常。在使用靜態初始化器時,我們無法自已捕獲異常。

Exceptions are the most common reason to use the static constructor instead of static initializers. If you use static initializers, you cannot catch the exceptions yourself. With a static constructor, you can (see Item 47):

static MySingleton2()
{
try
{
theOneAndOnly = new MySingleton2();
}
catch
{
// Attempt recovery here.
}
}

靜態初始化器和靜態構造函數是初始化類的靜態成員的最佳選擇。

Static initializers and static constructors provide the cleanest, clearest way to initialize static members of your class. They are easy to read and easy to get correct. They were added to the language to specifically address the difficulties involved with initializing static members in other languages.


P.74(原文P.82)
注意,第二個構造函數中使用了" "來給出name的默認值,而並沒有使用更有語意的string.Empty。這是因為string.Empty並不是一個編譯期常量,而只是一個定義在string類中的靜態屬性。正因為這樣,所以string.Empty不能用作參數默認值。

You’ll note that the second constructor specifies "" for the default value on the name parameter, rather than the more customary string.Empty. That’s because string.Empty is not a compile-time constant. It is a static property defined in the string class. Because it is not a compile-time constant, you cannot use it for the default value for a parameter.

P.75(原文P.82)
這時你可以使用構造函數鍵,讓一個構造函數調用聲明在同一個類中的另一個構造函數,而不用創建一個公用的輔助方法。因為創建公用的輔助方法有著一些不足。

C# versions 1 through 3 do not support default parameters, which is the preferred solution to this problem. You must write each constructor that you support as a separate function. With constructors, that can mean a lot of duplicated code. Use constructor chaining, by having one constructor invoke another constructor declared in the same class, instead of creating a common utility routine. Several inefficiencies are present in this alternative method of factoring out common constructor logic:

P.75(原文P.82-83)
這個版本看上去似乎沒什麼不同,不遇其生成的目標代碼效率卻大打折扣。

That version looks the same, but it generates far less efficient object code.

Bear: 自行翻書或PDF比對上下二坨程式碼

P.85(原文P.94)
若你可以自由選擇,那麼Dispose()要比 Close() 更好一些。

If you have the choice, Dispose() is better than Close(). You’ll learn all the gory details in Item 18.



P.87(原文P.96-97)
前面介紹了兩種盡量降低程序中創建對象數量的方法。第一,將常用的局部變量提昇為成員變量。第二,提供一個類,存放某個類型常用實例的單例對象。還有一種方法,即直接創建不可變量型的最終值。

You’ve learned two techniques to minimize the number of allocations your program performs as it goes about its business. You can promote oftenused local variables to member variables. You can provide a class that stores singleton objects that represent common instances of a given type. The last technique involves building the final value for immutable types.

P.88(原文P.97)
StringBuilder是一個可變的字符串類,用來創建不可變的string對象。

StringBuilder is the mutable string class used to build an immutable string object.

P.99(原文P.110)
一般來說,我們創建的大都是引用類型而不是值類型。不過,若你對下面問題的答案都是肯定的,那麼就應該創建值類型。你可以將這些問題套用在前面的Employee例子上:
一、該類型的主要職責在於數據存儲嗎?
二、該類型的公有接口都是由訪問其數據成員的屬性定義的嗎?
三、你確定該類型絕不會有派生類型嗎?
四、你確定該類型訪遠都不需要多態支持嗎?

You’ll create more reference types than value types. If you answer yes to all these questions, you should create a value type. Compare these to the previous Employee example:
1. Is this type’s principal responsibility data storage?
2. Is its public interface defined entirely by properties that access its data members?
3. Am I confident that this type will never have subclasses?
4. Am I confident that this type will never be treated polymorphically?

用值類型表示底層存儲數據的類型,用引用類型來封裝程序的行為。這樣,你可以保證類暴露出的數據能以復制的形式安全提供,也能得到其於棧存儲和使用內聯方式存儲帶來的內存性能提昇,更可以使用標準的面向對象技術來表達應用程序的邏輯。而若是你對類型的未來用途不確定,那麼應該選擇引用類型。

Build low-level data storage types as value types. Build the behavior of your application using reference types. You get the safety of copying data that gets exported from your class objects. You get the memory usage benefits that come with stack-based and inline value storage, and you can utilize standard object-oriented techniques to create the logic of your application. When in doubt about the expected use, use a reference type.

P.110(原文P.122)
初始化常量類型通常有三種策略,具體選擇那一種則依賴於類型本身的復雜程度。定義一組合適的構造函數通常是最簡單的做法。

The complexity of a type dictates which of three strategies you will use to initialize your immutable type. The Address structure defined one constructor to allow clients to initialize an address. Defining the reasonable set of constructors is often the simplest approach.

P.115(原文P.130)
不過,擴展方法卻可以應用在接口上,讓接口"看上去"彷彿可以帶有具體的實現。

What you can’t do in an interface is provide implementation for any of these members. Interfaces contain no implementation whatsoever, and they cannot contain any concrete data members. You are declaring the
binary contract that must be supported by all types that implement an interface. However, you can create extension methods on those interfaces to give the illusion of an implementation for interfaces.

P.116(原文P.131)
在抽象基類和接口之間做選擇,實際上就表示了對日後可能發生變化的不同處理態度。接口是固定的:我們將一組功能封裝在一個接口中,作為其他類型的實現契約。而基類則可以在日後進行擴展,這些擴展也會成為每個派生類的一部份。

Choosing between an abstract base class and an interface is a question of how best to support your abstractions over time. Interfaces are fixed: You release an interface as a contract for a set of functionality that any type can implement. Base classes can be extended over time. Those extensions become part of every derived class.

P.123(原文P.138)
綜上所述,基類描述並實現了一組相關類型間共用的行為。接口則定義了一組具有原子性的功能,供其他不相關的具體類型來實現。二者均有其用武之地,但用處各不相同。接口是一種按契約設計的方式:一個實現了某個接口的類型,必須提供接口中約定的方法實現。抽象基類則為一組相關的類型提供了一個共用的抽象。

Base classes describe and implement common behaviors across related concrete types. Interfaces describe atomic pieces of functionality that unrelated concrete types can implement. Both have their place. Classes define the types you create. Interfaces describe the behavior of those types as pieces of functionality. If you understand the differences, you will create more expressive designs that are more resilient in the face of change. Use class hierarchies to define related types. Expose functionality using interfaces implemented across those types.

P.145(原文P.162-163)
因此,在添加 ISerializable 接口時,鍵名與變量的順序必須匹配。變量的順序即在類中聲明的順序。(順便提一句,這就意味著重新調整類中變量的順序或者重新命名變量,將會破壞與先前序列化創建文件的兼容性。)

The serialization stream stores each item as a key/value pair. The code generated from the attributes uses the variable name as the key for each value. When you add the ISerializable interface, you must match the key name and the order of the variables. The order is the order declared in the class. (By the way, this fact means that rearranging the order of variables in a class or renaming variables breaks the compatibility with files already created.)

P.149(原文P.167)
API的粒度越細,所花費在等待數據返回上的額時間也就愈多。

The more granular the API is, the higher percentage of time your application spends waiting for data to return from the server.

P.153(原文P.171)
若某個返回的類型可以由其派生類型替換,那麼這個類型就是支持協變的。若某個參數類型可以由其基類替換,那麼這個類型就是支持逆變的。

Type variance is one of those topics that many developers have encountered but not really understood. Covariance and contravariance are two different forms of type substitution. A return type is covariant if you can substitute a more derived type than the type declared. A parameter type is contravariant if you can substitute a more base parameter type than the type declared.

P.161(原文P.182)
另一個使用事件機制的原因是,事件是在運行時綁定的,因此會帶來更好的靈活性。你可以根據程序當前的運行環境而添加不同的事件處理函數。

Another reason for the event mechanism is that events are wired up at runtime. You have more flexibility using events. You can wire up different event handlers, depending on the circumstances of the program.