amazon的參考
C#與.NET4高級程序設計(第5版)/圖靈程序設計叢書
作者:(美)特羅爾森|譯者:朱曄//肖逵//姚琪琳//張大磊//王少葵等
出版社:人民郵電
ISBN:9787115250322
出版日期:2011/04/01
裝幀:
頁數:1197
人民幣:RMB 149 元
P.45(原文P.63)
找到了你希望激活的代碼塊之後,就按兩次Tab鍵。它會自動完成整個代碼塊並且留下一組占位符,然後你就可以填充它來完成代碼塊。如果按Tab鍵,就可以切換每一個問位符且填充內容(按Esc鍵來退出代碼段編輯模式)。
Once you find the snippet you want to activate, press the Tab key twice. This will autocomplete the entire snippet and leave a set of placeholders that you can fill in to complete the snippet. If you press the Tab key, you can cycle between each placeholder and fill in the gaps (press the Esc key to exit the code snippet edit mode).
P.54(原文P.75)
如果我們不提供一個明確的訪問修飾符,Main()也可以被定義為公有的。Visual Studio 2010會把程序的Main()方法自動定義為隱式私有的,以確保其他應用程序不能直接調用另一個應用程序的入口點。
The Main() method may also be defined as public as opposed to private, which is assumed if you do not supply a specific access modifier. Visual Studio 2010 automatically defines a program’s Main() method as implicitly private. Doing so ensures other applications cannot directly invoke the entry point of another.
P.54(原文P.76)
根據慣例,返回值0表示程序正常結束,而其它值(如-1)則表示有錯誤發生(要知道,值0是自動返回的,即使Main()方法的原型結構返回void)。
By convention, returning the value 0 indicates the program has terminated successfully, while another value (such as -1) represents an error condition (be aware that the value 0 is automatically returned, even if you construct a Main() method prototyped to return void).
P.55(原文P.77)
再說一次,絕大多數(但不是全部)C#應用程序會使用void作為Main()返回值。你應該記得吧,這其實是在隱式返回0作為錯誤代碼。
Again, a vast majority (if not all) of your C# applications will use void as the return value from Main(), which, as you recall, implicitly returns the error code of zero. To this end, the Main() methods used in this text (beyond the current example) will indeed return void (and the remaining projects will certainly not need to make use of batch files to capture return codes).
P.60(原文P.83)
// Change echo color, just for fun.
ConsoleColor prevColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
P.61(原文P.84)
D or d Used to format decimal numbers. This flag may also specify the minimum number of digits used to pad the value.
X or x Used for hexadecimal formatting. If you use an uppercase X, your hex format will also contain uppercase characters.
P.61-62(原文P.85)
但如果你想進一步深入學習.Net字符串格式化,可以查閱.NET Framework 4.0 SDK文檔中的formatting types(格式化類型)主題。
You’ll see additional formatting examples where required throughout this text; however, if you are interested in digging into .NET string formatting further, look up formatting types within the .NET Framework 4.0 SDK documentation.
P.63(原文P.87)
預設情況下,浮點數被當作double類型。
Note By default, a floating point number is treated as a double.
P.65(原文P.90)
static void NewingDataTypes()
{
Console.WriteLine("=> Using new to create variables:");
bool b = new bool(); // Set to false.
int i = new int(); // Set to 0.
double d = new double(); // Set to 0.
DateTime dt = new DateTime(); // Set to 1/1/0001 12:00:00 AM
Console.WriteLine("{0}, {1}, {2}, {3}", b, i, d, dt);
Console.WriteLine();
}
P.73(原文P.102)
還可以通過重複"標記向一個字面量字符串插入一個雙引號。
Using verbatim strings, you can also directly insert a double quote into a literal string by doubling the " token.
Console.WriteLine(@"Cerebus said ""Darrr! Pret-ty sun-sets""");
P.80(原文P.110)
要使用Visual Studio 2010啟用這個標誌,可以打開項目屬性頁,然後單擊Build標簽中的Advanced按鈕。在結束對話框中選擇Check for arithmetic overflow/underflow(檢測運算上溢/下溢)複選框(如圖3-16所示)。
To enable this flag using Visual Studio 2010, open your project’s property page and click the Advanced button on the Build tab. From the resulting dialog box, select the Check for arithmetic overflow/underflow check box (see Figure 3-16).
P.82(原文P.113)
要知道的是,你可以用隱式類型表示任何類型,包括數組、泛型類型,以及自定義的類型,你還將在本書中看到隱式類型的其它示範。
■ Note Be aware that you can use this implicit typing for any type including arrays, generic types (see Chapter 10), and your own custom types. You’ll see other examples of implicit typing over the course of this book.
P.82-83(原文P.114-115)
隱式類型變量的限制
Restrictions on Implicitly Typed Variables
對於var關鍵字的使用,自然有許多的限制。首先,隱式類型只能用於方法或屬性範圍內的本地變量。用var關鍵字定義返回值、參數或自定義類型的字段數據,都是不合法的。例如,下面的類定義將導致多個編譯時錯誤:
There are, of course, various restrictions regarding the use of the var keyword. First and foremost, implicit typing applies only to local variables in a method or property scope. It is illegal to use the var keyword to define return values, parameters, or field data of a custom type. For example, the following class definition will result in various compile-time errors:
class ThisWillNeverCompile { // Error! var cannot be used as field data! private var myInt = 10; // Error! var cannot be used as a return value // or parameter type! public var MyMethod(var x, var y) { } }
同樣,用var關鍵字聲明的本地變量必須在聲明時分配初始值,並且這個初始值不能為null。後一個限制是有意義的,因為編譯器僅僅根據null無法推斷該變量在內存中實際指向的數據類型。
Also, local variables declared with the var keyword must be assigned an initial value at the exact time of declaration and cannot be assigned the initial value of null. This last restriction should make sense, given that the compiler cannot infer what sort of type in memory the variable would be pointing to based only on null.
// Error! Must assign a value!
var myData;
// Error! Must assign value at exact time of declaration!
var myInt;
myInt = 0;
// Error! Can't assign null as initial value!
var myObj = null;
然而,當為隱式類型本地變量分配了初始值並進進行推斷之後(必須是引用類型),就可以對其分配null了。
It is permissible, however, to assign an inferred local variable to null after its initial assignment (provided it is a reference type).
// OK, is SportsCar is a reference type!
var myCar = new SportsCar();
myCar = null;
此外,可以將隱式類型本地變量的值分配給其它變量(不管它是否為隱式類型)。
Furthermore, it is permissible to assign the value of an implicitly typed local variable to the value of other variables, implicitly typed or not.
// Also OK!
var myInt = 0;
var anotherInt = myInt;
string myString = "Wake up!";
var myData = myString;
同樣,你還可以向調用方返回一個隱式類型本地變量,只要方法的返回類型與var定義的數據點的實際類型是相同的。
Also, it is permissible to return an implicitly typed local variable to the caller, provided the method return type is the same underlying type as the var-defined data point.
static int GetAnInt() { var retVal = 9; return retVal; }
最後,用C#的 ? 標記定義可空的隱式類型本地變量是不合法的(可空數據類型的詳細內容見第4章)
Last but not least, be aware that it is illegal to define a nullable implicitly typed local variable using the C# ? token (see Chapter 4 for details on nullable data types).
// Nope, can't define nullable implicit variables,
// as implicit variables can never be initially assigned
// null to begin with!
var? nope = new SportsCar();
var? stillNo = 12;
var? noWay = null;
P.84(原文P.116)
However, as you will see beginning in Chapter 13, the LINQ technology set makes use of query expressions that can yield dynamically created result sets based on the format of the query itself. In these cases, implicit typing is extremely helpful, as we do not need to explicitly define the type that a query may return, which in some cases would be literally impossible to do. Without getting hung up on the following LINQ example code, see if you can figure out the underlying data type of subset:
static void QueryOverInts() { int[] numbers = { 10, 20, 30, 40, 1, 2, 3, 8 }; // LINQ query! var subset = from i in numbers where i < 10 select i; Console.Write("Values in subset: "); foreach (var i in subset) { Console.Write("{0} ", i); } Console.WriteLine(); // Hmm...what type is subset? Console.WriteLine("subset is a: {0}", subset.GetType().Name); Console.WriteLine("subset is defined in: {0}", subset.GetType().Namespace); }
感興趣的讀者可以執行上面的代碼來查看subset的實際數據類型(你會發現它不是整數數組)。
事實上,可以說只有在定義LINQ查詢的返回數據時才應該使用var關鍵字。
Bear: 翻譯有問題,應該是整數數組而非整型數組
I’ll let the interested reader verify the underlying data type of subset by executing the preceding code (and it is not an array of integers!). In any case, it should be clear that implicit typing does have its place within the LINQ technology set. In fact, it could be argued that the only time one would make use of the var keyword is when defining data returned from a LINQ query. Remember, if you know you need an int, just declare an int! Overuse of implicit typing (via the var keyword) is considered bad style in production code.
P.91(原文P.126)
方法只能有一個params修飾符,而且必須是方法的最後一個參數。
params
This parameter modifier allows you to send in a variable number of arguments as a single logical parameter. A method can have only a single params modifier, and it must be the final parameter of the method. In reality, you may not need to use the params modifier all too often, however be aware that numerous methods within the base class libraries do make use of this C# language feature.
P.95(原文P.132)
非常重要的一點是,分配給可選參數的值必須在編譯時確定,而不能在運行時確定(否則將得到編譯時錯誤)。
One very important thing to be aware of, is that the value assigned to an optional parameter must be known at compile time, and cannot be resolved at runtime (if you attempt to do so, you’ll receive compile time errors!). To illustrate, assume you wish to update EnterLogData() with the following extra optional parameter:
// Error! The default value for an optional arg must be known // at compile time! static void EnterLogData(string message, string owner = "Programmer", DateTime timeStamp = DateTime.Now) { Console.Beep(); Console.WriteLine("Error: {0}", message); Console.WriteLine("Owner of Error: {0}", owner); Console.WriteLine("Time of Error: {0}", timeStamp); }
這將無法通過編譯,因為DateTime類的Now屬性是在運行時而不是在編譯時處理的。
This will not compile, as the value of the Now property of the DateTime class is resolved at runtime, not compile time.
P.100(原文P.138)
static void ArrayInitialization() { Console.WriteLine("=> Array Initialization."); // Array initialization syntax using the new keyword. string[] stringArray = new string[] { "one", "two", "three" }; Console.WriteLine("stringArray has {0} elements", stringArray.Length); // Array initialization syntax without using the new keyword. bool[] boolArray = { false, false, true }; Console.WriteLine("boolArray has {0} elements", boolArray.Length); // Array initialization with new keyword and size. int[] intArray = new int[4] { 20, 22, 23, 0 }; Console.WriteLine("intArray has {0} elements", intArray.Length); Console.WriteLine(); }
P.100(原文P.139)
static void DeclareImplicitArrays() { Console.WriteLine("=> Implicit Array Initialization."); // a is really int[]. var a = new[] { 1, 10, 100, 1000 }; Console.WriteLine("a is a: {0}", a.ToString()); // b is really double[]. var b = new[] { 1, 1.5, 2, 2.5 }; Console.WriteLine("b is a: {0}", b.ToString()); // c is really string[]. var c = new[] { "hello", null, "world" }; Console.WriteLine("c is a: {0}", c.ToString()); Console.WriteLine(); }
P.105(原文P.145)
列舉不一定是連續的,也不需要有唯一值。
Enumerations do not necessarily need to follow a sequential ordering, and need not have unique values.
P.106(原文P.145)
希望盡可能節省記憶體,那麼改變列舉的實際類型可能會很有用。
Changing the underlying type of an enumeration can be helpful if you are building a .NET application that will be deployed to a low-memory device (such as a .NET-enabled cell phone or PDA) and need to conserve memory wherever possible.
P.107(原文P.147)
一個很有用的方法就是靜態的Enum.GetUnderlyingType()方法。顧名恩義,它返回用於保存列舉類型值的數據類型(對於當前的EmpType聲明,就是System.Byte)
The interesting thing about .NET enumerations is that they gain functionality from the System.Enum class type. This class defines a number of methods that allow you to interrogate and transform a given enumeration. One helpful method is the static Enum.GetUnderlyingType(), which as the name implies returns the data type used to store the values of the enumerated type (System.Byte in the case of the current EmpType declaration).
static void Main(string[] args) { Console.WriteLine("**** Fun with Enums *****"); // Make a contractor type. EmpType emp = EmpType.Contractor; AskForBonus(emp); // Print storage for the enum. Console.WriteLine("EmpType uses a {0} for storage", Enum.GetUnderlyingType(emp.GetType())); Console.ReadLine(); }
P.112(原文P.154)
由於值類型使用基於值的語法,結構(也包括所有數值數據類型int, float等,以及任何列舉或自定義結構)的生命周期是可以預測的。當結構變量離開定義域的範圍時,它就會立即從記憶體中移除。
Given the fact that value types are using value-based semantics, the lifetime of a structure (which includes all numerical data types [int, float], as well as any enum or custom structure) is very predictable. When a structure variable falls out of the defining scope, it is removed from memory immediately:
// Local structures are popped off
// the stack when a method returns.
static void LocalValueTypes()
{
// Recall! "int" is really a System.Int32 structure.
int i = 0;
// Recall! Point is a structure type.
Point p = new Point();
} // "i" and "p" popped off the stack here!
P.113(原文P.155)
當把一個值類型賦給另外一個時,就是對字段成員逐一進行複制。
When you assign one value type to another, a member-by-member copy of the field data is achieved.
P.115(原文P.157-158)
static void ValueTypeContainingRefType() { // Create the first Rectangle. Console.WriteLine("-> Creating r1"); Rectangle r1 = new Rectangle("First Rect", 10, 10, 50, 50); // Now assign a new Rectangle to r1. Console.WriteLine("-> Assigning r2 to r1"); Rectangle r2 = r1; // Change some values of r2. Console.WriteLine("-> Changing values of r2"); r2.rectInfo.infoString = "This is new info!"; r2.rectBottom = 4444; // Print values of both rectangles. r1.Display(); r2.Display(); }
The output can been seen in the following:
-> Creating r1
-> Assigning r2 to r1
-> Changing values of r2
String = This is new info!, Top = 10, Bottom = 50, Left = 10, Right = 50
String = This is new info!, Top = 10, Bottom = 4444, Left = 10, Right = 50
預設情況下,當值類型包含其它引用類型時,賦值將生成一個引用的副本。這樣就有兩個獨立的結構,每一個都包含指向記憶體中同一個對象的引用(也就是"淺複制")。當想執行一個"深複制",即將內部引用的狀態完全複制到一個新對象中時,需要實現ICloneable接口。
As you can see, when you change the value of the informational string using the r2 reference, the r1 reference displays the same value. By default, when a value type contains other reference types, assignment results in a copy of the references. In this way, you have two independent structures, each of which contains a reference pointing to the same object in memory (i.e., a shallow copy). When you want to perform a deep copy, where the state of internal references is fully copied into a new object, one approach is to implement the ICloneable interface (as you will do in Chapter 9).
Bear: 因為是struct,所以可知當然是copy
P.116-117(原文P.158-160)
Passing Reference Types by Value
Reference types or value types can obviously be passed as parameters to methods. However, passing a reference type (e.g., a class) by reference is quite different from passing it by value. To understand the distinction, assume you have a simple Person class defined in a new Console Application project named RefTypeValTypeParams, defined as follows:
class Person { public string personName; public int personAge; // Constructors. public Person(string name, int age) { personName = name; personAge = age; } public Person() { } public void Display() { Console.WriteLine("Name: {0}, Age: {1}", personName, personAge); } }
Now, what if you create a method that allows the caller to send in the Person object by value (note the lack of parameter modifiers, such as out or ref):
static void SendAPersonByValue(Person p)
{
// Change the age of "p"?
p.personAge = 99;
// Will the caller see this reassignment?
p = new Person("Nikki", 99);
}
Notice how the SendAPersonByValue() method attempts to reassign the incoming Person reference to a new Person object as well as change some state data. Now let’s test this method using the following Main() method:
static void Main(string[] args) { // Passing ref-types by value. Console.WriteLine("***** Passing Person object by value *****"); Person fred = new Person("Fred", 12); Console.WriteLine("\nBefore by value call, Person is:"); fred.Display(); SendAPersonByValue(fred); Console.WriteLine("\nAfter by value call, Person is:"); fred.Display(); Console.ReadLine(); }
The following is the output of this call.
***** Passing Person object by value *****
Before by value call, Person is:
Name: Fred, Age: 12
After by value call, Person is:
Name: Fred, Age: 99
可以看出,personAge的值被修改了。這個行為看起來似乎違反了"按值"傳遞的語義。如果能夠改變傳入的Person的狀態,那麼複制的是什麼?答案是:複制了指向調用者對象的引用。由於SendAPersonByValue()方法與調用者指向同一個對象,所以改變對象的狀態數據是可能的。但是無法把引用重新賦值給一個新的對象。
譯者注:就像上面的 p = new Person("Nikki", 99); ,代碼並沒有起做用,有一點像C++中的常量指針。
As you can see, the value of personAge has been modified. This behavior seems to fly in the face of what it means to pass a parameter “by value.” Given that you were able to change the state of the incoming Person, what was copied? The answer: a copy of the reference to the caller’s object. Therefore, as the SendAPersonByValue() method is pointing to the same object as the caller, it is possible to alter the object’s state data. What is not possible is to reassign what the reference is pointing to.
P.118(原文P.161-162)
Table 4-3. Value Types and Reference Types Side by Side
Intriguing Question Value Type Reference Type
□Value Type
■Reference Type
Where is this type allocated?
□Allocated on the stack.
■Allocated on the managed heap.
How is a variable represented?
□Value type variables are local copies.
■Reference type variables are pointing to the memory occupied by the allocated instance.
What is the base type?
□Must derive from System.ValueType.
■Can derive from any other type (except System.ValueType), as long as that type is not “sealed” (more details on this in Chapter 6).
Can this type function as a base to other types?
□No. Value types are always sealed and cannot be inherited from.
■Yes. If the type is not sealed, it may function as a base to other types.
What is the default parameter passing behavior?
□Variables are passed by value (i.e., a copy of the variable is passed into the called function).
■For value types, the object is copied-by-value. For reference types, the reference is copied-by-value.
Can this type override System.Object.Finalize()?
□No. Value types are never placed onto the heap and therefore do not need to be finalized.
■Yes, indirectly (more details on this in Chapter 8).
Can I define constructors for this type?
□Yes, but the default constructor is reserved (i.e., your custom constructors must all have arguments).
■But of course!
When do variables of this type die?
□When they fall out of the defining scope
■When the object is garbage collected.
P.118(原文P.162)
盡管它們存在差異,但是值類型和引用類型都有實現接口的能力,並且可以支持任意數量的字段、方法、重載操作符、常量、屬性和事件。
Despite their differences, value types and reference types both have the ability to implement interfaces and may support any number of fields, methods, overloaded operators, constants, properties, and events.
P.119(原文P.163)
如果試圖創建一個可空引用類型(包括字符串),就遇到編譯時錯誤。
To define a nullable variable type, the question mark symbol (?) is suffixed to the underlying data type. Do note that this syntax is only legal when applied to value types. If you attempt to create a nullable reference type (including strings), you are issued a compile-time error. Like a nonnullable variable, local nullable variables must be assigned an initial value before you can use them:
static void LocalNullableVariables() { // Define some local nullable types. int? nullableInt = 10; double? nullableDouble = 3.14; bool? nullableBool = null; char? nullableChar = 'a'; int?[] arrayOfNullableInts = new int?[10]; // Error! Strings are reference types! // string? s = "oops"; }
P.120(原文P.163-164)
class DatabaseReader { // Nullable data field. public int? numericValue = null; public bool? boolValue = true; // Note the nullable return type. public int? GetIntFromDatabase() { return numericValue; } // Note the nullable return type. public bool? GetBoolFromDatabase() { return boolValue; } }
P.130(原文P.177)
一個更簡潔的方案就是,讓一個接受最多參數的構造函數做"主構造函數",並且實現必須的驗證邏輯。
A cleaner approach is to designate the constructor that takes the greatest number of arguments as the “master constructor” and have its implementation perform the required validation logic.
P.131(原文P.177-178)
在串聯構造函數時,請注意this如何在構造函數本身的作用域之外"躲開"構造函數的聲明(通過冒號操作符):
Here is the final iteration of the Motorcycle class (with one additional constructor for the sake of illustration). When chaining constructors, note how the this keyword is “dangling” off the constructor’s declaration (via a colon operator) outside the scope of the constructor itself:
class Motorcycle { public int driverIntensity; public string driverName; // Constructor chaining. public Motorcycle() { } public Motorcycle(int intensity) : this(intensity, "") { } public Motorcycle(string name) : this(0, name) { } // This is the 'master' constructor that does all the real work. public Motorcycle(int intensity, string name) { if (intensity > 10) { intensity = 10; } driverIntensity = intensity; driverName = name; } .... }
P.136(原文P.184-185)
// A simple savings account class. class SavingsAccount { public double currBalance; // A static point of data. public static double currInterestRate = 0.04; public SavingsAccount(double balance) { currBalance = balance; } // Static members to get/set interest rate. public static void SetInterestRate(double newRate) { currInterestRate = newRate; } public static double GetInterestRate() { return currInterestRate; } }
Now, observe the following usage:
static void Main(string[] args) { Console.WriteLine("***** Fun with Static Data *****\n"); SavingsAccount s1 = new SavingsAccount(50); SavingsAccount s2 = new SavingsAccount(100); // Print the current interest rate. Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate()); // Make new object, this does NOT 'reset' the interest rate. SavingsAccount s3 = new SavingsAccount(10000.75); Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate()); Console.ReadLine(); }
The output of the previous Main() is seen here:
***** Fun with Static Data *****
In static ctor!
Interest Rate is: 0.04
Interest Rate is: 0.04
P.138(原文P.186-187)
Simply put, a static constructor is a special constructor that is an ideal place to initialize the values of static data when the value is not known at compile time (e.g., you need to read in the value from an external file or generate a random number). Here are a few points of interest regarding static constructors:
一個類只可以定義一個靜態構造函數。換句話說,就是靜態函數不能被重載。
1. A given class may define only a single static constructor. In other words, the static constructor cannot be overloaded.
靜態構造函數不允許訪問修飾符並且不能接受任何參數。
2. A static constructor does not take an access modifier and cannot take any parameters.
無論創建了多少類型的對象,靜態構造函數只執行一次。
3. A static constructor executes exactly one time, regardless of how many objects of the type are created.
運行庫創建類實例或調用者首次訪問靜態成員之前,運行庫會調用靜態構造函數。
4. The runtime invokes the static constructor when it creates an instance of the class or before accessing the first static member invoked by the caller.
靜態構造函數的執行先於任何實例級別的構造函數。
5. The static constructor executes before any instance-level constructors.
P.149(原文P.200)
使用傳統的訪問方法和修改方法,需要如下代碼:
Now assume you have created an Employee object named joe. On his birthday, you wish to increment the age by one. Using traditional accessor and mutator methods, you would need to write code such as the following:
Employee joe = new Employee();
joe.SetAge(joe.GetAge() + 1);
然而,如果使用Age屬性來封裝empAge,就可以將代碼簡化為:
However, if you encapsulate empAge using a property named Age, you are able to simply write
Employee joe = new Employee();
joe.Age++;
P.151(原文P.203)
如果還想在同一個類中定義兩個名為get_SocialSecurityNumber() 和 set_SocialSecurityNumber() 的方法,將遇到編譯時錯誤:
If you were to also define two methods named get_SocialSecurityNumber() and set_SocialSecurityNumber() in the same class, you would be issued compile-time errors:
// Remember, a property really maps to a get_/set_ pair! class Employee { .... public string get_SocialSecurityNumber() { return empSSN; } public void set_SocialSecurityNumber(string ssn) { empSSN = ssn; } }
Bear: 會和CIL內部的屬性命名相衝
P.154(原文P.207-208)
但與傳統的C#屬性不同的是,不允許構建只讀或只寫的自動屬性,也許你會認為像下面那樣僅僅忽略屬性聲明中的get; 或 set;就可以了:
Unlike traditional C# properties, however, it is not possible to build read-only or write-only automatic properties. While you might think you can just omit the get; or set; within your property declaration as follows:
// Read-only property? Error! public int MyReadOnlyProp { get; } // Write only property? Error! public int MyWriteOnlyProp { set; }
而這將導致編譯器錯誤。自動屬性在定義時必須同時支持讀寫功能。但是,定義更嚴格的get或set是可以的。
this will result in a compiler error. When you are defining an automatic property, it must support both read and write functionality. However, it is possible to implement an automatic property which defines a more restrictive get or set:
// Public get, protected set! // You could also set a get / set to private. public int SomeOtherProperty { get; protected set; }
P.158(原文P.212)
Do be aware that when you are constructing a type using the new initialization syntax, you are able to invoke any constructor defined by the class. Our Point type currently defines a two-argument constructor to set the (x, y) position. Therefore, the following Point declaration results in an X value of 100 and a Y value of 100, regardless of the fact that our constructor arguments specified the values 10 and 16:
// Calling a custom constructor.
Point pt = new Point(10, 16) { X = 100, Y = 100 };
Bear: 若在constructor傳入參數設定屬性,又在後面初始化語法再設定該屬性,想當然爾,一定參數設定的值還是會被初始化語法蓋掉,即最後為100, 100。因此,這樣做是無意義的,且易造成誤會。
P.159(原文P.213)
// Calling a more interesting custom constructor with init syntax.
Point goldPoint = new Point(PointColor.Gold){ X = 90, Y = 20 };
Console.WriteLine("Value of Point is: {0}", goldPoint.DisplayStats());
Bear: 在constructor傳入參數設定的屬性和後面初始化語法對象不同。因此,這樣做是有意義的。
P.175(原文P.233)
通過嵌套類型可以完全控制內部類型的訪問級別,也就是可以聲明為私有(回憶一下,非嵌套類不能使用private關鍵字來聲明)。
Nested types allow you to gain complete control over the access level of the inner type, as they may be declared privately (recall that non-nested classes cannot be declared using the private keyword).
P.179(原文P.238-239)
如果在類的類型中輸入"override"單詞(然後按空白鍵),智能感知會自動顯示定義在父類中的所有可重寫成員的列表。
Visual Studio 2010 has a very helpful feature that you can make use of when overriding a virtual member. If you type the word “override” within the scope of a class type (then hit the spacebar), IntelliSense will automatically display a list of all the overridable members defined in your parent classes, as you see in Figure 6-6.
When you select a member and hit the Enter key, the IDE responds by automatically filling in the method stub on your behalf. Note that you also receive a code statement that calls your parent’s version of the virtual member (you are free to delete this line if it is not required). For example, if you used this technique when overriding the DisplayStats() method, you might find the following autogenerated code:
public override void DisplayStats() { base.DisplayStats(); }
P.180(原文P.239)
On a related note, sometimes you may not wish to seal an entire class, but simply want to prevent derived types from overriding particular virtual methods. For example, assume you do not want parttime salespeople to obtain customized bonuses. To prevent the PTSalesPerson class from overriding the virtual GiveBonus() method, you could effectively seal this method in the SalesPerson class as follows:
// SalesPerson has sealed the GiveBonus() method! class SalesPerson : Employee { public override sealed void GiveBonus(float amount) { } }
P.184(原文P.244)
抽象方法只可以定義在抽象類中,如果不是這樣的話,就會收到編譯器錯誤。
Abstract methods can only be defined in abstract classes. If you attempt to do otherwise, you will be issued a compiler error.
P.186(原文P.247)
我們仍然可以使用顯式強制轉換來觸發陰影成員在基類中的實現。
Finally, be aware that it is still possible to trigger the base class implementation of a shadowed member using an explicit cast, as described in the next section. For example, the following code shows:
static void Main(string[] args)
{
// This calls the Draw() method of the ThreeDCircle.
ThreeDCircle o = new ThreeDCircle();
o.Draw();
// This calls the Draw() method of the parent!
((Circle)o).Draw();
Console.ReadLine();
}
P.188(原文P.249)
The previous code compiles given the implicit cast from the base class type (Employee) to the derived type. However, what if you also wanted to fire Frank Zappa (currently stored in a general System.Object reference)? If you pass the frank object directly into this method, you will find a compiler error as follows:
// Error!
object frank = new Manager("Frank Zappa", 9, 3000, 40000, "111-11-1111", 5);
GivePromotion(frank);
P.203(原文P.268)
static void Main(string[] args) { .... // TargetSite actually returns a MethodBase object. catch (Exception e) { Console.WriteLine("\n*** Error! ***"); Console.WriteLine("Member name: {0}", e.TargetSite); Console.WriteLine("Class defining member: {0}", e.TargetSite.DeclaringType); Console.WriteLine("Member type: {0}", e.TargetSite.MemberType); Console.WriteLine("Message: {0}", e.Message); Console.WriteLine("Source: {0}", e.Source); } Console.WriteLine("\n***** Out of exception logic *****"); Console.ReadLine(); }
在這裡,我們使用MethodBase.DeclaringType屬性值來指定引發異常的類的全稱(這個例子中為SimpleException.Car),使用MethodBase對象的MemberType屬性來確定引發異常的成員類型(比如屬性與方法)。
This time, you make use of the MethodBase.DeclaringType property to determine the fully qualified name of the class that threw the error (SimpleException.Car in this case) as well as the MemberType property of the MethodBase object to identify the type of member (such as a property vs. a method) where this exception originated. In this case, the catch logic would display the following:
*** Error! ***
Member name: Void Accelerate(Int32)
Class defining member: SimpleException.Car
Member type: Method
Message: Zippy has overheated!
Source: SimpleException
P.207(原文P.273)
事實上最佳實踐表明,自定義異常應當派生自System.ApplicationException類型。
You could do this, but you could instead derive from the System.ApplicationException class:
public class ApplicationException : Exception { // Various constructors. }
按照約定,所有的異常類均應以"Exception"後綴結束,這是.NET的最佳實踐。
(by convention, all exception classes end with the “Exception” suffix; in fact, this is a .NET best practice)
P.209(原文P.275-276)
public class CarIsDeadException : ApplicationException { public DateTime ErrorTimeStamp { get; set; } public string CauseOfError { get; set; } public CarIsDeadException() { } // Feed message to parent constructor. public CarIsDeadException(string message, string cause, DateTime time) : base(message) { CauseOfError = cause; ErrorTimeStamp = time; } }
請注意,這次我們並沒有定義一個字符串變量來呈現信息,也沒有重寫Message屬性,僅是將參數傳遞到基類構造函數而已。通過這樣的設計,一個自定異常類就沒有任何基類重寫,和一個派生自System.ApplicationException的特定命名的類沒有任何差別了。
Notice that this time you have not defined a string variable to represent the message, and have not overridden the Message property. Rather, you are simply passing the parameter to your base class constructor. With this design, a custom exception class is little more than a uniquely named class deriving from System.ApplicationException, (with additional properties if appropriate), devoid of any base class overrides.
要知道大多數(甚至全部)的自定義異常類都遵循這個簡單的模式,請不要感到驚訝。很多情況下,自定義異常類的作用並不是提供繼承類之外附加的功能,而是提供明確標誌錯誤種類的強命名類型,因此客戶會為不同類型的異常提供不同的處理程序邏輯。
Don’t be surprised if most (if not all) of your custom exception classes follow this simple pattern. Many times, the role of a custom exception is not necessarily to provide additional functionality beyond what is inherited from the base classes, but to supply a strongly named type that clearly identifies the nature of the error, so the client can provide different handler-logic for different types of exceptions.
P.209~210(原文P.276)
如果讀者想構造一個真正意義上嚴謹的自定義異常類,需確保類遵守.NET異常處理的最佳實踐。具體來講,自定義異常需要:
If you wish to build a truly prim-and-proper custom exception class, you would want to make sure your type adheres to .NET best practices. Specifically, this requires that your custom exception
繼承自ApplicationException類。
1. Derives from ApplicationException
有[System.Serializable]特性標記。
2. Is marked with the [System.Serializable] attribute
定義一個預設的構造函數。
3. Defines a default constructor
定義一個設定繼承的Message屬性的構造函數。
4. Defines a constructor that sets the inherited Message property
定義一個處理"內部異常"的構造函數。
5. Defines a constructor to handle “inner exceptions”
定義一個處理類型予列化的構造函數。
6. Defines a constructor to handle the serialization of your type
P.229(原文P.303)
只是在你使用非托管資料時(例如原始的操作系統文件句柄、原始的非托管數據庫連接、非托管內存或其他非托管資源),才可能需要設計一個在用完後清理自身的類。
Now, despite what your developer instincts may tell you, the vast majority of your C# classes will not require any explicit cleanup logic or a custom finalizer. The reason is simple: if your classes are just making use of other managed objects, everything will eventually be garbage-collected. The only time you would need to design a class that can clean up after itself is when you are using unmanaged resources (such as raw OS file handles, raw unmanaged database connections, chunks of unmanaged memory, or other unmanaged resources). Under the .NET platform, unmanaged resources are obtained by directly calling into the API of the operating system using Platform Invocation Services (PInvoke) or as a result of some very elaborate COM interoperability scenarios. Given this, consider the next rule of garbage collection:
重寫Finalize()的唯一原因是,C#類通過PInvoke或複雜的COM互操作產任務使用了非托管資源(一般情況是通過System.Runtime.InteropServices.Marshal類型定義的各成員)。
The only reason to override Finalize() is if your C# class is making use of unmanaged resources via PInvoke or complex COM interoperability tasks (typically via various members defined by the System.Runtime.InteropServices.Marshal type).
P.233(原文P.307-308)
如果對象支持IDisposable,總是要對任何直接創建的對象調用Dispose()。應該認為,如果類設計者選擇支持Dispose()方法,這個類型就需要執行清除工作。
Always call Dispose() on any object you directly create if the object supports IDisposable. The assumption you should make is that if the class designer chose to support the Dispose() method, the type has some cleanup to perform.
對於之前的規則,還有一個補充:基礎類庫中的許多類型都實現了IDisposable接口,並且還提供了Dispose()方法的一個別名(有點混淆),這樣使得定義類型的釋放相關的方法聽上去更自然。例如,System.IO.FileStream類實現了IDisposable(因此也支持Dispose()方法),它還定義了用於相同目的的Close()方法。
There is one caveat to the previous rule. A number of types in the base class libraries that do implement the IDisposable interface provide a (somewhat confusing) alias to the Dispose() method, in an attempt to make the disposal-centric method sound more natural for the defining type. By way of an example, while the System.IO.FileStream class implements IDisposable (and therefore supports a Dispose() method), it also defines a Close() method that is used for the same purpose:
// Assume you have imported // the System.IO namespace... static void DisposeFileStream() { FileStream fs = new FileStream("myFile.txt", FileMode.OpenOrCreate); // Confusing, to say the least! // These method calls do the same thing! fs.Close(); fs.Dispose(); }
雖然關閉一個文件比釋放更自然,但是你可能也覺得重複地釋放相關的方法很容易混淆,由於提供別名的類型也不是很多,我們只要記住,如果類型實現了IDisposable,調用Dispose()總是正確的。
While it does feel more natural to “close” a file rather than “dispose” of one, this doubling up of disposal-centric methods can be confusing. For the few types that do provide an alias, just remember that if a type implements IDisposable, calling Dispose() is always a correct course of action.
Bear:重覆呼叫Dispose()並不會引發Exception喔,just do it。
P.234(原文P.309)
如果試圖"使用"一個沒有實現IDisposable的對象,將會收到編譯器錯誤。
If you attempt to “use” an object that does not implement IDisposable, you will receive a compiler error.
同樣,還要知道可以在using作用域中聲明相同類型的多個對象。和我們期望的那樣,編譯器會插入代碼來調用每一個聲明對象的Dispose()。
Also, be aware that it is possible to declare multiple objects of the same type within a using scope. As you would expect, the compiler will inject code to call Dispose() on each declared object:
static void Main(string[] args) { Console.WriteLine("***** Fun with Dispose *****\n"); // Use a comma-delimited list to declare multiple objects to dispose. using (MyResourceWrapper rw = new MyResourceWrapper(), rw2 = new MyResourceWrapper()) { // Use rw and rw2 objects. } }
P.246(原文P.326)
Remember that when you define interface members, you do not define an implementation scope for the member in question. Interfaces are pure protocol, and therefore never define an implementation (that is up to the supporting class or structure). Therefore, the following version of IPointy would result in various compiler errors:
// Ack! Errors abound! public interface IPointy { // Error! Interfaces cannot have fields! public int numbOfPoints; // Error! Interfaces do not have constructors! public IPointy() { numbOfPoints = 0;}; // Error! Interfaces don't provide an implementation of members! byte GetNumberOfPoints() { return numbOfPoints; } }
In any case, this initial IPointy interface defines a single method. However, .NET interface types are also able to define any number of property prototypes. For example, you could create the IPointy interface to use a read-only property rather than a traditional accessor method:
// The pointy behavior as a read-only property. public interface IPointy { // A read-write property in an interface would look like: // retType PropName { get; set; } // while a write-only property in an interface would be: // retType PropName { set; } byte Points { get; } }
接口類型還可以包含事件及索引器定義。
Interface types can also contain event (see Chapter 11) and indexer (see Chapter 12) definitions.
P.247(原文P.327)
由於結構總是從System.ValueType繼承,只需要在結構定義後直接列出每一個接口就行了。
When a class (or structure) chooses to extend its functionality by supporting interfaces, it does so using a comma-delimited list in the type definition. Be aware that the direct base class must be the first item listed after the colon operator. When your class type derives directly from System.Object, you are free to simply list the interface(s) supported by the class, as the C# compiler will extend your types from System.Object if you do not say otherwise. On a related note, given that structures always derive from System.ValueType (see Chapter 4 for full details), simply list each interface directly after the structure definition. Ponder the following examples:
P.255-256(原文P.337-338)
如果要實現具有相同成員的接口,可以使用顯式接口實現語法來解決這種命名沖突。
Clearly, the sort of code required to render the image to a window is quite different from the code needed to render the image to a networked printer or a region of memory. When you implement several interfaces that have identical members, you can resolve this sort of name clash using explicit interface implementation syntax. Consider the following update to the Octagon type:
class Octagon : IDrawToForm, IDrawToMemory, IDrawToPrinter { // Explicitly bind Draw() implementations // to a given interface. void IDrawToForm.Draw() { Console.WriteLine("Drawing to form..."); } void IDrawToMemory.Draw() { Console.WriteLine("Drawing to memory..."); } void IDrawToPrinter.Draw() { Console.WriteLine("Drawing to a printer..."); } }
As you can see, when explicitly implementing an interface member, the general pattern breaks down to returnType InterfaceName.MethodName(params)
在使用這種語法時,不能提供訪問修飾符,顯式實現的成員總是自動為私有的。
Note that when using this syntax, you do not supply an access modifier; explicitly implemented members are automatically private. For example, the following is illegal syntax:
// Error! No access modifer! public void IDrawToForm.Draw() { Console.WriteLine("Drawing to form..."); }
P.259(原文P.341-342)
Now, the million dollar question is, if you have a class supporting IShape, how many methods will it be required to implement? The answer: it depends. If you want to provide a simple implementation of the Draw() method, you need only provide three members, as shown in the following Rectangle type:
class Rectangle : IShape { public int GetNumberOfSides() { return 4; } public void Draw() { Console.WriteLine("Drawing..."); } public void Print() { Console.WriteLine("Prining..."); } }
If you’d rather have specific implementations for each Draw() method (which in this case would make the most sense), you can resolve the name clash using explicit interface implementation, as shown in the following Square type:
class Square : IShape { // Using explicit implementation to handle member name clash. void IPrintable.Draw() { // Draw to printer ... } void IDrawable.Draw() { // Draw to screen ... } public void Print() { // Print ... } public int GetNumberOfSides() { return 4; } }
P.260-261(原文P.343-344)
Ideally, it would be convenient to iterate over the Garage object’s subitems using the foreach construct, just like an array of data values:
// This seems reasonable... public class Program { static void Main(string[] args) { Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n"); Garage carLot = new Garage(); // Hand over each car in the collection? foreach (Car c in carLot) { Console.WriteLine("{0} is going {1} MPH", c.PetName, c.CurrentSpeed); } Console.ReadLine(); } }
讓人沮喪的是,編譯器通知我們Garage類沒有實現名為GetEnumerator()的方法。這個方法是由隱藏在System.Collections命名空間中的IEnumerable接口定義的。
Sadly, the compiler informs you that the Garage class does not implement a method named GetEnumerator(). This method is formalized by the IEnumerable interface, which is found lurking within the System.Collections namespace.
P.261-262(原文P.344-345)
If you wish to update the Garage type to support these interfaces, you could take the long road and implement each method manually. While you are certainly free to provide customized versions of GetEnumerator(), MoveNext(), Current, and Reset(), there is a simpler way. As the System.Array type (as well as many other collection classes) already implements IEnumerable and IEnumerator, you can simply delegate the request to the System.Array as follows:
using System.Collections;
...
public class Garage : IEnumerable { // System.Array already implements IEnumerator! private Car[] carArray = new Car[4]; public Garage() { carArray[0] = new Car("FeeFee", 200, 0); carArray[1] = new Car("Clunker", 90, 0); carArray[2] = new Car("Zippy", 30, 0); carArray[3] = new Car("Fred", 30, 0); } public IEnumerator GetEnumerator() { // Return the array object's IEnumerator. return carArray.GetEnumerator(); } }
P.282(原文P.371)
只有類、結構、接口和委托可以使用泛型,列舉類型不可以。
Only classes, structures, interfaces, and delegates can be written generically; enum types cannot.
尖括號中標記的正式名稱為型參數,但你也可以通谷地將其稱為佔位符。<T>符號讀作of T。因此IEnumerable<T>讀作IEnumerable of T,或者也可以稱其為類型T的列舉。
Formally speaking, you call these tokens type parameters; however, in more user friendly terms, you can simply call them placeholders. You can read the symbol <T> as of T. Thus, you can read IEnumerable<T> as IEnumerable of T; or, to say it another way, an enumeration of type T.
P.293(原文P.386-387)
static void Main(string[] args) { Console.WriteLine("***** Fun with Custom Generic Methods *****\n"); // Swap 2 ints. int a = 10, b = 90; Console.WriteLine("Before swap: {0}, {1}", a, b); Swap<int>(ref a, ref b); Console.WriteLine("After swap: {0}, {1}", a, b); Console.WriteLine(); // Swap 2 strings. string s1 = "Hello", s2 = "There"; Console.WriteLine("Before swap: {0} {1}!", s1, s2); Swap<string>(ref s1, ref s2); Console.WriteLine("After swap: {0} {1}!", s1, s2); Console.ReadLine(); }
The output looks like this:
***** Fun with Custom Generic Methods *****
Before swap: 10, 90
You sent the Swap() method a System.Int32
After swap: 90, 10
Before swap: Hello There!
You sent the Swap() method a System.String
After swap: There Hello!
該方法最大的優勢在於,只需要維護一個Swap<T>()版本,而且它能以類型安全的方式操作任意兩個給定參數類型的項。更重要的是,棧數據保留在棧上,堆數據保留在堆上。
The major benefit of this approach is that you have only one version of Swap<T>() to maintain, yet it can operate on any two items of a given type in a type safe manner. Better yet, stack-based items stay on the stack, while heap-based items stay on the heap!
P.293-294(原文P.387-388)
儘管編譯器可以根據聲明b1和b2的類型發現正確的類型參數,但你還是應該養成顯式指定類型參數的習慣。
Even though the compiler is able to discover the correct type parameter based on the data type used to declare b1 and b2, you should get in the habit of always specifying the type parameter explicitly:
Swap<bool>(ref b1, ref b2);
還可以讓你的同事很清楚地知道該方法是泛型的。另外,類型參數推斷只在泛型方法至少有一個參數的時候起作用。
This makes it clear to your fellow programmers that this method is indeed generic. Moreover, inference of type parameters only works if the generic method has at least one parameter. For example, assume you have the following generic method in your Program class:
static void DisplayBaseClass<T>() { // BaseType is a method used in reflection, // which will be examined in Chapter 15 Console.WriteLine("Base class of {0} is: {1}.", typeof(T), typeof(T).BaseType); }
In this case, you must supply the type parameter upon invocation:
static void Main(string[] args) { ... // Must supply type parameter if // the method does not take params. DisplayBaseClass<int>(); DisplayBaseClass<string>(); // Compiler error! No params? Must supply placeholder! // DisplayBaseClass(); Console.ReadLine(); }
P.297(原文P.392)
// Note that we now have a default constructor constraint (see next section). public class MyList<T> where T : new() { private List<T> listOfData = new List<T>(); public virtual void Insert(T data) { } } // Derived type must honor constraints. public class MyReadOnlyList<T> : MyList<T> where T : new() { public override void Insert(T data) { } }
P.298(原文P.392)
該類型參數<T>必須包含一個默認的構造函數,因為無法預知自定義構造函數的格式,所以如果泛型類型必須創建一個類型參數的實例,這將是非常有用的。注意在有多個約束的類型上,此約束必須列在末尾。
where T : new()
The type parameter <T> must have a default constructor. This is helpful if your generic type must create an instance of the type parameter because you cannot assume you know the format of custom constructors. Note that this constraint must be listed last on a multiconstrained type.
P.307(原文P.405)
Clearly, the previous SimpleDelegate example was intended to be purely illustrative in nature, given that there would be no compelling reason to define a delegate simply to add two numbers! To provide a more realistic use of delegate types, let’s use delegates to define a Car class that has the ability to inform external entities about its current engine state. To do so, we will take the following steps:
1. Define a new delegate type that will send notifications to the caller.
2. Declare a member variable of this delegate in the Car class.
3. Create a helper function on the Car that allows the caller to specify the method to call back to.
4. Implement the Accelerate() method to invoke the delegate’s invocation list under the correct circumstances.
P.388(原文P.506-507)
If you want to build a more complex query, you might wish to only find the BMWs that have a Speed value above 90. To do so, simply build a compound Boolean statement using the C# && operator:
static void GetFastBMWs(List<Car> myCars)
{
// Find the fast BMWs!
var fastCars = from c in myCars where c.Speed > 90 && c.Make == "BMW" select c;
foreach (var car in fastCars)
{
Console.WriteLine("{0} is going too fast!", car.PetName);
}
}
P.389(原文P.508)
As you know, nongeneric types are capable of containing any combination of items, as the members of these containers (again, such as the ArrayList) are prototyped to receive System.Objects. For example, assume an ArrayList contains a variety of items, only a subset of which are numerical. If you want to obtain a subset that contains only numerical data, you can do so using OfType<T>(), since it filters out each element whose type is different from the given type during the iterations:
static void OfTypeAsFilter() { // Extract the ints from the ArrayList. ArrayList myStuff = new ArrayList(); myStuff.AddRange(new object[] { 10, 400, 8, false, new Car(), "string data" }); var myInts = myStuff.OfType<int>(); // Prints out 10, 400, and 8. foreach (int i in myInts) { Console.WriteLine("Int value: {0}", i); } }
....未完待續....