2011年7月15日 星期五

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


人民幣: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 實現小尺寸、高內聚的程序集


public class Customer
public virtual string Name


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.


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


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.


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.


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.


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.


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);


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.


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.


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.


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 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.


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.


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


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.

當然,調用 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.


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:


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.


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:

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

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

.NET BCL已經為List提供了一個ForAll的實現。

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


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.


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.


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();
return SecondPath();


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.

在如下的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.


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.


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


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).


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.


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()
theOneAndOnly = new MySingleton2();
// 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.

注意,第二個構造函數中使用了" "來給出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.


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:


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

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

若你可以自由選擇,那麼Dispose()要比 Close() 更好一些。

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


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.


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


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.


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.


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.


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.


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.

因此,在添加 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.)


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


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.


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.