C#本質論(第3版)/微軟技術系列/圖靈程序設計叢書
Bear在這裡買的
amazon的參考
-------------------------------------------------------------------------------
作者:(美)米凱利斯|譯者:周靖
出版社:人民郵電
ISBN:9787115233837
出版日期:2010/09/01
裝幀:
頁數:694
人民幣:RMB 99 元
-------------------------------------------------------------------------------
P.5(原文P.7)
雖然很少見,但如果關鍵字包含一個@前綴,那麼也可作為標識符使用。例如,可將一個局部變量名為@return。
Although it is rare, keywords may be used as identifiers if they include “@” as a prefix. For example, you could name a local variable @return.
P.7(原文P.10)
沒有分號的語句
C#許多編程元素都以一個分號結尾,不要求使用分號的一個例子是switch語句。由於大括號總是包含在switch語句中,所以C#不要求在該語句之後跟上一個分號。事實上,代碼塊本身被視為語句(它們也由語句構成),而且它們不要求使用分號來結束。類似地,在某些情況下(比如using指令),雖然要求在末尾使用一個分號,但不被視為一個語句。
Statements without Semicolons
Many programming elements in C# end with a semicolon. One example that does not include the semicolon is a switch statement. Because curly braces are always included in a switch statement, C# does not require a semicolon following the statement. In fact, code blocks themselves are considered statements (they are also composed of statements) and they don’t require closure using a semicolon. Similarly, there are cases, such as the using declarative, in which a semicolon occurs at the end but it is not a statement.
P.15(原文P.22)
XML帶分隔符的注釋
以/**開頭並以**/結尾的注釋稱為XML帶分隔符的注釋。
例子:/**注釋**/
XML delimited comments
Comments that begin with /** and end with **/ are called XML delimited comments. They have the same characteristics as regular delimited comments, except that instead of ignoring XML comments entirely, the compiler can place them into a separate text file. XML delimited comments were only explicitly added in C# 2.0, but the syntax is compatible with C# 1.0.
/**comment**/
P.18(原文P.26)
原文和簡體版表1-3看起來有錯
原文:C# 4.0 with .NET Framework 3.5(Visual Studio 2010)
應該:C# 4.0 with .NET Framework 4.0(Visual Studio 2010)
P.24(原文P.34)
除非超過範圍,否則decimal數字表示的十進位數都是完全準確的。
Unless they are out of range, decimal numbers represent denary numbers exactly.
decimal類型和C#的浮點數類型的區別在於,decimal類型的基數都是十進制的,而浮點數的基數是二進制的。
The difference between the decimal type and the C# floating-point types is that the base of a decimal type is a denary and the base of floating-point types is binary.
P.26(原文P.38)
對於整數數據類型,相應的後綴是u, l, lu和ul。整數字面量的類型是像下面這樣確定的:
1.沒有後綴的數值字面量按照以下順序,解析成能夠存儲該值的第一個數據類型:int, uint, long, ulong
2.具有後綴u的數值字面量按照以下順序,解析成能夠存儲該值的第一個數據類型:uint, ulong
3.具有後綴l的數值字面量按照以下順序,解析成能夠存儲該值的第一個數據類型:long, ulong。
4.如果數值字面量的後綴是ul或lu,就解析成ulong類型。
注意,字面的後綴是不區分大小寫的。然而,對於long來說,一般推薦使用大寫字母L,因為小寫字母l和數字1不好區分。
For integer data types, the suffixes are U, L, LU, and UL. The type of an integer literal can be determined as follows.
1.Numeric literals with no suffix resolve to the first data type that can store the value in this order: int, uint, long, and ulong.
2.Numeric literals with the suffix U resolve to the first data type that can store the value in the order uint and then ulong.
3.Numeric literals with the suffix L resolve to the first data type that can store the value in the order long and then ulong.
4.If the numeric literal has the suffix UL or LU, it is of type ulong.
Note that suffixes for literals are case-insensitive. However, uppercase is generally preferred because of the similarity between the lowercase letter l and the digit 1.
為了指定一個十六進制值,要為值附加0x前綴。
To specify a hexadecimal value, prefix the value with 0x and then use any hexadecimal digit
P.27(原文P.38)
要想顯示一個數的十六進制形式,必須使用x或X數值格式說明符。大小寫決定了十六進制字母的大小寫形式。
To display a numeric value in its hexadecimal format, it is necessary to use the x or X numeric formatting specifier. The casing determines whether the hexadecimal letters appear in lower- or uppercase.
P.31(原文P.45)
在以@開頭的字符串中,唯一支持的轉義序列是"",它代表一個雙引號,這個雙引號不會終止字符串。
The only escape sequence the verbatim string does support is "", which signifies double quotes and does not terminate the string.
P.32~33(原文P.47)
翻譯有誤:
簡體版:例如,concat
應該是:例如,Concat
表2-5列出的都是靜態方法。這意味著,為了調用方法,需要方法名(例如,Concat)之前附加包含了該方法(例如,string)的那個類型的名稱。然而,string類還包含一些實例方法。實例方法不是以類型名作為前綴,而是以變量名(或者對實例的其它引用)作為前綴。
All of the methods in Table 2.5 are static. This means that, to call the method, it is necessary to prefix the method name (for example, Concat) with the type that contains the method (for example, string). As illustrated below, however, some of the methods in the string class are instance methods. Instead of prefixing the method with the type, instance methods use the variable name (or some other reference to an instance).
P.41(原文P.59)
使用轉型運算符,程序員相當於告訴編譯器:"相信我,我知道要幹什麼。我知道這個轉換也許不合適,但我願意嘗試一下。"只有在這種情況下,編譯器才允許轉換。
With the cast operator, the programmer essentially says to the compiler, “Trust me, I know what I am doing. I know that the conversion could
possibly not fit, but I am willing to take the chance.” Making such a choice will cause the compiler to allow the conversion.
P.44(原文P.63)
不過,System.Convert只支持預定義數量的類型,而且是不可擴展的。它允許從任何基本類型(bool, char, sbyte, short, int, long, ushort, uint, ulong, float, double, decimal,DateTime和String)轉換到其它基本類型。
System.Convert supports only a predefined number of types and it is not extensible. It allows conversion from any primitive type (bool, char, sbyte, short, int, long, ushort, uint, ulong, float, double, decimal, DateTime, and string) to any other primitive type.
P.45(原文P.63)
TryParse()
只是在轉換失敗的情況下,它不是引發一個異常,而是返回false。
This method is very similar to the Parse() method, except that instead of throwing an exception if the conversion fails, the TryParse() method returns false,
P.45(原文P.64)
Parse()和TryParse()的關鍵區別在於,假如轉換失敗,那麼TryParse()不會引發異常。
The key difference between Parse() and TryParse() is the fact that TryParse() won’t throw an exception if it fails.
P.47(原文P.67)
自C#3.0起,不必在new後面指定數組的數據類型(string),只要數組元素的數據類型是兼容的即可。但是,方括號仍是需要的。
Starting in C# 3.0, specifying the data type of the array (string) following new became optional as long as the data type of items within the array was compatible—the square brackets are still required.
P.48(原文P.68)
分配一個數組但不指定初始值仍然會初始化每個元素。"運行時"會將每個元素初始化為它們的預設值,如下所示:
1.引用類型(比如string)初始化為null。
2.數值類型初始化為零。
3.bool初始化為false。
4.char初始化為'\0'。
非基本值類型是以遞迴的方式初始化,它們的每個字段都被初始化為預設值。
其結果就是,在使用之前,不必單獨對數組的每個元素進行賦值。
在C#2.0中,可以使用default()運算符來判斷一種數據類型的預設值。default()獲取一個數據類型作為參數。例如,default(int)會返回0,而default(char)會返回\0。
Assigning an array but not initializing the initial values will still initialize each element. The runtime initializes elements to their default values, as follows.
1.Reference types (such as string) are initialized to null.
2.Numeric types are initialized to zero.
3.bool is initialized to false.
4.char is initialized to '\0'.
Nonprimitive value types are recursively initialized by initializing each of their fields to their default values.
As a result, it is not necessary to individually assign each element of an array before using it.
In C# 2.0, it is possible to use the default() operator to determine the default value of a data type. default() takes a data type as a parameter. default(int), for example, returns 0 and default(char) returns \0. Because the array size is not included as part of the variable declaration, it is possible to specify the size at runtime.
P.53(原文P.75)
Clear()方法不刪除數組的元素,而且不將長度設為零。數組大小是固定的,不能修改。所以Clear()方法將數組中的每個元素都設為預設值(flase, 0或者null),這解釋了在調用Clear()之後輸出數組時,Console.WriteLine()為什麼會創建一個空行。
The Clear() method does not remove elements of the array and does not set the length to zero. The array size is fixed and cannot be modified. Therefore, the Clear() method sets each element in the array to its default value (false, 0, or null). This explains why Console.Write-Line() creates a blank line when writing out the array after Clear() is called.
P.54(原文P.75)
但.NET 2.0提供了一個方法,該方法能夠重新創建數組,並將所有元素複製到新數組,這個方法稱為System.Array.Resize。
Although there is no equivalent C# specific keyword, there is a method available in .NET 2.0 that will re-create the array and then copy all the elements over to the new array. The method is called System.Array.Resize.
要獲取一個特定的維的長度,不是使用Length屬性,而是使用數組的GetLength()實例方法。調用該方法時,需要指定返回那一維的長度。
Retrieving the length of a particular dimension does not require the Length property. To retrieve the size of a particular rank, an array includes a GetLength() instance method. When calling this method, it is necessary to specify the rank whose length will be returned
P.55(原文P.77)
可以使用字符串的ToCharArray()方法,將整個字符串作為一個字符數組返回。
Not only can string characters be accessed individually using the array accessor, but it is also possible to retrieve the entire string as an array of characters using the string’s ToCharArray() method.
P.57(原文P.79)
常見數組編碼錯誤
int[] numbers = new int[3];
Console.WriteLine(numbers[3]);
Array indexes start at zero. Therefore, the last item is one less than the array size. (Note that this is a runtime error, not a compile-time error.)
int[] numbers = new int[3];
Console.WriteLine(numbers[2]);
P.64(原文P.91)
浮點類型還有其它一些特殊性。例如,一個整數除以零,理論上來說應該造成一個錯誤。對於精確的數據類形(比如int和decimal)來說,這一點是成立的。然float和double允許一些特殊的值。
Bear: 說的就是 NaN
You should be aware of some additional unique floating-point characteristics as well. For instance, you would expect that dividing an integer by zero would result in an error, and it does with precision data types such as int and decimal. float and double, however, allow for certain special values.
P.73(原文P.105)
代碼塊(Code Block)
With curly braces, however, we can combine statements into a single unit called a code block, allowing the execution of multiple statements for a condition.
P.75(原文P.107)
大括號可以自成一體(例如,沒有條件或循環),這是完全合法的一種語法。
In other words, placing curly braces on their own (without a conditional or loop, for example) is legal syntax.
P.80(原文P.114)
條件運算符要少要,因為它通常會影響到程式的可讀性。大多數時候簡單的if/else語句或許更恰當。
Use the conditional operator sparingly, because readability is often sacrificed and a simple if/else statement may be more appropriate.
從C#2.0開始,執行空值檢查的條件運算符有了一個簡化語法,稱為空接合運算符(),它檢查一個表達式是否為null,如果為null,就返回第二個表達式。
Null Coalescing Operator (??)
Starting with C# 2.0, there is a shortcut to the conditional operator when checking for null. The shortcut is the null coalescing operator, and it evaluates an expression for null and returns a second expression if the value is null.
P.85(原文P.120)
fields &= ~mask會從fields中消除mask中已經設置的位。
The opposite, fields &= ~mask, clears out the bits in fields that are set in mask.
P.88(原文P.125)
倒數第3行有錯誤
原文中並無註明是何段代碼,譯者好心加上去但好像加錯了
簡體版:從代碼3-42
應該是:從代碼3-43
If you wrote out each for loop execution step in pseudocode without using a for loop expression, it would look like this:
P.93(原文P.132)
default不一定非要掛在switch語句的最後。default之後的case語句照樣會被評估。
default does not have to appear last within the switch statement. case statements appearing after default are evaluated.
P.114(原文P.160)
return語句意味著跳到方法的末尾,所以它在switch語句中可以代替break。一旦執行到return,方法調用就會終止。
A return statement indicates a jump to the end of the method, so no break is required in a switch statement. Once the execution encounters a return, the method call will end.
雖然C#允許提前返回,但為了增強代碼的可讀性,以及使代碼更易維護,應該盡可能地確定單一的退出位置,而不是在方法的多個代碼路徑中散布多個return語句。
In spite of the C# allowance for early returns, code is generally more readable and easier to maintain if there is a single exit location rather than multiple returns sprinkled through various code paths of the method.
P.116(原文P.163~164)
在文件頂部放置using指令和在命名空間聲明的頂部放置using指令的區別在於,後者的using指令只在你聲明的那個命名空間內有效。
The difference between placing the using declarative at the top of a file rather than at the top of a namespace declaration is that the declarative is active only within the namespace declaration.
P.117(原文P.164)
using CountDownTimer = System.Timers.Timer;
P.126(原文P.175)
參數組必須是方法聲明中的最後一個參數。
However, the parameter array must be the last parameter in the method declaration.
調用者可以為參數數組指定零個參數,這會造成包含零個數據項的一個數組。
The caller can specify zero parameters for the parameter array, which will result in an array of zero items.
P.126(原文P.176)
假如目標方法的實現要求一個最起碼的參數數量,請在方法聲明中顯式指定必要提供的參數,這樣一來,假如要求的參數遺失了,就會導致編譯器報錯,而不需要依賴於運行時錯誤處理。例如,使用int Max(int first, params int[] operands)而不是int Max(params int[] operands)確保至少有一個值傳給Max()。
If the target method implementation requires a minimum number of parameters, then those parameters should appear explicitly within the method declaration, forcing a compile error instead of relying on runtime error handling if required parameters are missing. For example, use int Max(int first, params int[] operands) rather than int Max(params int[] operands) so that at least one value is passed to Max().
使用參數數組,我們可以將相同類型的,數量可變的多個參數傳給一個方法。
Using a parameter array, you can pass a variable number of parameters of the same type into a method.
P.169(原文P.232)
為取值方法或者賦值方法指定訪問修飾符時,注意,這個訪問修符的"限制性"必須比應用於整個屬性的訪問修飾符更"嚴格"。例如,將屬性聲明為較嚴格的private,但將它的賦值方法聲明為較寬鬆的public,就會發生編譯錯誤。
When specifying an access modifier on the getter or setter, take care that the access modifier is more restrictive than the access modifier on the property as a whole. It is a compile error, for example, to declare the property as private and the setter as public.
P.174(原文P.238)
所以,有必要考慮一種編碼風格,避免在同一個類中,既在聲明時賦值,又在構造器中賦值。
Therefore, it is worth considering a coding style that does not mix both declaration assignment and constructor assignment within the same class.
P.178(原文P.243)
可以從一個構造器中調用另一個構造器,以避免輸入重複的代碼。這稱為構造器鍵(constructor chaining),它是用構造器初始化器(constructor initializer)來實現的。構造器會在執行當前構造器的實現之前,判斷要調用另外那一個構造器。
The amount of code is small, but there are ways to eliminate the duplication by calling one constructor from another—constructor chaining—using constructor initializers. Constructor initializers determine which constructor to call before executing the implementation of the current constructor
P.181(原文P.247)
但除非使用Lambda表達式和查詢表達式關聯來自不同型的數據,或者對數據進行水平投射(以減少一個特定類型的總體數據量),否則一般情況下還是應該盡量避免使用匿名類型,甚至避免使用var來指定隱式類型的變量。除非需要頻繁地查詢集合中的數據而使顯式的類型聲明成為一個負擔,否則最好還是像本章描述的那樣顯式地聲明類型。
Although the compiler allows anonymous type declarations such as the ones shown in Listing 5.33, you should generally avoid anonymous type declarations and even the associated implicit typing with var until you are working with lambda and query expressions that associate data from different types or you are horizontally projecting the data so that for a particular type, there is less data overall. Until frequent querying of data out of collections makes explicit type declaration burdensome, it is preferable to explicitly declare types as outlined in this chapter.
P.183(原文P.249)
與實例字段不同,如果不對靜態字段進行初始化,靜態字段將自動獲得預設值(0, null, false等)。另外,一個靜態字段即使沒有顯式地賦值,也可以被訪問。
Unlike with instance fields, if no initialization for a static field is provided, the static field will automatically be assigned its default value (0, null, false, and so on), and it will be possible to access the static field even if it has never been explicitly assigned.
P.186(原文P.254)
最好在聲明的同時完成靜態初始化(而不要使用靜態類構造器)
Favor Static Initialization during Declaration
P.187(原文P.255)
使用靜態屬性幾乎肯定要比使用公共靜態字段好,因為公共靜態字段在任何地方都能調用,而靜態屬性至少提供了一定程度的封裝。
It is almost always better to use a static property rather than a public static field because public static fields are callable from anywhere whereas a static property offers at least some level of encapsulation.
P.190(原文P.258)
除了本章前面討論的屬性和訪問修飾符,還有其它幾種特殊的方式可以將數據封裝到類中。例如,還有另外兩個字段修飾符。第一個是const修飾符,在聲明局部變量的時候,你已經遇到過它。第二個是readonly。
In addition to properties and the access modifiers we looked at earlier in the chapter, there are several other specialized ways of encapsulating the data within a class. For instance, there are two more field modifiers. The first is the const modifier, which you already encountered when declaring local variables. The second is the capability of fields to be defined as read-only.
P.191(原文P.259)
public常量應該是恒常不變的。否則,如果對它進行了修改,那麼在使用它的程序中,不一定能反映出這個修改,如果一個程序集引用了另一個程序集中的常量,常量值將直接編譯到引用程序集中,所以,如果被引用程序集中的值發生改變,而且引用程序集沒有重新編譯,那麼引用程序集將繼續使用原始值,而不是新值。將來可能改變的值應該指定為readonly,不要指定為常量(const)。
Bear: 還記得Effective C#的第2條嗎(Item 2: Prefer readonly to const)?不用const只用readonly。
Public Constants Should Be Permanent Values
public constants should be permanent because changing their value will not necessarily take effect in the assemblies that use it. If an assembly references constants from a different assembly, the value of the constant is compiled directly into the referencing assembly. Therefore, if the value in the referenced assembly is changed but the referencing assembly is not recompiled, then the referencing assembly will still use the original value, not the new value. Values that could potentially change in the future should be specified as readonly instead.
P.191(原文P.260)
和const字段不同,每個實例readonly字段都可以是不同的。事實上,在聲明時指定了一個readonly字段的值之後,這個值可以在構造器更改為一個新值。除此之外,readonly字段既可以是實例字段,也可以是靜態字段,另一個關鍵區別在於,可以在執行時為readonly字段賦值,而非只能在編譯時賦值。
Unlike constant fields, readonly fields can vary from one instance to the next. In fact, a readonly field’s value can change from its value during
declaration to a new value within the constructor. Furthermore, readonly fields occur as either instance or static fields. Another key distinction is that you can assign the value of a readonly field at execution time rather than just at compile time.
P.202(原文P.274)
類型間的轉換並不限於單一繼承鍵的類型,完全不相關的類型相互之間也能進行轉換。這里的關鍵在於在兩個類型之間提供一個轉型運算符。C#允許類型包含顯式或隱式轉型運算符。在轉型有可能失敗時,比如從long轉型為int,開發者就應該選用顯式轉型運算符。這樣可以提醒開發者:只有在確信轉型會成功的時候,才執行這樣的轉型,否則,就準備好在失敗的時候捕捉異常。執行一次有損轉換的時候,開發者也應該優先進行顯式轉型而不是隱式轉型。例如,將float轉型為int,小數部份會被丟棄。即使你接著執行一次反向轉換(int轉型回float),丟失的部份也找不回來。
Defining Custom Conversions
Conversion between types is not limited to types within a single inheritance chain. It is possible to convert between entirely unrelated types as well. The key is the provision of a conversion operator between the two types. C# allows types to include either explicit or implicit conversion operators. Anytime the operation could possibly fail, such as in a cast from long to int, developers should choose to define an explicit conversion operator. This warns developers performing the conversion to do so only when they are certain the conversion will succeed, or else to be prepared to catch the exception if it doesn’t. They should also use explicit conversions over an implicit conversion when the conversion is lossy. Converting from a float to an int, for example, truncates the decimal, which a return cast (from int back to float) would not recover.
P.202(原文P.275)
在這個例子中,你寫的是從GPSCoordinates向UTMCoordinates的一個隱式轉換。
注意,將implicit替換成explicit,還可以寫一個顯式轉換。
// Listing 6.5: Defining Cast Operators class GPSCoordinates { // ... public static implicit operator UTMCoordinates( GPSCoordinates coordinates) { // ... } }
In this case, you have an implicit conversion from GPSCoordinates to UTMCoordinates. A similar conversion could be written to reverse the process. Note that an explicit conversion could also be written by replacing implicit with explicit.
P.222(原文P.300)
下面這種看起來頗為奇怪的代碼實際上是合法的:
Even literals include these methods, enabling somewhat peculiar-looking code such as this:
Console.WriteLine( 42.ToString() );
即使類定義沒有顯式地指明自已從object派生,也肯定是從object派生的。
Even class definitions that don’t have any explicit derivation from object derive from object anyway.
P.224(原文P.302)
is運算符相較於as運算符的一個優點在於,後者不能成功判斷基礎類型。後者允許在一個繼承鍵上向上或向下轉型為支持轉型運算符的類型。所以,和as運算符不同,is運算符能判斷基礎類型。
One advantage of the is operator over the as operator is that the latter cannot successfully determine the underlying type. The latter potentially casts up or down an inheritance chain, as well as across to types supporting the cast operator. Therefore, unlike the as operator, the is operator can determine the underlying type.
P.227(原文P.307)
字段(也就是數據)不能出現在一個接口中。如果一個接口要求派生類包含特定的數據,那麼它會使用屬性而不是字段。
Fields (data) cannot appear on an interface. When an interface requires the derived class to have certain data, it uses a property rather than a field.
P.234(原文P.314~315)
為了聲明一個顯式接口成員實現,需要在成名之前附加接口名前綴。
Listing 7.4: Calling Explicit Interface Member Implementations string[] values; Contact contact1, contact2; // ... // ERROR: Unable to call ColumnValues() directly // on a contact. // values = contact1.ColumnValues; // First cast to IListable. // ...
The cast and the call to ColumnValues occur within the same statement in this case. Alternatively, you could assign contact2 to an IListable variable before calling ColumnValues.
To declare an explicit interface member implementation, prefix the member name with the interface name (see Listing 7.5).
Listing 7.5: Explicit Interface Implementation public class Contact : PdaItem, IListable, IComparable { // ... public int CompareTo(object obj) { // ... } #region IListable Members string[] IListable.ColumnValues { get { return new string[] { FirstName, LastName, Phone, Address }; } } #endregion }
P.235(原文P.316)
由於成員的接口聲明不包含實現,所以override沒有意義。
Interestingly, override is not allowed because the interface declaration of the member does not include implementation, so override is not meaningful.
P.238(原文P.319~320)
在用於顯式接口成員實現的一個完全限定的接口成員名稱中,必須引用最初聲明它的那個接口的名稱。
The fully qualified interface member name used for explicit interface member implementation must reference the interface name in which it was originally declared.
P.238(原文P.320)
在這個代碼清單中,類上的接口實現沒有改變,雖然類標題中的附加接口實現聲明純屬多餘,但它能提供了更好的可讀性。
In this listing, there is no change to the interface’s implementations on the class, and although the additional interface implementation declaration on the class header is superfluous, it can provide better readability.
P.241(原文P.323)
這證明C#不僅允許為一個特定的對象實例添加擴展方法,還允許為那些對象的一個集合添加擴展方法。
This demonstrates that C# allows extension methods not only on an instance of a particular object, but also on a collection of those objects.
P.243(原文P.325)
棒棒糖(lollipop)
P.245(原文P.328~329)
抽象類和接口的比較
TABLE 7.1: Comparing Abstract Classes and Interfaces
□Abstract Classes ■Interfaces
□Cannot be instantiated independently from their derived classes. Abstract class constructors are called only by their derived classes.
■Cannot be instantiated.
□Define abstract member signatures that base classes must implement.
■Implementation of all members of the interface occurs in the base class. It is not possible to implement only some members within the implementing class.
□Are more extensible than interfaces, without breaking any version compatibility. With abstract classes, it is possible to add additional nonabstract
members that all derived classes can inherit.
■Extending interfaces with additional members breaks the version compatibility.
□Can include data stored in fields.
■Cannot store any data. Fields can be specified only on the deriving classes. The workaround for this is to define properties, but without implementation.
□Allow for (virtual) members that have implementation and, therefore, provide a default implementation of a member to the deriving class.
■All members are automatically virtual and cannot include any implementation.
□Deriving from an abstract class uses up a subclass’s one and only base class option.
■Although no default implementation can appear, classes implementing interfaces can continue to derive from one another.
P.249(原文P.336)
雖然語言本身未作要求,但作為一個良好的習慣,應該確保值類型是不可變的。換言之,一旦實例化好了一個值類例,那個實例就不能修改。如果需要修改,應該創建一個新的實例。代碼清單8-1提供了一個Move()方法,它不修改Angle的實例,而是返回一個全新的實例。
Although nothing in the language requires it, a good guideline is for value types to be immutable: Once you have instantiated a value type, you should not be able to modify the same instance. In scenarios where modification is desirable, you should create a new instance. Listing 8.1 supplies a Move() method that doesn’t modify the instance of Angle, but instead returns an entirely new instance.
P.249(原文P.336)
除了屬性和字段,struct中還可以包含方法和構造器,但不能包含預設(無參數)的構造器。
In addition to properties and fields, structs may contain methods and constructors. However, default (parameterless) constructors are not allowed.
P.258(原文P.348)
Disconnected仍然具有預設值0,Connecting則被顯式賦值為10,所以它後面的Connected會被賦值為11。隨後,Joined也被賦值為11,也就是由Connected引用的值(將Connected引用的值賦給Joined時,不需要為Connected附加列舉名稱前綴,因為目前正處在列舉的作用域內)。最後,Disconnecting自動遞增1,所以值是12。
Listing 8.11: Defining an Enum Type enum ConnectionState : short { Disconnected, Connecting = 10, Connected, Joined = Connected, Disconnecting }
Disconnected has a default value of 0, Connecting has been explicitly assigned 10, and consequently, Connected will be assigned 11. Joined is assigned 11, the value referred to by Connected. (In this case, you do not need to prefix Connected with the enum name, since it appears within its scope.) Disconnecting is 12.
P.259(原文P.349)
並非只有有效的列舉值才可以轉換成功。完全可以將42轉型為一個ConnectionState,即使當前沒有對應的ConnectionState列舉值。只要值能成功轉型為基礎類型,轉換就會成功。
Successful conversion doesn’t work just for valid enum values. It is possible to cast 42 into a ConnectionState, even though there is no corresponding ConnectionState enum value. If the value successfully converts to the underlying type, the conversion will be successful.
P.259~260(原文P.349~350)
C#不支持兩個不同的列舉數組之間的直接轉型。然而,有一個辦法可以強制實現兩者之間的轉換,辦法是先轉型為一個數組,再轉型為第二個列舉。這里要求兩個列舉具有相同的基礎類型,而且必須先轉型為System.Array如代碼清單8-12末頁所示。
C# also does not support a direct cast between arrays of two different enums. However, there is a way to coerce the conversion by casting first to an array and then to the second enum. The requirement is that both enums share the same underlying type, and the trick is to cast first to System.Array, as shown at the end of Listing 8.12.
Listing 8.12: Casting between Arrays of Enums enum ConnectionState1 { Disconnected, Connecting, Connected, Disconnecting } enum ConnectionState2 { Disconnected, Connecting, Connected, Disconnecting } class Program { static void Main() { ConnectionState1[] states = (ConnectionState1[])(Array)new ConnectionState2[42]; } }
這個技巧利用了CLR的賦值兼容性比C#寬鬆這一事實。(還可用同樣的技巧進行非法轉換,比如int[]轉換成uint[])。然而,利用這個技巧時務必慎重,因為C#規範沒有說在不同的CLR實現中,這個技巧都應該正常發揮作用。
This exploits the fact that the CLR’s notion of assignment compatibility is more lenient than C#’s. (The same trick is possible for illegal conversions, such as int[] to uint[].) However, use this approach cautiously because there is no C# specification detailing that this should work across different CLR implementations.
P.261~262(原文P.352)
列舉作為標誌使用
由於列舉支持組合的值,按照約定,位標誌的列舉名稱應該是複數。
Listing 8.15: Using Enums As Flags public enum FileAttributes { ReadOnly = 1 << 0, // 000000000000001 Hidden = 1 << 1, // 000000000000010 System = 1 << 2, // 000000000000100 Directory = 1 << 4, // 000000000010000 Archive = 1 << 5, // 000000000100000 Device = 1 << 6, // 000000001000000 Normal = 1 << 7, // 000000010000000 Temporary = 1 << 8, // 000000100000000 SparseFile = 1 << 9, // 000001000000000 ReparsePoint = 1 << 10, // 000010000000000 Compressed = 1 << 11, // 000100000000000 Offline = 1 << 12, // 001000000000000 NotContentIndexed = 1 << 13, // 010000000000000 Encrypted = 1 << 14, // 100000000000000 }Because enums support combined values, the guideline for the enum name of bit flags is plural. P.281(原文P.375) 定義轉型運算符在形式上類似於定義其它運算符,只要是用"operator"替代轉換結果類型,除此之外,operator關鍵字要跟在表示隱式或顯式轉換的implicit或explicit關鍵字後面。 Defining a conversion operator is similar in style to defining any other operator, except that the “operator” is the resultant type of the conversion. Additionally, the operator keyword follows a keyword that indicates whether the conversion is implicit or explicit (see Listing 9.11). P.282(原文P.377) 定義隱式和顯式轉換運算符的差別主要在於,後者能防止不小心執行一次隱式轉型,造成你不希望的行為。使用顯式轉換運算符通常是出於對兩個方面的考慮。首先,會引發異常的轉換運算符始終都應該是顯式的。例如,從string轉換為Coordinate時,提供的string並非一定具有正確的格式(這是極有可能發生的)。因為轉換可能失敗,所以應該將轉換運算符定義為顯式,從而明確要進行轉換的意圖,並要求用戶確保格式正確,或者由你提供代碼來處理可能發生的異常。我們通常採用的轉換模式是:在一個方向上(string到Coordinate)是顯式的,在相反的方向上(Coordinate到string)則是隱式的。 第二個要考慮的是某些轉換可能丟失信息。例如,雖然完全可以從float(4,2)轉換成int,但前提是用戶要知道float小數部份會丟失這一個事實。任何轉換只要會丟失數據,而且不能成功轉換回原始類型,就應該定義成顯式轉換。總之,隱式轉換運算符應當從不引發異常並且從不丟失信息,以便可以在程序員不知曉的情況下安全使用它們。如果轉換運算符不能滿足這些條件,則應將其標記為explicit。 Guidelines for Conversion Operators The difference between defining an implicit and an explicit conversion operator centers on preventing an unintentional implicit conversion that results in undesirable behavior. You should be aware of two possible consequences of using the explicit conversion operator. First, conversion operators that throw exceptions should always be explicit. For example, it is highly likely that a string will not conform to the appropriate format that a conversion from string to Coordinate requires. Given the chance of a failed conversion, you should define the particular conversion operator as explicit, thereby requiring that you be intentional about the conversion and that you ensure that the format is correct, or that you provide code to handle the possible exception. Frequently, the pattern for conversion is that one direction (string to Coordinate) is explicit and the reverse (Coordinate to string) is implicit. A second consideration is the fact that some conversions will be lossy. Converting from a float (4.2) to an int is entirely valid, assuming an awareness of the fact that the decimal portion of the float will be lost. Any conversions that will lose data and not successfully convert back to the original type should be defined as explicit. P.283(原文P.379) 預設情況下,沒有任何訪問修飾符的類會被定義成internal。結果是該類無法從程序集的外部訪問。 By default, a class without any access modifier is defined as internal. The result is that the class is inaccessible from outside the assembly. 嵌套類型除外,它們預設為private。 1. Excluding nested types which are private by default. P.284(原文P.381) protected internal是另一種類型成員訪問修飾符。這種成員可以從包容程序集內部的所有位置以及從該類型派生的類中訪問,即使派生類不在同一個程序集中。 protected internal is another type member access modifier. Members with an accessibility modifier of protected internal will be accessible from all locations within the containing assembly and from classes that derive from the type, even if the derived class is not in the same assembly. P.287(原文P.384~385) 為了引用有別名的程序集中的類,需要提供一個extern指令,它聲明命名空間別名限定符是在外部提供給源代碼的。 However, adding the alias during compilation is not sufficient on its own. In order to refer to classes in the aliased assembly, it is necessary to provide an extern directive that declares that the namespace alias qualifier is provided externally to the source code (see Listing 9.16).
Listing 9.16: Using the extern Alias Directive // extern must precede all other namespace elements extern alias CoordPlus; using System; using CoordPlus ::AddisonWesley.Michaelis.EssentialCSharp // Equivalent also allowed // using CoordPlus .AddisonWesley.Michaelis.EssentialCSharp using global ::AddisonWesley.Michaelis.EssentialCSharp // Equivalent NOT allowed // using global.AddisonWesley.Michaelis.EssentialCSharp public class Program { // ... }Once the extern alias for CoordPlus appears, you can reference the namespace using CoordPlus, followed by either two colons or a period. 為了確保類型的查找發生在全局命名空間中,C# 2.0允許使用global::限定符(但不允許使用global.,否則會被誤會成一個真正的、名為global的命名空間。 To ensure that the lookup for the type occurs in the global namespace, C# 2.0 allows items to have the global:: qualifier (but not global. because it could imaginably conflict with a real namespace of global). P.299(原文P.400) 你的代碼應該確保在調用了Dispose()之後,對象不能繼續使用。具體地說,在對一個對象進行"釋放"之後,除Dispose()之外的其它任何方法都應引發一個ObjectDisposedException()異常(Dispose()方法可以多次調用)。 9. Generally, objects should be coded as unusable after Dispose() is called. After an object has been disposed, methods other than Dispose() (which could potentially be called multiple times) should throw an ObjectDisposedException(). P.300(原文P.401) 將一個對象的初始化推遲到需要這個對象時進行,就稱為推遲初始化。 Deferring the initialization of an object until it is required is called lazy initialization. 從.NET Framework 4.0開始,CLR添加了一個新類來幫助進行推遲初始化,這個類就是System.Lazy<T>。 Lazy Loading with Generics and Lambda Expressions Starting with .NET Framework 4.0, a new class was added to the CLR to assist with lazy initialization: System.Lazy<T>. P.306~308(原文P.411~413) 以下指導原則提供了異常處理的最佳實踐。 Guidelines for Exception Handling Exception handling provides much-needed structure to the error-handling mechanisms that preceded it. However, it can still make for some unwieldy results if used haphazardly. The following guidelines offer some best practices for exception handling. 只捕捉你能處理的異常。 A. Catch only the exceptions that you can handle. Generally it is possible to handle some types of exceptions but not others. For example, opening a file for exclusive read-write access may throw a System.IO.IOException because the file is already in use. In catching this type of exception, the code can report to the user that the file is in use and allow the user the option of canceling the operation or retrying it. Only exceptions for which there is a known action should be caught. Other exception types should be left for callers higher in the stack. 不要隱藏(bury)你不能完全處理的異常。 B. Don’t hide (bury) exceptions you don’t fully handle. New programmers are often tempted to catch all exceptions and then continue executing instead of reporting an unhandled exception to the user. However, this may result in a critical system problem going undetected. Unless code takes explicit action to handle an exception or explicitly determines certain exceptions to be innocuous, catch blocks should rethrow exceptions instead of catching them and hiding them from the caller. Predominantly, catch(System.Exception) and general catch blocks should occur higher in the call stack, unless the block ends by rethrowing the exception. 盡可能少地使用System.Exception和常規cactch塊。 C. Use System.Exception and general catch blocks rarely. Virtually all exceptions derive from System.Exception. However, the best way to handle some System.Exceptions is to allow them to go unhandled or to gracefully shut down the application sooner rather than later. These exceptions include things such as System.OutOfMemoryException and System.StackOverflowException. In CLR 4, such exceptions were defaulted to nonrecoverable such that catching them without rethrowing them will cause the CLR to rethrow them anyway. These exceptions are runtime exceptions that the developer cannot write code to recover from. Therefore, the best course of action is to shut down the application—something the runtime will force in CLR 4. Code prior to CLR 4 should catch such exceptions only to run cleanup or emergency code (such as saving any volatile data) before shutting down the application or rethrowing the exception with throw;. 避免在調用棧較低的位置報告或記錄異常。 D. Avoid exception reporting or logging lower in the call stack. Often, programmers are tempted to log exceptions or report exceptions to the user at the soonest possible location in the call stack. However, these locations are seldom able to handle the exception fully and they resort to rethrowing the exception. Such catch blocks should not log the exception or report it to a user while in the bowels of the call stack. If the exception is logged and rethrown, the callers higher in the call stack may do the same, resulting in duplicate log entries of the exception. Worse, displaying the exception to the user may not be appropriate for the type of application. (Using System.Console.WriteLine() in a Windows application will never be seen by the user, for example, and displaying a dialog in an unattended command-line process may go unnoticed and freeze the application.) Logging- and exception-related user interfaces should be reserved for high up in the call stack. 在一個catch塊中使用throw;而不是throw <異常對象>語句。 E. Use throw; rather than throw <exception object> inside a catch block. It is possible to rethrow an exception inside a catch block. For example, the implementation of catch(ArgumentNullException exception) could include a call to throw exception. However, rethrowing the exception like this will reset the stack trace to the location of the rethrown call, instead of reusing the original throw point location. Therefore, unless you are rethrowing with a different exception type or intentionally hiding the original call stack, use throw; to allow the same exception to propagate up the call stack. 重新引發不同的異常時要小心。 通常只應在應在以下情況下才重新引發一個不同的異常。 F. Use caution when rethrowing different exceptions. From inside a catch block, rethrowing a different exception will not only reset the throw point, it will also hide the original exception. To preserve the original exception set the new exception’s InnerException property, generally assignable via the constructor. Rethrowing a different exception should be reserved for the following situations. 更改異常類型可以更好地澄清問題。 1. Changing the exception type clarifies the problem. For example, in a call to Logon(User user), rethrowing a different exception type is perhaps more appropriate than propagating System.IO.IOException when the file with the user list is inaccessible. 私有數據是原始異常一部份。 2. Private data is part of the original exception. In the preceding scenario, if the file path is included in the original System.IO.IOException, thereby exposing private security information about the system, the exception should be wrapped. This assumes, of course, that InnerException is not set with the original exception. (Funnily enough, a very early version of CLR v1 (pre-alpha even) had an exception that said something like “Security exception: You do not have permission to determine the path of c:\temp\foo.txt”.) 異常類型過於具體,以至於調用者不能恰當地處理。 3. The exception type is too specific for the caller to handle appropriately. For example, instead of throwing an exception specific to a particular database system, a more generic exception is used so that database-specific code higher in the call stack can be avoided. P.309(原文P.415) 自定義異常唯一的硬性要求就是它必須從System.Exception或者其某個子類派生。除此之外,在使用自定義異常的時候,還應遵照以下最佳實踐。 The only requirement for a custom exception is that it derives from System.Exception or one of its descendents. However, there are several more good practices for custom exceptions. 所有異常都應該使用"Exception"後綴,彰顯其用途。 1. All exceptions should use the “Exception” suffix. This way, their purpose is easily established from the name. 通常,所有異常都應該包含以下3個構造器:無參數構造器、獲得一個string參數的構造器以及同時獲取一個字符串和一個內部異常作為參數的構造器。 2. Generally, all exceptions should include constructors that take no parameters, a string parameter, and a parameter set of a string and an inner exception. Furthermore, since exceptions are usually constructed within the same statement in which they are thrown, any additional exception data should also be allowed as part of the constructor. (The obvious exception to creating all these constructors is if certain data is required and a constructor circumvents the requirements.) 避免使用深的繼承層次結構(一般應該小於5級)。 3. The inheritance chain should be kept relatively shallow (with fewer than approximately five levels). P.323(原文P.433~434) 模板接口造成的另一個結果是,可以使用不同的類型參數來多次實現同一個接口。 One side effect of template interfaces is that you can implement the same interface many times using different type parameters. Consider the IContainer< T> example in Listing 11.10.
Listing 11.10: Duplicating an Interface Implementation on a Single Class public interface IContainer<T> { ICollection<T> Items { get; set; } } public class Person : IContainer<Address>, IContainer<Phone>, IContainer<Email> { ICollection<Address> IContainer<Address>.Items { get{...} set{...} } ICollection<Phone> IContainer<Phone>.Items { get{...} set{...} } ICollection<Email> IContainer<Email>.Items { get{...} set{...} } }在這個例子中,Items屬性使用一個顯式接口實現多次出現,每一次,類型參數都有所不同。如果沒有泛型,這是不可能的。在沒有泛型的情況下,編譯器只允許一個顯式的IContainer.Items屬性。 In this example, the Items property appears multiple times using an explicit interface implementation with a varying type parameter. Without generics, this is not possible, and instead, the compiler would allow only one explicit IContainer.Items property. P.332(原文P.444~445) 類型約束的語法與接口約束基本相同。但是,假如同時指定了多個約束,那麼基類約束必須第一個出現。然而,和接口約束不同,多個基類約束是不允許的,因為不可能從多個類派生。類似地,不能為密封類或者某些特殊的結構指定基類約束。例如,C#不允許在一個約束中將參數類型限制為必須從string或者System.Nullable<T>派生。 The syntax for the base class constraint is the same as that for the interface constraint, except that base class constraints must appear first when multiple constraints are specified. However, unlike interface constraints, multiple base class constraints are not allowed since it is not possible to derive from multiple classes. Similarly, base class constraints cannot be specified for sealed classes or specific structs. For example, C# does not allow a constraint for a type parameter to be derived from string or System.Nullable<T>. 另一個重要的泛型約束是將類型參數限制為一個值類型或者一個引用類型。編譯器不允許在一個約束中將System.ValueType指定為基類。相反,C#提供了特殊的語法,這種語法同時適用於引用類型,不是為T指定一個基類。相反,只需指定關鍵字struct 或者 class。 Another valuable generic constraint is the ability to restrict type parameters to a value type or a reference type. The compiler does not allow specifying System.ValueType as the base class in a constraint. Instead, C# provides special syntax that works for reference types as well. Instead of specifying a class from which T must derive, you simply use the keyword struct or class, as shown in Listing 11.24.
Listing 11.24: Specifying the Type Parameter As a Value Type public struct Nullable<T> : IFormattable, IComparable, IComparable<Nullable<T>>, INullable { // ... }P.333(原文P.446) 對於任何給定的類型參數,都可以指定任意數量的接口作為約束,但基類約束只能指定一個,因為一個類可以實現任意數量的接口,但肯定只能從一個類繼承。每個新約束都在一個以逗號分隔的列表中聲明,約束列表跟在泛型類型名稱和一個冒號之後。如果有多個類型參數,那麼每個類型名稱的前面都要使用一個where關鍵字。 For any given type parameter, you may specify any number of interfaces as constraints, but no more than one class, just as a class may implement any number of interfaces but inherit from only one other class. Each new constraint is declared in a comma-delimited list following the generic type and a colon. If there is more than one type parameter, each must be preceded by the where keyword. In Listing 11.25, the EntityDictionary class contains two type parameters: TKey and TValue. The TKey type parameter has two interface constraints, and the TValue type parameter has one base class constraint.
Listing 11.25: Specifying Multiple Constraints public class EntityDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TKey : IComparable<TKey>, IFormattable where TValue : EntityBase { .... }In this case, there are multiple constraints on TKey itself and an additional constraint on TValue. When specifying multiple constraints on one type parameter, an AND relationship is assumed. TKey must implement IComparable<TKey> and IFormattable, for example. 注意,在兩個where子句之間,並不存在逗號。 Notice there is no comma between each where clause. P.336(原文P.450) 假如為一個類型參數提供多個接口約束或者類約束,編譯器認為不同約束之間總是存在一個AND關係。 你不能在約束之間指定一個OR關係。 If you supply multiple interfaces or class constraints for a type parameter, the compiler always assumes an AND relationship between constraints. For example, where T : IComparable<T>, IFormattable requires that both IComparable<T> and IFormattable are supported. There is no way to specify an OR relationship between constraints. Hence, an equivalent of Listing 11.30 is not supported.
Listing 11.30: Combining Constraints Using an OR Relationship Is Not Allowed public class BinaryTree<T> // Error: OR is not supported. where T: System.IComparable<T> || System.IFormattable { .... }Supporting this would prevent the compiler from resolving which method to call at compile time. P.337(原文P.450) 將任何委托類作為一個類約束來使用是一種不被允許的約束。 Readers who are already familiar with C# 1.0 and are reading this chapter to learn newer features will be familiar with the concept of delegates, which are covered in Chapter 12. One additional constraint that is not allowed is the use of any delegate type as a class constraint. For example, the compiler will output an error for the class declaration in Listing 11.31.
Listing 11.31: Inheritance Constraints Cannot Be of Type System.Delegate // Error: Constraint cannot be special class 'System.Delegate' public class Publisher<T> where T : System.Delegate { public event T Event; public void Publish() { if (Event != null) { Event(this, new EventArgs()); } } }所有委托類型都被視為是不能指定為類型參數的特殊類。否則,編譯時就無法對Event()調用進行驗證,因為對於數據類型System.Delegate 和 System.MulticastDelegate 來說,所觸發事件的簽名是未知的,同樣的限制也適用於任何列舉類型。 All delegate types are considered special classes that cannot be specified as type parameters. Doing so would prevent compile-time validation on the call to Event() because the signature of the event firing is unknown with the data types System.Delegate and System.MulticastDelegate. The same restriction occurs for any enum type. P.340(原文P.454) 很明顯,類型參數int和string對應於泛型方法調用中使用的實際類型。然而,這裡指定類型是多餘的,因為編譯器能根據傳給方法的參數推斷出類型。為了避免多餘的編碼,你可以從調用中拿掉類型參數。這就是所謂的類型推斷。 Type Inferencing The code used to call the Min<T> and Max<T> methods looks like that shown in Listing 11.36.
Listing 11.36: Specifying the Type Parameter Explicitly Console.WriteLine( MathEx.Max<int>(7, 490)); Console.WriteLine( MathEx.Min<string>("R.O.U.S.", "Fireswamp"));OUTPUT 11.4: 490 Fireswamp Not surprisingly, the type parameters, int and string, correspond to the actual types used in the generic method calls. However, specifying the type is redundant because the compiler can infer the type from the parameters passed to the method. To avoid redundancy, you can exclude the type parameters from the call. This is known as type inferencing, and an example appears in Listing 11.37. The output appears in Output 11.5.
Listing 11.37: Inferring the Type Parameter Console.WriteLine( MathEx.Max(7, 490)); Console.WriteLine( MathEx.Min("R.O.U.S'", "Fireswamp"));OUTPUT 11.5: 490 Fireswamp 類型推斷要想成功,類型必須與方法簽名匹配。然而,從C#3.0起,編譯器有了一個增強措施,允許將類型參數隱含-只要類型參數是隱式兼容的。例如,使用MathEx.Max(7.0, 490)調用Max<T>方法會成功通過編譯。雖然兩個參數的類型不同(一個是int,一個是double),但它們都能隱式轉換為double,所以方法調用能成功編譯。如果類型推斷出錯,可以顯式轉型或者指定類型實參。還要注意的是,不能僅僅依據返回類型來執行推斷。有了參數才能進行類型推斷。 For type inferencing to be successful, the types must match the method signature. However, starting with C# 3.0, the compiler added an enhancement to imply the type parameter as long as the types were implicitly compatible. For example, calling the Max<T> method using MathEx.Max(7.0, 490) will compile successfully. Even though the parameters are not both the same type (int and double), they will both implicitly convert to double, so the method call compiles. You can resolve the error by either casting explicitly or including the type argument. Also note that you cannot perform type inferencing purely on the return type. Parameters are required for type inferencing to be allowed. P.341(原文P.455) 泛型方法也允許指定約束。例如,你可以指定一個類型參數必須實現IComparable<T>。約束緊接在方法頭的後面,但位於構成方法主體的大括號之前。 Specifying Constraints The generic method also allows constraints to be specified. For example, you can restrict a type parameter to implement IComparable<T>. The constraint is specified immediately following the method header, prior to the curly braces of the method block, as shown in Listing 11.38.
Listing 11.38: Specifying Constraints on Generic Methods public class ConsoleTreeControl { // Generic method Show<T> public static void Show<T>(BinaryTree<T> tree, int indent) where T : IComparable<T> { Console.WriteLine("\n{0}{1}", "+ --".PadLeft(5 * indent, ' '), tree.Item.ToString()); if (tree.SubItems.First != null) Show(tree.SubItems.First, indent + 1); if (tree.SubItems.Second != null) Show(tree.SubItems.Second, indent + 1); } }注意,Show<T>實現本身沒有使用IComparable<T>接口,但BinaryTree<T>類需要這個接口。 Notice that the Show<T> implementation itself does not use the IComparable<T> interface. Recall, however, that the BinaryTree<T> class did require this (see Listing 11.39). Listing 11.39: BinaryTree<T> Requiring IComparable<T> Type Parameters
public class BinaryTree<T> where T : System.IComparable<T> { ..... }因為BinaryTree<T>類為T施加了這個約束,而且Show<T>使用了BinaryTree<T>,所以Show<T>也需要提供約束。 Because the BinaryTree<T> class requires this constraint on T, and because Show<T> uses BinaryTree<T>, Show<T> also needs to supply the constraint. P.342(原文P.457) 在方法中執行顯式轉型,相較於在泛型版本中執行隱式轉型,前者能夠更加明確地描述所發生的事情。開發者在泛型方法中執行轉型時,假如沒有約束來驗證轉型的有效性,那麼一定要非常小心。 A method using an explicit cast is more explicit about what is taking place than is a generic version with a hidden cast. Developers should use care when casting in generic methods if there are no constraints to verify cast validity. P.347(原文P.462~463) 換言之,雖然C#2.0引入了對協變量和逆變量的限制,但C#4.0放寬了那些限制,允許在有效的情形中進行協變和逆變。 如第14章所述,利用一系列接口和集合,可替換掉傳統數組,並提供數組的一個超集。由於支持泛型,再加上C#3.0用於初始化數組的語法(參見14.2節),傳統的在使用數組要遵守的"最佳實踐"就可以放棄了-除非是為了保持和現有的接口兼容。將來,數組可能會被完全廢棄。 Support for Parameter Covariance and Contravariance in Arrays Unfortunately, ever since C# 1.0, arrays allowed for covariance and contravariance. For example, both PdaItem[] pdaItems = new Contact[] { } and Contact[] contacts = (Contact[])new PdaItem[] { } are valid assignments in spite of the negative implications discussed earlier. The result is that the covariant and contravariant restrictions imposed by the compiler in C# 2.0 and the loosening of those restrictions in C# 4.0 to enable valid scenarios do not apply to arrays. As regrettable as this is, the situation can be avoided. As Chapter 14 describes, a host of interfaces and collections are available that effectively supersede arrays and enable a super set of functionality. Support for generics in combination with C# 3.0 syntax for initializing arrays (see Collection Initializers in Chapter 14) eliminates any best practice use of arrays except when required by existing interfaces. Moving forward, arrays may be treated as deprecated. P.367(原文P.488~489) 通常,只要編譯器能推斷出參數類型,或者能將參數類型隱式轉換成期望的類型,語句Lambda就不需要參數類型。然而,如果要指定類型,那麼指定的類型必須和委托類型完全匹配。如果無法進行推斷,就必須加上數據類型。另外,即使數據類型不是必需的,你也可以顯式地指定數據類型來增強可讀性,只要語句Lambda包含了一個類型,所有類型都要加上。 通常,C#要求用一對圓括號來封閉Lambda表達式的參數列表,不管是否指定了這些參數的數據類型,即使是無參數的語句Lambda(代表無輸入參數的委托),也要輸入一對空白的圓括號。 In general, statement lambdas do not need parameter types as long as the compiler can infer the types or can implicitly convert them to the requisite expected types. If the types are specified, however, there must be an exact match for the delegate type. In cases where inference is not possible, the data type is required, although even when it is not required, you can specify the data type explicitly to increase readability; once the statement lambda includes one type, all types are required. In general, C# requires a lambda expression to have parentheses around the parameter list regardless of whether the data type is specified. Even parameterless statement lambdas, representing delegates that have no input parameters, are coded using empty parentheses (see Listing 12.17).
Listing 12.17: Parameterless Statement Lambdas using System; // ... Func<string> getUserInput = () => { string input; do { input = Console.ReadLine(); } while (input.Trim().Length == 0); return input; }; // ...圓括號規則的另一個例外是,當編譯器能推斷數據類型,而且只有一個輸入參數的時候,語句Lambda可以不帶圓括號。 The exception to the parenthesis rule is that if the compiler can infer the data type and there is only a single input parameter, the statement lambda does not require parentheses (see Listing 12.18).
Listing 12.18: Statement Lambdas with a Single Input Parameter using System.Collections.Generic; using System.Diagnostics; using System.Linq; // ... IEnumerable<Process> processes = Process.GetProcesses().Where( process => { return process.WorkingSet64 > 2 ^ 30; }); // ...P.369~370(原文P.491~493) 表12-1總結了Lambda表達式的其它注意事項。 TABLE 12.1: Lambda Expression Notes and Examples Statement Example Lambda expressions themselves do not have type. In fact, there is no concept of a lambda expression in the CLR. Therefore, there are no members to call directly from a lambda expression. The . operator on a lambda expression will not compile, eliminating even the option of calls to object methods. // ERROR: Operator '.' cannot be applied to // operand of type 'lambda expression' Type type = ((int x) => x).ToString(); Given that a lambda expression does not have an intrinsic type, it cannot appear to the right of an is operator. // ERROR: The first operand of an 'is' or 'as' // operator may not be a lambda expression or // anonymous method bool boolean = ((int x) => x) is Func<int, int> Although there is no type on the lambda expression on its own, once assigned or cast, the lambda expression takes on a type. Therefore, it is common for developers to informally refer to the type of the lambda expression concerning type compatibility, for example. // ERROR: Lambda expression is not compatible with // Func<int, bool> type. Func<int, bool> expression = ((int x) => x); A lambda expression cannot be assigned to an implicitly typed local variable since the compiler does not know what type to make the variable given that lambda expressions do not have type. // ERROR: Cannot assign lambda expression to an // implicitly typed local variable var thing = (x => x); C# does not allow jump statements (break, goto, continue) inside anonymous functions if the target is outside the lambda expression. Similarly, you cannot target a jump statement from outside the lambda expression (or anonymous methods) into the lambda expression.
// ERROR: Control cannot leave the body of an // anonymous method or lambda expression string[] args; Func<string> expression; switch (args[0]) { case "/File": expression = () => { if (!File.Exists(args[1])) { break; } // ... return args[1]; }; // ... }Variables introduced within a lambda expression are visible only within the scope of the lambda expression body. // ERROR: The name 'first' does not // exist in the current context Func<int, int, bool> expression = (first, second) => first > second; first++; The compiler’s flow analysis is unable to detect initialization of local variables in lambda expressions.
int number; Func<string, bool> expression = text => int.TryParse(text, out number); if (expression("1")) { // ERROR: Use of unassigned local variable System.Console.Write(number); } int number; Func<int, bool> isFortyTwo = x => 42 == (number = x); if (isFortyTwo(42)) { // ERROR: Use of unassigned local variable System.Console.Write(number); }P.385(原文P.513-514) Listing 13.5: Invoking a Delegate
public class Thermostat { public float CurrentTemperature { get { return _CurrentTemperature; } set { if (value != CurrentTemperature) { _CurrentTemperature = value; // If there are any subscribers // then notify them of changes in // temperature TemperatureChangeHandler localOnChange = OnTemperatureChange; if (localOnChange != null) { // Call subscribers localOnChange(value); } } } } private float _CurrentTemperature; }在這裡,我們並不是一開始就檢查空值,而是首先將OnTemperatureChange賦值給另一個委托變量localOnChange。這個簡單的修改可以確保在檢查空值和發送通知之間,假如所有OnTemperatureChange訂閱者都被移除(由一個線程),那麼不會觸發NullReferenceException異常。 Instead of checking for null directly, first assign OnTemperatureChange to a second delegate variable, handlerCopy. This simple modification ensures that if all OnTemperatureChange subscribers are removed (by a different thread) between checking for null and sending the notification, you will not fire a NullReferenceException. 再次提醒你記住:調用一個委托之前,要檢查它的值是不是空值。 One more time: Remember to check the value of a delegate for null before invoking it. A D V A N C E D T O P I C -= Operator for a Delegate Returns a New Instance Given that a delegate is a reference type, it is perhaps somewhat surprising that assigning a local variable and then using that local variable is sufficient for making the null check thread-safe. Since localOnChange points at the same location that OnTemperatureChange points, one would think that any changes in OnTemperatureChange would be reflected in localOn-Change as well. This is not the case, because effectively, any calls to OnTemperatureChange -= <listener> will not simply remove a delegate from OnTemperatureChange so that it contains one less delegate than before. Rather, it will assign an entirely new multicast delegate without having any effect on the original multicast delegate to which localOnChange also points. P.390(原文P.519) 錯誤處理凸顯了順序通知的重要性。假如一個訂閱者引發了一個異常,鍵中的後續訂閱者就接收不到通知。 Error Handling Error handling makes awareness of the sequential notification critical. If one subscriber throws an exception, later subscribers in the chain do not receive the notification. P.392(原文P.521) 這個代碼清單演示了你可以從一個委托的GetInvocationList()方法獲得一份訂閱者列表。列舉該列表的每一項,可以返回單獨的訂閱者。若隨後將每個訂閱者調用都放到一個try/catch塊中,就可以先處理好任何出錯的情形,再繼續循環迭代。在這個例子中,盡管委托偵聽者(delegate listener)引發了一個異常,cooler仍會接收到溫度發生改變的通知。 This listing demonstrates that you can retrieve a list of subscribers from a delegate’s GetInvocationList() method. Enumerating over each item in this list returns the individual subscribers. If you then place each invocation of a subscriber within a try/catch block, you can handle any error conditions before continuing with the enumeration loop. In this sample, even though the delegate listener throws an exception, cooler still receives notification of the temperature change. P.394(原文P.524) 在本該使用"+="運算符的地方使用了賦值運算符"=",由於這是一個十分容易犯的錯誤,所以最好的解決方案就是根本不為包容類外部的對象提供對賦值運算符的支持。event關鍵字的目的就是提供額外的封裝,避免你不小心地取消其它訂閱者。 The potential for mistakenly using an assignment operator, when in fact the += assignment was intended, is so high that it would be preferable if the assignment operator were not even supported for objects except within the containing class. It is the purpose of the event keyword to provide additional encapsulation such that you cannot inadvertently cancel other subscribers. P.395~396(原文P.526) 這個新的Thermostat類進行了4處修改。首先,OnTemperatureChange屬性被移除了,OnTemperatureChange被聲明為一個public字段。從表面上看,這似乎並不是在解決早先描述的封裝問題。現在需要的是增強封裝,而不是讓一個字段變成public字段來削弱封裝。然而,我們進行的第二處修改是在字段聲明之前添加event關鍵字。這一處簡單的修改提供了我們需要的全部封裝。添加event關鍵字後,會禁止為一個public托字段使用賦值運算符(比如 thermostat.OnTemperatureChange = cooler.OnTemperatureChanged)。除此之外,只有包容類才能調用向所有訂閱者發出通知的委托(例如,不允許在類的外部執行thermostat.OnTemperatureChange(42)。換言之,event關鍵字提供了必要的封裝來防止任何外部類發布一個事件或者取消之前的訂閱者。這樣,就完美地解決了普通委托存在的兩個問題,這是在C#中提供event關鍵字的關鍵原因之一。 The new Thermostat class has four changes from the original class. First, the OnTemperatureChange property has been removed, and instead, OnTemperatureChange has been declared as a public field. This seems contrary to solving the earlier encapsulation problem. It would make more sense to increase the encapsulation, not decrease it by making a field public. However, the second change was to add the event keyword immediately before the field declaration. This simple change provides all the encapsulation needed. By adding the event keyword, you prevent use of the assignment operator on a public delegate field (for example, thermostat.OnTemperatureChange = cooler.OnTemperatureChanged). In addition, only the containing class is able to invoke the delegate that triggers the publication to all subscribers (for example, disallowing thermostat.OnTemperatureChange(42) from outside the class). In other words, the event keyword provides the needed encapsulation that prevents any external class from publishing an event or unsubscribing previous subscribers they did not add. This resolves the two issues with plain delegates and is one of the key reasons for the event keyword in C#. P.404(原文P.538) 匿名類型完全是由C#編譯器來實現的,而不會在運行時內有顯式實現。具體地說,當編譯器遇到匿名類型的語法時,會自動生成一個CIL類,其屬產和在匿名類型聲明中命名的值和數據類型是對應的。 The construct of an anonymous type is implemented entirely by the C# compiler, with no explicit implementation awareness within the runtime. Rather, when the compiler encounters the anonymous type syntax, it generates a CIL class with properties corresponding to the named values and data types in the anonymous type declaration. 由於根據定義,匿名類型是沒有名稱的,所以不可能將一個局部變量顯式聲明為匿名類型。相反的,局部變量的類型要替換成var。然而,並不是說隱式類型的變量就沒有類型了。相反的,這只是說他們的類型是在編譯時確定的,具體地說就是賦給它們的值的數據類型。 Implicitly Typed Local Variables (var) Since an anonymous type by definition has no name, it is not possible to declare a local variable as explicitly being of an anonymous type. Rather, the local variable’s type is replaced with var. However, by no means does this indicate that implicitly typed variables are untyped. On the contrary, they are fully typed to the data type of the value they are assigned. P.405(原文P.539) 雖然C#的匿名類型沒有可用的名稱,但它仍然是強類型的。例如,類型的屬性是完全可以訪問的。 在Visual Studio 2008 IDE中,就連"智能感知"功能都能很好地支持匿名類型。 Although there is no available name in C# for the anonymous type, it is still strongly typed as well. For example, the properties of the type are fully accessible. In Listing 14.1, patent1.Title and patent2.YearOfPublication are called within the Console.WriteLine statement. Any attempts to call nonexistent members will result in compile errors. Even IntelliSense in IDEs such as Visual Studio 2008 works with the anonymous type. P.406(原文P.540) 換言之,兩個匿名類型要想在同一個程序集中做到類型兼容,要求屬性名、數據類型和屬性順序都完全匹配。只要滿足這些條件,類型就是兼容的-即使他們出現在不同的方法或類中。 P.S: 所謂類型兼容,就是指最終只生成一個CIL類型。 In other words, the requirements for two anonymous types to be type-compatible within the same assembly are a match in property names, data types, and order of properties. If these criteria are met, the types are compatible even if they appear in different methods or classes. Listing 14.2 demonstrates the type incompatibilities. P.407(原文P.541) 雖然代碼清單14-2沒有顯示,但請記住在聲明一個方法時,不可能將它的某個參數聲明為隱式數據類型(var)。 Although not shown in Listing 14.2, it is not possible to declare a method with an implicit data type parameter (var). 雖然,Console.WriteLine()的實現是調用ToString(),但注意在代碼清單14-1中,Console.WriteLine()的輸出並不是預設的ToString()(預設的ToString()輸出的是完全限定的數據類型名稱)。相反,現在輸出的是一對對的PropertyName = value,匿名類型的每個屬性都有一對這樣的輸出。之所以會得到這樣的結果,是因為編譯器在生成匿名類型的代碼時,重寫了ToString()方法,像輸出14-1展示的那樣對輸出進行了格式化。類似地,在生成的類型中也重寫了Equals()和GetHashCode()的實現。 Anonymous Type Generation Even though Console.WriteLine()’s implementation is to call ToString(), notice in Listing 14.1 that the output from Console.WriteLine() is not the default ToString(), which writes out the fully qualified data type name. Rather, the output is a list of PropertyName = value pairs, one for each property on the anonymous type. This occurs because the compiler overrides ToString() in the anonymous type code generation, and instead formats the ToString() output as shown. Similarly, the generated type includes overriding implementations for Equals() and GetHashCode(). P.408~409(原文P.544) 這個語法不僅和數組初始化的語法相似,也和對象初始化器(參見5.7.3節)的語法相似,都是在構造器調用的後面添加一一對大括號,然後在大括號內添加一個初始化列表。如果調用構造器時沒有傳遞參數,那麼數據類型名稱之後的圓括號是可要可不要的(這一點同對象初始化器)。 The syntax is similar not only to the array initialization, but also to an object initializer with the curly braces following the constructor. If no parameters are passed in the constructor, the parentheses following the data type are optional (as they are with object initializers). 其次,由於要匹配方法名,而且方法的簽名要和集合的初始化項兼容,所以可以在集合中初始化更加多樣化的數據項。例如,初始化器現在支持new DataStore(){ a, {b, c}}-只要一個Add()方法的簽名兼容於a,另一個Add()方法兼容於b, c。 Second, matching on the method name and signature compatibility with the collection initialize items enables greater diversity in the items initialized into the collection. For example, the initializer now can support new DataStore(){ a, {b, c}} as long as there is one Add() method whose signature is compatible with a and a second Add() method compatible with b, c. P.409(原文P.545) 為了初始化匿名類型的一個集合,另一個解決方案是使用數組初始化器。由於不可能在構造器中指定數據類型,所以允許使用new[],從而為匿名數組初始化器使用數組初始化語法。 Another approach to initializing a collection of anonymous types is to use an array initializer. Since it is not possible to specify the data type in the constructor, array initialization syntax allows for anonymous array initializers using new[] (see Listing 14.4).
Listing 14.4: Initializing Anonymous Type Arrays using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { var worldCup2006Finalists = new[] { new { ....P.411(原文P.548) Reset()方法一般拋出一個NotImplementedException,所以永遠都不應調用它。如果需要重新開始枚舉,只需創建一個新的枚舉數。 (The Reset() method usually throws a NotImplementedException and, therefore, should never be called. If you need to restart an enumeration, just create a fresh enumerator.) P.414(原文P.551) 注意,由於IEnumerator<T>支持IDisposable,所以可以使用using關鍵字來簡化代碼。 Notice that because the IDisposable interface is supported by IEnumerator<T>, the using statement can simplify the code in Listing 14.9 to that shown in Listing 14.10.
Listing 14.10: Error Handling and Resource Cleanup with using System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>(); int number; using ( System.Collections.Generic.Stack<int>.Enumerator<int> enumerator = stack.GetEnumerator()) { while (enumerator.MoveNext()) { number = enumerator.Current; Console.WriteLine(number); } }但要記住,using關鍵字也不是直接由CIL支持的,所以實際上,代碼清單14-9的C#代碼才更準確地對應於foreach的CIL代碼。 However, recall that the CIL also does not directly support the using keyword, so in reality, the code in Listing 14.9 is a more accurate C# representation of the foreach CIL code. P.415(原文P.551-552) 從技術上說,編譯器為了用foreach在一個數據類型上迭代,並不要求一定要支持IEnumerable/IEnumerable<T>。相反的,編譯器采用一個稱為"duck typing"概念。如果沒有發現IEnumerable/IEnumerable<T>的方法,它就查找一個GetEnumerator()方法來返回,具有Current()和MoveNext()方法的類型。Duck typing要求按名稱搜索一個方法,而不是依賴接口或顯式方式調用。 foreach without IEnumerable Technically, the compiler doesn’t require that IEnumerable/IEnumerable<T> be supported in order to iterate over a data type using foreach. Rather, the compiler uses a concept known as “duck typing” such that if no IEnumerable/IEnumerable<T> method is found, it looks for the GetEnumerator() method to return a type with Current() and MoveNext() methods. Duck typing involves searching for a method by name rather than relying on an interface or explicit method call to the method. 如果將 System.Object定義的方法排除在外,那麼實現IEnumerable<T>的任何類型都只有一個方法,即GetEnumerator()。但看問題不能只看到表面,任何類型在實現IEnumerable<T>之後,都有超過50個方法可供使用,其中還不包括重載的版本。而為了享受到所有這一切,除了GetEnumerator()之外,你根本不需要顯式地實現任何方法。附加的功能是由C#3.0的擴展方法來提供的,所有方法都在System.Linq.Enumerable類中定義。所以,為了用到這些方法,只需簡單地添加以下語句即可:using System.Linq; P.415(原文P.552) Besides the methods on System.Object, any type that implements IEnumerable<T> has only one method, GetEnumerator(). And yet, it makes more than 50 methods available to all types implementing IEnumerable<T>, not including any overloading—and this happens without needing to explicitly implement any method except the GetEnumerator() method. The additional functionality is provided using C# 3.0’s extension methods and it all resides in the class System.Linq.Enumerable. Therefore, including the using declarative for System.Linq is all it takes to make these methods available. Ducking typing來源於一句英語俗語: if it walks like a duck and quacks like a duck, it must be a duck. (如果它走起路來像一只鴨子,叫起來像一只鴨子,那麼肯定是一只鴨子。)對於C#這樣的動態語言,它的意思是說,可將一個對象傳給正在期待一個特定類型的方法,即使它並非繼承自該類型。它唯一要做的就是支持該方法想要使用的,由它原來期待的類型定義的方法和屬性。 P.423(原文P.561) 如果集合直接提供了一個Count屬性,就應該首選它,而不要用LINQ的Count()方法(這是一個許多人都沒有意識到的差異)。幸好,ICollection<T>包含了Count屬性,所以如果一個集合支持ICollection<T>,那麼在它上面調用Count()方法,會對集合進行轉型,並直接調用Count。然而,如果不支持ICollection<T>,Enumerable.Count()就會枚舉集合中的所有項,而不是調用內建的Count機制。如果計數的目的只是為了看這個計數是否大於0(if(patents.Count() > 0){...}),那麼首選的做法是使用Any()運算符(if(patents.Any()){...})。Any()只嘗試遍歷集合中的一個項,如果成功就返回true。它不會遍歷整個序列。 Whenever a Count property is directly available on the collection, it is preferable to use that rather than LINQ’s Count() method (a subtle difference). Fortunately, ICollection<T> includes the Count property, so code that calls the Count() method on a collection that supports ICollection<T> will cast the collection and call Count directly. However, if ICollection<T> is not supported, Enumerable.Count() will proceed to enumerate all the items in the collection rather than call the built-in Count mechanism. If the purpose of checking the count is only to see whether it is greater than zero (if(patents.Count() > 0){...}), a preferable approach would be to use the Any() operator (if(patents.Any()){...}). Any() attempts to iterate over only one of the items in the collection to return a true result, rather than the entire sequence. P.425(原文P.563) 在聲明時,它們是不執行的,除非調用Lambda表達式,造成其中的代碼開始執行,否則Lambda表達式不會執行。 At the time of declaration, lambda expressions do not execute. It isn’t until the lambda expressions are invoked that the code within them begins to execute. P.481(原文P.632) C#編譯器為索引運算符創建的CIL代碼是一個名為Item的持殊屬性,它要獲取一個參數。接受參數的屬性不可以在C#中顯式地創建,所以Item屬性在這個方面是相當特殊的。這是由於使用了Item這個標誌的其它任何成員,即使它有一個完全不同的簽名,都會和編譯器創建的成員產生,所以是不允許其它成員使用Item標誌符的。 The resultant CIL code the C# compiler creates from an index operator is a special property called Item that takes an argument. Properties that accept arguments cannot be created explicitly in C#, so the Item property is unique in this aspect. This is because any additional member with the identifier Item, even if it has an entirely different signature, will conflict with the compiler-created member, and will therefore not be allowed. P.491(原文P.645) 上述代碼的問題在於,SubItems是Pair<T>類型的一個屬性,而Pair<T>是一個struct。因此當屬性返回值時,會生成SubItems的一個副本,這個副本在語句執行完畢之後就會丟失。所以對一個馬上就要丟失的副本上的First進行賦值,顯然會引起誤解。幸好,C#編譯器禁止這樣做。 An interesting side effect of defining Pair<T> as a struct rather than a class is that SubItems.First and SubItems.Second cannot be assigned directly. The following will produce a compile error indicating that Sub-Items cannot be modified, “because it is not a variable”: jfkFamilyTree.SubItems.First = new BinaryTree<string>("Joseph Patrick Kennedy"); The issue is that SubItems is a property of type Pair<T>, a struct. Therefore, when the property returns the value, a copy of _SubItems is made, and assigning First on a copy that is promptly lost at the end of the statement would be misleading. Fortunately, the C# compiler prevents this. To overcome the issue, don’t assign it (see the approach in Listing 16.17), use class rather than struct for Pair<T>, don’t create a SubItems property and instead use a field, or provide properties in BinaryTree<T> that give direct access to _SubItems members. P.497(原文P.653) 注意,GetMethods()調用不能返回擴展方法,它們只能作為實現類型的靜態成員使用。 Note that the GetMethods() call does not return extension methods. They are available only as static members on the implementing type. P.502(原文P.659) 換言之,只要設置了恰當的代碼訪問安全性(code access security, CAS, 參見第21章)權限,反射就可以繞過可訪問性規則。 In other words, reflection is able to circumvent accessibility rules as long as appropriate code access security (CAS; see chapter 21) permissions are established. P.504(原文P.) 請大家注意parameters和arguments的區別,前者可以理解為形參,是一個占位符,後者可以理解為實參,是實際傳遞的參數。 P.507(原文P.) 有兩個辦法可以在同一個構造上合併多個特性,既可以在同一對方括號中,以逗號分隔多個特性,也可以將每個特性都放在自已的一對方括號中。 There are two ways to combine attributes on the same construct. You can either separate the attributes with commas within the same square brackets, or place each attribute within its own square brackets, as shown in Listing 17.8. Listing 17.8: Decorating a Property with Multiple Attributes
[CommandLineSwitchRequired] [CommandLineSwitchAlias("FileName")] public string Out { get { return _Out; } set { _Out = value; } } [CommandLineSwitchRequired, CommandLineSwitchAlias("FileName")] public string Out { get { return _Out; } set { _Out = value; } }