This is the second time I have systematically studied a computer language. The first time is learning process oriented C language; the second time is learning object-oriented C# now.
(Ebook)Illustrated C# 7: The C# Language Presented Clearly, Concisely, and Visually
链接:百度网盘分享链接
提取码:q91l
Chapter 1: C# and the .NET Framework
The first four sections are introductory, so the code examples are omitted.
Chapter 2: C# and .NET Core
The first four sections are introductory, so the code examples are omitted.
Chapter 3: Overview of C# Programming
The first four sections are introductory, so the code examples are omitted.
Chapter 4: Types,Storage,and Variables
The first four sections are introductory, so the code examples are omitted.
/* Core: • Declaring a class • Creating instances of the class • Accessing the class members (that is, writing to a field and reading from a field) Output: • t1:76,57,66 */ using System;
namespaceAnonymous { classDaysTemp//Declare the class { publicint High,Low; //Declare the instance fields publicintAverage() //Declare the insntance method { return (High+Low)/2;//We use int as the return type, so the decimal part is ignored if there is a decimal } } classProgram { staticvoidMain(string[] args) { //Create one instances of DaysTemp DaysTemp t1 = new DaysTemp(); //Write to the fields of each instance t1.High = 76; t1.Low = 57; //Read from the fields of the instance and call a meathod of the instance Console.WriteLine($"t1:{t1.High},{t1.Low},{t1.Average()}"); } } }
/* Core: Value Parameters Analysis: • Before the method call, variables a1 and a2, which will be used as the actual parameters, are already on the stack. • By the beginning of the method, the system will have allocated space on the stack for the formal parameters and copied the values from the actual parameters. – Since a1 is a reference type, the reference is copied, resulting in both the actual and formal parameters referring to the same object in the heap. – Since a2 is a value type, the value is copied, producing an independent data item. • At the end of the method, both f2 and the field of object f1 have been incremented by 5. – After method execution, the formal parameters are popped off the stack. – The value of a2, the value type, is unaffected by the activity in the method. – The value of a1, the reference type, however, has been changed by the activity in the method. Output: • f1.Val: 25, f2: 15 • a1.Val: 25, a2: 10 */ using System;
namespaceAnonymous { classMyClass { publicint Val = 20; // Initialize the field to 20. } classProgram { staticvoidMyMethod( MyClass f1, int f2 ) { f1.Val = f1.Val + 5; // Add 5 to field of f1 param. f2 = f2 + 5; // Add 5 to second param. Console.WriteLine($"f1.Val: { f1.Val }, f2: { f2 }"); } staticvoidMain() { MyClass a1 = new MyClass(); int a2 = 10; MyMethod( a1, a2 ); // Call the method. Console.WriteLine($"a1.Val: { a1.Val }, a2: { a2 }"); } } }
/* Core: Reference Parameters Analysis: • Before the method call, variables a1 and a2, which will be used as the actual parameters, are already on the stack. • By the beginning of the method, the names of the formal parameters will have been set as aliases for the actual parameters. Variables a1 and f1 reference the same memory location, and a2 and f2 also reference the same memory location. • At the end of the method, both f2 and the field of the object of f1 have been incremented by 5. • After method execution, the names of the formal parameters are gone (“out of scope”), but both the value of a2, which is the value type, and the value of the object pointed at by a1, which is the reference type, have been changed by the activity in the method. Output: • f1.Val: 25, f2: 15 • a1.Val: 25, a2: 10 */ using System;
namespaceAnonymous { classMyClass { publicint Val = 20; // Initialize the field to 20. } classProgram { staticvoidMyMethod(ref MyClass f1,refint f2) { f1.Val = f1.Val + 5; // Add 5 to field of f1 param. f2 = f2 + 5; // Add 5 to second param. Console.WriteLine($"f1.Val: { f1.Val }, f2: { f2 }"); } staticvoidMain() { MyClass a1 = new MyClass(); int a2 = 10; MyMethod(ref a1,ref a2); // Call the method. Console.WriteLine($"a1.Val: { a1.Val }, a2: { a2 }"); } } }
Output Parameters
Attention:
The biggest difference between reference type and reference type is that the direction of variable passing is different. Reference type is passed from method to method, while output type is from inside method. Therefore, the value of variable (parameter) must be expressed inside method.
/* Core: Output Parameters Analysis: • Before the method call, variables a1 and a2, which will be used as the actual parameters, are already on the stack. • At the beginning of the method, the names of the formal parameters are set as aliases for the actual parameters. Therefore, variables a1 and f1 reference the same memory location, and a2 and f2 also reference the same memory location. The names a1 and a2 are out of scope and cannot be accessed from inside MyMethod. • Inside the method, the code creates an object of type MyClass and assigns it to f1. It then assigns a value to f1’s field and also assigns a value to f2. The assignments to f1 and f2 are both required since they’re output parameters. • After method execution, the names of the formal parameters are out of scope, but the values of both a1, the reference type, and a2, the value type, have been changed by the activity in the method. Output: • After method call: a1:25 a2:15 */ using System;
namespaceAnonymous { classMyClass { publicint Val = 20; // Initialize the field to 20. } classProgram { staticvoidMyMethod(out MyClass f1,outint f2) { f1 = new MyClass(); f1.Val = 25; f2 = 15; } staticvoidMain() { MyClass a1 = null; int a2; MyMethod(out a1,out a2); //In the C#7,you can institute the three line with MyMethod(out MyClass a1,out int a2); Console.WriteLine($"After method call: a1:{a1.Val} a2:{a2}"); } } }
/* Core: Reference Types As Value Parameters Analysis: • Passing a reference type object as a value parameter: If you create a new object inside the method and assign it to the formal parameter, it breaks the connection between the formal parameter and the actual parameter, and the new object does not persist after the method call. • At the beginning of the method, both the actual parameter and the formal parameter point to the same object in the heap. • After the assignment to the object’s member, they still point at the same object in the heap. • When the method allocates a new object and assigns it to the formal parameter, the actual parameter (outside the method) still points at the original object, and the formal parameter points at the new object. • After the method call, the actual parameter points to the original object, and the formal parameter and new object are gone. Output: • Before method call: 20 • After member assignment: 50 • After new object creation: 20 • After method call: 50 */ using System;
namespaceAnonymous { classMyClass { publicint Val = 20; // Initialize the field to 20. } classProgram { staticvoidRefAsParameter( MyClass f1 ) { f1.Val = 50; Console.WriteLine($"After member assignment: { f1.Val }"); f1 = new MyClass(); Console.WriteLine($"After new object creation: { f1.Val }"); } staticvoidMain() { MyClass a1 = new MyClass(); Console.WriteLine($"Before method call: { a1.Val }"); RefAsParameter( a1 ); Console.WriteLine($"After method call: { a1.Val }"); } } }
/* Core: Reference Types As Reference Parameters Analysis: • Passing a reference type object as a reference parameter: If you create a new object inside the method and assign it to the formal parameter, that new object persists after the method is ended and is the value referenced by the actual parameter. • At the beginning of the method, both the actual parameter and the formal parameter point to the same object in the heap. • After the assignment to the object’s member, they still point at the same object in the heap. • When the method allocates a new object and assigns it to the formal parameter, the actual parameter (outside the method) still points at the original object, and the formal parameter points at the new object. • After the method call, the actual parameter points to the original object, and the formal parameter and new object are gone. Output: • Before method call: 20 • After member assignment: 50 • After new object creation: 20 • After method call: 20 */ using System;
namespaceAnonymous { classMyClass { publicint Val = 20; // Initialize the field to 20. } classProgram { staticvoidRefAsParameter(ref MyClass f1 ) { f1.Val = 50; Console.WriteLine($"After member assignment: { f1.Val }"); f1 = new MyClass(); Console.WriteLine($"After new object creation: { f1.Val }"); } staticvoidMain() { MyClass a1 = new MyClass(); Console.WriteLine($"Before method call: { a1.Val }"); RefAsParameter( ref a1 ); Console.WriteLine($"After method call: { a1.Val }"); } } }
Chapter7: More About Classes
Properties and Associated Fields
Convention for naming properties and their backing fields:
Use Pascal casing for the property, and then for the field, use the camel case version of the same identifier, with an underscore in front.
/* Core: Properties and Associated Fields Analysis: The implicit parameter value in the set accessor is a normal value parameter. Like other value parameters, you can use it to send data into a method body—or in this case, the accessor block. Once inside the block, you can use value like a normal variable, including assigning values to it. Other important points about accessors are the following: • All paths through the implementation of a get accessor must include a return statement that returns a value of the property type. • The set and get accessors can be declared in either order, and no methods other than the two accessors are allowed on a property. Output: • MyValue:10 • MyValue:20 */ using System;
namespaceAnonymous { classC1 { privateint _myValue = 10;//Backing Field:memory allocated publicint MyValue//Property:no memory allocated { set{_myValue = value;}//Sets the value of field theRealValue get{return _myValue;}//Gets the value of the field } } classProgram {
staticvoidMain() { C1 c = new C1(); //Read from the property as if it were a field Console.WriteLine("MyVlaue:{0}",c.MyValue);
c.MyValue = 20; //Use assignment to set the value of a property Console.WriteLine("MyValue:{0}",c.MyValue); } } }
/* Core: Static Properties Analysis: • They cannot access instance members of a class—although they can be accessed by them. • They exist regardless of whether there are instances of the class. • From inside the class, you reference the static property using just its name. • From outside the class, you must reference the property either using its class name or using the using static construct, as described earlier in this chapter. Output: • Init Value:0 • New Value:10 • New Value:20 • Value from inside:20 */ using System; usingstatic Anonymous.Trivial; namespaceAnonymous { classTrivial { publicstaticint MyValue { get;//auto-properties set;//auto-properties } publicvoidPrintValue() { Console.WriteLine("Value from inside:{0}",MyValue); } } classProgram {
staticvoidMain() { Console.WriteLine("Init Value:{0}",Trivial.MyValue);//Read from outside the class Trivial.MyValue = 10;//Write from outside the class Console.WriteLine("New Value:{0}",Trivial.MyValue);//Read from outside the class
MyValue =20;//Write from outside the class,but no class beacause of using static Console.WriteLine($"New Value:{MyValue}");
Trivial tr = new Trivial(); tr.PrintValue(); //From the result,we can see Class's static members occupy only the same area in memory } } }
/* Core: Static Constructor Analysis: • The constructor is used to "assign values to the initialization of an object" Output: • Next Random #: 47857058 • Next Random #: 47857058 */ using System; namespaceAnonymous { classRandomNumberClass { privatestatic Random RandomKey; // Private static field staticRandomNumberClass() // Static constructor { RandomKey = new Random(); // Initialize RandomKey } publicintGetRandomNumber() { return RandomKey.Next(); } } classProgram { staticvoidMain() { RandomNumberClass a = new RandomNumberClass(); RandomNumberClass b = new RandomNumberClass(); Console.WriteLine("Next Random #: {0}", a.GetRandomNumber()); Console.WriteLine($"Next Random #: { b.GetRandomNumber() }"); } } }
/* Core: Declare an indexer Analysis: • The indexer must read and write values of type string—so string must be declared as the indexer’s type. It must be declared public so that it can be accessed from outside the class. • The three fields in the example have been arbitrarily indexed as integers 0 through 2, so the formal parameter between the square brackets, named index in this case, must be of type int. • In the body of the set accessor, the code determines which field the index refers to and assigns the value of implicit variable value to it. In the body of the get accessor, the code determines which field the index refers to and returns that field’s value. Output: • Person LastName:Doe,FirstName:Jane,CityOfBirth:Dallas */ using System; namespaceAnonymous { classEmployee { privatestring LastName; privatestring FirstName; privatestring CityOfBirth; publicstringthis[int index] { set { switch(index) { case0:LastName = value;break; case1:FirstName = value;break; case2:CityOfBirth = value;break; default: thrownew ArgumentOutOfRangeException("index"); } } get { switch(index) { case0: return LastName; case1:return FirstName; case2:return CityOfBirth; default:thrownew ArgumentOutOfRangeException("index"); } } } } classProgram { staticvoidMain() { Employee Person = new Employee(); Person[0] = "Doe"; Person[1] = "Jane"; Person[2] = "Dallas"; Console.WriteLine($"Person LastName:{Person[0]},FirstName:{Person[1]},CityOfBirth:{Person[2]}"); } } }
Chapter8: Classes and Inheritance
New Modifier
(Include:Mask Members of a Base Class+Base Access+Cast To Base Class)
/* Core: Mask Members of a Base Class+Base Access+Cast to base class Analysis: • To mask an inherited data member, declare a new member of the same type and with the same name. • To mask an inherited function member, declare a new function member with the same signature. Remember that the signature consists of the name and parameter list but does not include the return type. • To let the compiler know that you’re purposely masking an inherited member, use the new modifier. Without it, the program will compile successfully, but the compiler will warn you that you’re hiding an inherited member. • You can also mask static members. Output: • Field1 -- In the derived class • Field1 -- In the base class • Field1 -- In the base class */ using System; namespaceAnonymous { classSomeClass { publicstring Field1 = "Field1 -- In the base class"; publicvoidPrintField1() { Console.WriteLine(Field1); } } classOtherClass : SomeClass { newpublicstring Field1 = "Field1 -- In the derived class"; newpublicvoidPrintField1() { Console.WriteLine(Field1); } publicvoidPrintBaseField1() { Console.WriteLine(base.Field1);//To an elegant program,wo need to use 'base' less. } } classProgram { staticvoidMain() { OtherClass oc = new OtherClass(); SomeClass a = (SomeClass)oc;//cast to base class oc.PrintField1(); oc.PrintBaseField1(); a.PrintField1(); } } }
Override Modifier
(Include:Override a Method+Distingish the Difference Between 'new' And 'override')
/* Core: Override a Method+Distingish between 'new' and 'override' Analysis: • When you use a reference to the base class part of an object to call an overridden method, the method call is passed up the derivation hierarchy for execution to the most derived version of the method marked as override. • If there are other declarations of the method at higher levels of derivation that are not marked as override, they are not invoked. Output: • Override:This is the second derived class • Override:This is the second derived class • New:This is the second derived class • This is the derived class */ using System; namespaceAnonymous { classMyBaseClass { virtualpublicvoidPrint() { Console.WriteLine("This is the base class"); } } classMyDerivedClass : MyBaseClass { overridepublicvoidPrint() { Console.WriteLine("This is the derived class"); } } classSecondDerived1 : MyDerivedClass { overridepublicvoidPrint() { Console.WriteLine("Override:This is the second derived class"); } } classSecondDerived2 : MyDerivedClass { newpublicvoidPrint() { Console.WriteLine("New:This is the second derived class"); } } classProgram { staticvoidMain() { SecondDerived1 derived1 = new SecondDerived1(); MyBaseClass mybc1 = (MyBaseClass)derived1;
/* Core: Abstract Classes And Abstract Methods Analysis: • It must be a function member. That is, fields and constants cannot be abstract members. • It must be marked with the abstract modifier. • It must not have an implementation code block. The code of an abstract member is represented by a semicolon. • You cannot create instances of an abstract class. • An abstract class is declared using the abstract modifier. Output: • This is a string. • 28 • Perimeter Length: 30 */ using System; namespaceAnonymous { abstractclassMyBase { publicint SideLength = 10;//The data member-fields and constants constint TriangleSideCount = 3;//-cannot be declared as abstract abstractpublicvoidPrintStuff(string s);//Abstact method:It must not have an implementation code block abstractpublicint MyInt{get;set;}//Abstract property publicintPerimeterLength() { return TriangleSideCount * SideLength; } } classMyClass : MyBase { publicoverridevoidPrintStuff(string s)//Override abstract method { Console.WriteLine(s); } privateint _myInt; publicoverrideint MyInt//Override abstract property { get { return _myInt; } set { _myInt = value; } } } classProgram { staticvoidMain(string[] args) { // MyBase oc = new MyBase(); //Error.Cannot instantiate an abstract class MyClass mc = new MyClass(); mc.PrintStuff("This is a string."); mc.MyInt = 28; Console.WriteLine(mc.MyInt); Console.WriteLine($"Perimeter Length:{mc.PerimeterLength()}");//Inheritance } } }
/* Core: User-Defined Type Conversions Analysis: • The public and static modifiers are required for all user-defined conversions. • C# provides implicit and explicit conversions. – With an implicit conversion, the compiler automatically makes the conversion, if necessary, when it is resolving what types to use in a particular context. – With an explicit conversion, the compiler will make the conversion only when an explicit cast operator is used. Output: • li:100,value:100 */ using System; namespaceAnonymous { classLimitedInt { constint MaxValue = 100; constint MinValue = 0; privateint mTheValue = 0; publicint TheValue//Property:Convert rules { get { return mTheValue; } set { if(value < MinValue) mTheValue = 0; else mTheValue = value > MaxValue ? MaxValue : value; } } publicstaticimplicitoperatorint(LimitedInt li)//Convert to int type { return li.TheValue; } publicstaticimplicitoperatorLimitedInt(int x)//Convert to LimitedInt type { LimitedInt li = new LimitedInt(); li.TheValue = x; return li; } } classProgram { staticvoidMain(string[] args) { LimitedInt li = 500;//Convert 500 to LimitedInt intvalue = li;//Convert LImitedInt to int Console.WriteLine($"li:{li.TheValue},value:{value}"); } } }
/* Core: User-Defined Type Conversions Analysis: • The public and static modifiers are required for all user-defined conversions. • C# provides implicit and explicit conversions. – With an implicit conversion, the compiler automatically makes the conversion, if necessary, when it is resolving what types to use in a particular context. – With an explicit conversion, the compiler will make the conversion only when an explicit cast operator is used. Output: • li:100,value:100 */ using System; namespaceAnonymous { classLimitedInt { constint MaxValue = 100; constint MinValue = 0; privateint mTheValue = 0; publicint TheValue//Property:Convert rules { get { return mTheValue; } set { if(value < MinValue) mTheValue = 0; else mTheValue = value > MaxValue ? MaxValue : value; } } publicstaticexplicitoperatorint(LimitedInt li)//Convert to int type { return li.TheValue; } publicstaticexplicitoperatorLimitedInt(int x)//Convert to LimitedInt type { LimitedInt li = new LimitedInt(); li.TheValue = x; return li; } } classProgram { staticvoidMain(string[] args) { LimitedInt li = (LimitedInt)500;//Convert 500 to LimitedInt intvalue = (int)li;//Convert LImitedInt to int Console.WriteLine($"li:{li.TheValue},value:{value}"); } } }
/* Core: Operator Overloading Analysis: • Operator overloading is available only for classes and structs. • You can overload an operator x for use with your class or struct by declaring a method named operator x that implements the behavior (for example, operator +, operator -, and so on). – The overload methods for unary operators take a single parameter of the class or struct type. – The overload methods for binary operators take two parameters, at least one of which must be of the class or struct type. Output: • li1:10,li2:100 • li3:0 • li3:90 • li3:0 • li3:20 */ using System; namespaceAnonymous { classLimitedInt { constint MaxValue = 100; constint MinValue = 0; publicstatic LimitedInt operator -(LimitedInt x) { //In this strange class,negating a value just sets its value to 0. LimitedInt li = new LimitedInt(); li.TheValue = 0; return li; } publicstatic LimitedInt operator -(LimitedInt x,LimitedInt y) { LimitedInt li = new LimitedInt(); li.TheValue = x.TheValue - y.TheValue; return li; } publicstatic LimitedInt operator +(LimitedInt x,double y) { LimitedInt li = new LimitedInt(); li.TheValue = x.TheValue + (int)y; return li; } privateint _theValue = 0; publicint TheValue { /* Scenario one:we use the default setting set; get; */ get{return _theValue;} set { if(value < MinValue) _theValue = 0; else _theValue = value > MaxValue ? MaxValue : value; } } } classProgram { staticvoidMain(string[] args) { LimitedInt li1 = new LimitedInt(); LimitedInt li2 = new LimitedInt(); LimitedInt li3 = new LimitedInt(); double plusnumber = 10.0; /* Scenario one:we can see the output "li1:0,li2:100" li1.TheValue =-10; li2.TheValue =101; Console.WriteLine($"li1:{li1.TheValue},li2:{li2.TheValue}"); */ li1.TheValue =10; li2.TheValue =101; Console.WriteLine($"li1:{li1.TheValue},li2:{li2.TheValue}");
/* Core: The Using Statement Analysis: • The standard way of handling the possibility of exceptions is to place the code that might cause an exception in a try block and place any code that must be executed, whether or not there is an exception, into a finally block. • Allocating the resource • Placing Statement in a try block • Creating a call to the resource’s Dispose method and placing it in a finally block Output: • Four score and seven years ago,... */ using System; //Using directive,not using statement; using System.IO; namespaceAnonymous { classProgram { staticvoidMain() { //using statement using (TextWriter tw = File.CreateText("Lincoln.txt")) { tw.WriteLine("Four score and seven years ago,..."); } //using statement using (TextReader tr = File.OpenText("Lincoln.txt")) { string InputString; while(null!=(InputString = tr.ReadLine())) Console.WriteLine(InputString); } } } }
2.ANOTHER FORM
ResType Resource = new ResType(...); using(Resource) Statement
ln this form of the using statement,the resource has already been allocated,so it is outside the scope of the using statement.Attempting to use the resource after the using statement will cause an exception because Dispose has alredy been called.So it is discouraged.
/* Core: Differece Between Struct And Class Analysis: • Classes are reference types, and structs are value types. • Structs are implicitly sealed, which means they cannot be derived from. • A variable of a struct type cannot be null. • Two struct variables cannot refer to the same object Output: • Class Initialize:cs1.X:0,CS1.Y:0 • Stuct Initialize:ss1.X:0,ss1.Y:0,ss2.X:0,ss2.Y:0 • Reference Type Pointer:cs1.X:5,cs1.Y:10,cs2.X:5,cs1.Y:10 • Value Type Copy:ss1.X:5,ss1.Y:10,ss2.X:5,ss2.Y:10 */ using System; //Using directive,not using statement; using System.IO; namespaceAnonymous { classCSimple { publicint X,Y; } struct Simple { publicint X,Y; } classProgram { staticvoidMain() { /* we can assign 'null' to the reference types, but in the following code if we want to use it,we must point the 'null pointer' to the specific place; */ CSimple cs1 = new CSimple(),cs2=null; Console.WriteLine($"Class Initialize:cs1.X:{cs1.X},cs1.Y:{cs1.Y}"); //Console.WriteLine($"Class Initialize:cs2.X:{cs2.X},cs2.Y:{cs2.Y}"); //This will cause errors:because cs2 Object reference not set to an instance of an object /* We should know the truth ,we use 'new' to initialize the struct, system will automaticly assign initial value to struct variable,such as ss1.X=0;ss1.Y=0;ss2.X=0;ss2.Y=0;If we do not initialize the initial value, we can not use the struct variable. */ Simple ss1 = new Simple(),ss2 = new Simple(); Console.WriteLine($"Struct Initialize:ss1.X:{ss1.X},ss1.Y:{ss1.Y},ss2.X:{ss2.X},ss2.Y:{ss2.Y}");
cs2 = cs1;//The pointer points to the heap where CS1 resides. Console.WriteLine($"Reference Type Pointer:cs1.X:{cs1.X},cs1.Y:{cs1.Y},cs2.X:{cs2.X},cs1.Y:{cs2.Y}"); ss2 = ss1;//The struct variables of ss1 are copied to SS2 Console.WriteLine($"Value Type Copy:ss1.X:{ss1.X},ss1.Y:{ss1.Y},ss2.X:{ss2.X},ss2.Y:{ss2.Y}"); } } }
/* Core: Subarrays in Jagged Arrays Analysis: • If somewhere do not understand,read the book Chapter Array Output: • ArrOption settings: • Arr[0][0,0] = 10 • Arr[0][0,1] = 20 • • Arr[0][1,0] = 100 • Arr[0][1,1] = 200 • • Arr[1][0,0] = 30 • Arr[1][0,1] = 40 • Arr[1][0,2] = 50 • • Arr[1][1,0] = 300 • Arr[1][1,1] = 400 • Arr[1][1,2] = 500 • • Arr[2][0,0] = 60 • Arr[2][0,1] = 70 • Arr[2][0,2] = 80 • Arr[2][0,3] = 90 • • Arr[2][1,0] = 600 • Arr[2][1,1] = 700 • Arr[2][1,2] = 800 • Arr[2][1,3] = 900 */ using System; namespaceAnonymous { classProgram { staticvoidMain() { int[][,] Arr; //Declare an array of 2D arrays Arr = newint[3][,];//Instantiate an array of three 2D arrays Arr[0]= newint[,]{{10,20},{100,200}}; Arr[1]= newint[,]{{30,40,50},{300,400,500}}; Arr[2]= newint[,]{{60,70,80,90},{600,700,800,900}}; //Get length of dimension 0 of Arr,in this Arr.GetLength(0)=3 for (int i = 0;i < Arr.GetLength(0);i++) { //Get length of dimension 0 of Arr[i],in this Arr[0].GetLength(0)=2;Arr[1].GetLength(0)=2;Arr[2].GetLength(0)=2 for (int j = 0; j < Arr[i].GetLength(0);j++) { //Get length of dimension 1 of Arr[i],in this Arr[0].GetLength(1)=2;Arr[1].GetLength(1)=3;Arr[2].GetLength(1)=4 for (int k = 0; k < Arr[i].GetLength(1);k++) { Console.WriteLine($"Arr[{i}][{j},{k}] = {Arr[i][j,k]}"); } Console.WriteLine(" "); } Console.WriteLine(" "); } } } }
Chapter14: Delegates
Delegate Example
Multiple methods can be called sequentially using multicast delegates, whereas multicast delegates only get the result of the last method called.In general, the return value of a multicast delegate is declared void.
/* Core: Delegate Example Analysis: The following code defines and uses a delegate with no parameters and no return value. Note the following about the code: • Class Test defines two print functions. • Method Main creates an instance of the delegate and then adds three more methods. • The program then invokes the delegate, which calls its methods. Before invoking the delegate, however, it checks to make sure it’s not null. Output: • Print1 -- instance • Print2 -- static • Print1 -- instance • Print2 -- static */ using System; namespaceAnonymous { //Define a delegate type with no return value and no parameters. delegatevoidPrintFunction(); classTest { publicvoidPrint1() { Console.WriteLine("Print1 -- instance"); } publicstaticvoidPrint2() { Console.WriteLine("Print2 -- static") } } classProgram { staticvoidMain() { Test t = new Test();//Create a test class instance PrintFunction pf;//Create a null delegate
pf = t.Print1; //Instantiate and initialize the delegate
//Add three more methods to the delegate pf += Test.Print2; pf += t.Print1; pf += Test.Print2; //The delegate now contains four methods if(null != pf)//Make sure the delegate isn't null pf();//Invoke the delegate else Console.WriteLine("Delegate is empty"); } } }
/* Core: Nonstandard Event Usage Analysis: • In its constructor, class Dozens subscribes to the event, supplying method IncrementDozensCount as its event handler. • In method DoCount in class Incrementer, event CountedADozen is raised each time the method increments another 12 times. Output: • Number of dozens = 8 */ using System; namespaceAnonymous { delegatevoidHandler(); //1.Delegate Type Declaration
/*****Pbulisher******/ classIncrementer { publicevent Handler CountedADozen;//2.Event Declaration:Create and publish an event publicvoidDoCount() { for(int i=1;i<100;i++) if(i%12==0 && CountedADozen != null) CountedADozen();//3.Raise the event:Raise the event every 12 counts } } /*****Subscriber*****/ classDozens { publicint DozensCount{get;privateset;} publicDozens(Incrementer incrementer) { DozensCount = 0; incrementer.CountedADozen += IncrementDozensCount;//4.Subscribe to the event } voidIncrementDozensCount()//5.Declare the event handler { DozensCount++; } } /*****Main Program*****/ classProgram { staticvoidMain() { Incrementer incrementer = new Incrementer(); Dozens dozensCounter = new Dozens(incrementer); incrementer.DoCount();//Once invoke the 'Raise the event',it will invoke 'the event handler' at the same time Console.WriteLine("Number of dozens = {0}",dozensCounter.DozensCount); } } }
Standard Event Usage
'EventHandler' is the system-defined EventHandler delegate
/* Core: Standard Event Usage Analysis: • The declaration for delegate Handler has been removed since the event uses the system-defined EventHandler delegate. • The signature of the event handler declaration in the subscriber class must match the signature (and return type) of the event delegate, which now uses parameters of type object and EventArgs. In the case of event handler IncrementDozensCount, the method just ignores the formal parameters. • The code that raises the event must invoke the event with objects of the appropriate parameter types. Output: • Number of dozens = 8 */ using System; namespaceAnonymous { //delegate void Handler(); //No more need,because we use the system-defined EventHandler delegate
/*****Pbulisher******/ classIncrementer { publicevent EventHandler CountedADozen;//2.Event Declaration:Create and publish an event publicvoidDoCount() { for(int i=1;i<100;i++) if(i%12==0 && CountedADozen != null) CountedADozen(this,null);//3.Raise the event:Use EventHandler's parameters when raising the event } } /*****Subscriber*****/ classDozens { publicint DozensCount{get;privateset;} publicDozens(Incrementer incrementer) { DozensCount = 0; incrementer.CountedADozen += IncrementDozensCount;//4.Subscribe to the event } voidIncrementDozensCount(object source,EventArgs e)//5.Declare the event handler:The signature of the event handler must match that of the delegate { DozensCount++; } } /*****Main Program*****/ classProgram { staticvoidMain() { Incrementer incrementer = new Incrementer(); Dozens dozensCounter = new Dozens(incrementer); incrementer.DoCount();//Once invoke the 'Raise the event',it will invoke 'the event handler' at the same time Console.WriteLine("Number of dozens = {0}",dozensCounter.DozensCount); } } }
/* Core: Passing Data by Extending EventArgs Analysis: • To pass data in the second parameter of your event handler and adhere to the standard conventions, you need to declare a custom class derived from EventArgs that can store the data you need passed. By convention, the name of the class should end in EventArgs. Output: • Incremented at iteration:12 in Anonymous.Incrementer • Incremented at iteration:24 in Anonymous.Incrementer • Incremented at iteration:36 in Anonymous.Incrementer • Incremented at iteration:48 in Anonymous.Incrementer • Incremented at iteration:60 in Anonymous.Incrementer • Incremented at iteration:72 in Anonymous.Incrementer • Incremented at iteration:84 in Anonymous.Incrementer • Incremented at iteration:96 in Anonymous.Incrementer • Number of dozens = 8 */ using System; namespaceAnonymous { publicclassIncrementerEventArgs : EventArgs//Custom class derived from EventArgs { publicint IterationCount{get;set;}//Stores an integer } classIncrementer { publicevent EventHandler<IncrementerEventArgs> CountedADozen;//Generic delegate using custom class publicvoidDoCount() { IncrementerEventArgs args = new IncrementerEventArgs(); for(int i = 1;i < 100;i++) if(i%12==0 && CountedADozen != null) { args.IterationCount = i; CountedADozen(this,args);//Pass parameters when raising the event } } } classDozens { publicint DozensCount{get;privateset;} publicDozens(Incrementer incrementer) { DozensCount = 0; incrementer.CountedADozen += IncrementDozensCount; } voidIncrementDozensCount(object source,IncrementerEventArgs e) { Console.WriteLine($"Incremented at iteration:{e.IterationCount} in {source.ToString()}"); DozensCount++; } } classProgram { staticvoidMain() { Incrementer incrementer = new Incrementer(); Dozens dozensCounter = new Dozens(incrementer); incrementer.DoCount(); Console.WriteLine($"Number of dozens = {dozensCounter.DozensCount}"); } }
/* Core: An Interface Is a Reference Type(A Simple Example) Analysis: • For example, the following code declares an interface and a class that implements it. The code in Main creates an object of the class and calls the implementation method through the class object. It also creates a variable of the interface type, casts the reference of the class object to the interface type,or use the 'as' opertor to convert to the interface type and calls the implementation method through the reference to the interface. Output: • Calling through: object • Calling through: interface • Calling through: interface */ using System; namespaceAnonymous { interfaceIIfc1 { voidPrintOut(string s); } classMyClass : IIfc1 { publicvoidPrintOut(string s) { Console.WriteLine($"Calling through: {s}"); } } classProgram { staticvoidMain() { MyClass mc = new MyClass(); mc.PrintOut("object");
IIfc1 ifc = (IIfc1)mc; ifc.PrintOut("interface"); IIfc1 ift = mc as IIfc1; ift.PrintOut("interface"); } } }
/* Core: An Interface Is a Reference Type(A Simple Example) Analysis: • The program declares a class called Animal, which is used as a base class for several other classes that represent various types of animals. It also declares an interface named ILiveBirth. Classes Cat, Dog, and Bird all derive from base class Animal. Cat and Dog both implement the ILiveBirth interface, but class Bird does not. In Main, the program creates an array of Animal objects and populates it with a class object of each of the three types of animal classes. The program then iterates through the array and, using the as operator, retrieves a reference to the ILiveBirth interface of each object that has one and calls its BabyCalled method. Output: • Baby is called: kitten • Baby is called: puppy */ using System; namespaceAnonymous { interfaceILiveBirth//Declare interface { stringBabyCalled(); } classAnimal//Base class Animal {
} classCat : Animal, ILiveBirth//Declare class Cat { string ILiveBirth.BabyCalled() { return"kitten"; } } classDog : Animal, ILiveBirth//Declare class Dog { string ILiveBirth.BabyCalled() { return"puppy"; } } classBird : Animal//Declare class Bird {
} classProgram { staticvoidMain() { Animal[] animalArray = new Animal[3];//Create Animal array animalArray[0] = new Cat();//Insert Cat class object animalArray[1] = new Bird();//Insert Bird class object animalArray[2] = new Dog();//Insert Dog class object foreach(Animal a in animalArray)//Cycle through array { ILiveBirth b = a as ILiveBirth;//if implements ILiveBirth.. if(b != null) Console.WriteLine($"Baby is called: {b.BabyCalled()}"); } } } }
Chapter17: Conversions
This section focuses on implicit and explicit conversions of value types and reference types, as well as overflow checks for value types,implicit conversion of boxing and conversion of user-defined types.
/* Core: A User-Defined Conversion Analysis: • The following code defines a class called Person that contains a person’s name and age. The class also defines two implicit conversions. The first converts a Person object to an int value. The target int value is the age of the person. The second converts an int to a Person object. Output: • Person Info: bill, 25 • Person Info: Nemo, 35 */ using System; namespaceAnonymous { classPerson { publicstring Name; publicint Age; publicPerson(string name,int age) { Name = name; Age = age; } publicstaticimplicitoperatorint(Person p)//Convert Person to int { return p.Age; } publicstaticimplicitoperatorPerson(int i)//Convert int to Person { returnnew Person("Nemo",i); } } classProgram { staticvoidMain() { Person bill = new Person("bill",25); int age = bill;//Convert a Person object to an int Console.WriteLine($"Person Info: {bill.Name},{age}");
Person anon = 35;//Convert an int to a Person object Console.WriteLine($"Person Info: {anon.Name},{anon.Age}"); } } }
/* Core: The Stack Example Using Generics Analysis: • The following code shows the stack example implemented using generics. Method Main defines two variables: stackInt and stackString. The two constructed types are created using int and string as the type arguments. Output: • Value: 7 • Value: 5 • Value: 3 • Value: Hi there! • Value: This is fun */ using System; namespaceAnonymous { classMyStack<T>//泛型 { T[] StackArray; int StackPointer = 0; publicvoidPush(T x)//进栈方法 { if(!IsStackFull) StackArray[StackPointer++] = x; } public T Pop()//出栈方法 { return (!IsStackEmpty) ? StackArray[--StackPointer] : StackArray[0]; } constint MaxStack = 10;//这个数组最大容纳是个元素 bool IsStackFull {get{return StackPointer >= MaxStack;}}//大于等于十个元素,说明数组被存满了 bool IsStackEmpty {get{return StackPointer <= 0;}}//小于等于零个元素,说明数组被清空了 publicMyStack() { StackArray = new T[MaxStack];//实例化数组对象 } publicvoidPrint()//打印方法 { for(int i = StackPointer-1;i >= 0;i--) Console.WriteLine($"Value: {StackArray[i]}"); } } classProgram { staticvoidMain() { MyStack<int> StackInt = new MyStack<int>();//声明并实例化 MyStack<string> StackString = new MyStack<string>(); //这边可以自己多试验几种情况,感受一下进栈出栈(先进后出,后进先出),还有数组最大容量 /* //Example one:发现这个数组最大容量确实是10个元素 StackInt.Push(1); StackInt.Push(2); StackInt.Push(3); StackInt.Push(4); StackInt.Push(5); StackInt.Push(6); StackInt.Push(7); StackInt.Push(8); StackInt.Push(9); StackInt.Push(10); StackInt.Push(11); StackInt.Print(); */
/* Core: Example of a Generic Method Analysis: • The following code declares a generic method called ReverseAndPrint in a nongeneric class called Simple. The method takes as its parameter an array of any type. Main declares three different array types. It then calls the method twice with each array. The first time it calls the method with a particular array, it explicitly uses the type parameter. The second time, the type is inferred. Output: • 11,9,7,5,3, • 3,5,7,9,11, • third,second,first, • first,second,third, • 2.345,7.891,3.567, • 3.567,7.891,2.345, */ using System; namespaceAnonymous { classSimple//Non-generic class { staticpublicvoidReverseAndPrint<T>(T[] arr)//Generic method { Array.Reverse(arr); foreach(T item in arr) Console.Write($"{item.ToString()},"); Console.WriteLine(""); } } classProgram { staticvoidMain() { //Create arrays of various types. var intArray = newint[]{3,5,7,9,11}; var stringArray = newstring[]{"first","second","third"}; var doubleArray = newdouble[]{3.567,7.891,2.345};
Simple.ReverseAndPrint<int>(intArray);//Invoke method Simple.ReverseAndPrint(intArray);//Infer type and invoke
Simple.ReverseAndPrint<string>(stringArray); // Invoke method Simple.ReverseAndPrint(stringArray); // Infer type and invoke
Simple.ReverseAndPrint<double>(doubleArray); // Invoke method Simple.ReverseAndPrint(doubleArray); // Infer type and invoke } } }
/* Core: Using an Iterator to Create an Enumeratror Analysis: • Method BlackAndWhite is an iterator block that produces a method that returns an enumerator for class MyClass. • MyClass also implements method GetEnumerator, which just calls BlackAndWhite, and returns the enumerator that BlackAndWhite returns to it. • Notice that in Main, you can use an instance of the class directly in the foreach statement since the class implements GetEnumerator and is therefore enumerable. It doesn’t check for the interface—only the implementation of the interface. Output: • black • gray • white */ using System; using System.Collections.Generic; namespaceAnonymous { classMyClass { public IEnumerator<string> GetEnumerator() { return BlackAndWhite();//Returns the enumerator } public IEnumerator<string> BlackAndWhite()//Iterator { yieldreturn"black"; yieldreturn"gray"; yieldreturn"white"; } } classProgram { staticvoidMain() { MyClass mc = new MyClass(); foreach(string shade in mc) Console.WriteLine(shade); } } }
/* Core: Using an Iterator to Create an Enumerable Analysis: • In the previous example, iterator method BlackAndWhite returned an IEnumerator<string>, and MyClass implemented method GetEnumerator by returning the object created by BlackAndWhite. • In this example, the iterator method BlackAndWhite returns an IEnumerable<string> rather than an IEnumerator<string>. MyClass, therefore, implements its GetEnumerator method by first calling method BlackAndWhite to get the enumerable object and then calling that object’s GetEnumerator method and returning its results. • Notice that in the foreach statement in Main, you can either use an instance of the class or call BlackAndWhite directly since it returns an enumerable. Both ways are shown. Output: • black • gray • white • black • gray • white */ using System; using System.Collections.Generic; namespaceAnonymous { classMyClass { public IEnumerator<string> GetEnumerator() { IEnumerable<string> myEnumerable = BlackAndWhite();//Get enumerable return myEnumerable.GetEnumerator();//Get enumerator } public IEnumerable<string> BlackAndWhite()//Iterator { yieldreturn"black"; yieldreturn"gray"; yieldreturn"white"; } } classProgram { staticvoidMain() { MyClass mc = new MyClass(); foreach(string shade in mc) Console.WriteLine(shade);
foreach(string shade in mc.BlackAndWhite()) Console.WriteLine(shade); } } }
/* Core: The From Clause And The Join Clause Analysis: • The query finds the last names of all the students taking the history course. Output: • Student taking History: Carson • Student taking History: Fleming */ using System; using System.Linq; namespaceAnonymous { classProgram { publicclassStudent { publicint StID; publicstring LastName; } publicclassCourseStudent { publicstring CourseName; publicint StID; } static Student[] students = new Student[] { new Student {StID = 1,LastName = "Carson"}, new Student {StID = 2,LastName = "Klassen"}, new Student {StID = 3,LastName = "Fleming"}, }; static CourseStudent[] studentsInCourses = new CourseStudent[] { new CourseStudent {CourseName = "Art",StID = 1}, new CourseStudent {CourseName = "Art",StID = 2}, new CourseStudent {CourseName = "History",StID = 1}, new CourseStudent {CourseName = "History",StID = 3}, new CourseStudent {CourseName = "Physics",StID = 3}, }; staticvoidMain() { //Find the last names of the students taking history. var query = from s in students join c in studentsInCourses on s.StID equals c.StID where c.CourseName == "History" select s.LastName; //Display the names of the students taking history. foreach(var q in query) Console.WriteLine($"Student taking History: {q}"); } } }
/* Core: Example Using a Delegate Parameter Analysis: The following code declares method IsOdd, which takes a single parameter of type int and returns a bool value specifying whether the input parameter was odd. Method Main does the following: • Declares an array of ints as the data source. • Creates a delegate object called MyDel, of type Func<int, bool>, and it uses method IsOdd to initialize the delegate object. Notice that you don’t need to declare the Func delegate type because, as you saw, it’s already predefined in the .NET Framework. • Calls Count using the delegate object. Output: • Count of odd numbers: 4 */ using System; using System.Linq; namespaceAnonymous { classProgram { staticboolIsOdd(int x)//Method to be used by the delegate object { return x % 2 == 1;//Return true if x is odd } staticvoidMain() { int[] intArray = newint[]{3,4,5,6,7,9}; Func<int,bool> myDel = new Func<int,bool>(IsOdd);//Delegate object var countOdd = intArray.Count(myDel);//Use delegate Console.WriteLine($"Count of odd numbers: {countOdd}"); } } }
/* Core: Create,Saving,Loading,and Displaying an XML Document Analysis: The following code shows how simple it is to perform several of the important tasks required when working with XML. It starts by creating a simple XML tree consisting of a node called Employees, with two subnodes containing the names of two employees. Notice the following about the code: • The tree is created with a single statement that creates all the nested elements in place in the tree. This is called functional construction. • Each element is created in place using an object-creation expression, using the constructor of the type of the node. After creating the tree, the code saves it to a file called EmployeesFile.xml, using XDocument’s Save method. It then reads the XML tree back from the file using XDocument’s static Load method and assigns the tree to a new XDocument object. Finally, it uses WriteLine to display the structure of the tree held by the new XDocument object. Output: • <Employees> • <Name>Bob Smith</Name> • <Name>Sally Jones</Name> • </Employees> */ using System; using System.Xml.Linq; namespaceAnonymous { classProgram { staticvoidMain() { XDocument employees1 = new XDocument(//Create the XML document new XElement("Employees",//Create the root element new XElement("Name","Bob Smith"),//Create element new XElement("Name","Sally Jones")//Create element ) ); employees1.Save("EmployeesFile.xml");//Save to a file //Load the saved document into a new variable XDocument employees2 = XDocument.Load("EmployeesFile.xml"); Console.WriteLine(employees2);//Display document } } }
/* Core: Using LINQ Queries with LINQ to XML Analysis: You can combine the LINQ to XML API with LINQ query expressions to produce simple yet powerful XML tree searches. The following code creates a simple XML tree, displays it to the screen, and then saves it to a file called SimpleSample.xml. Although there’s nothing new in this code, we’ll use this XML tree in the following examples. Output: • first • third • • Name: first, color: red, size: small • Name: third, color: blue, size: large */ using System; using System.Xml.Linq; using System.Linq; namespaceAnonymous { classProgram { staticvoidMain() { XDocument xd1 = new XDocument( new XElement("MyElements", new XElement("first", new XAttribute("color","red"), new XAttribute("size","small")), new XElement("second", new XAttribute("color","red"), new XAttribute("size","medium")), new XElement("third", new XAttribute("color","blue"), new XAttribute("size","large")) ) ); xd1.Save("SimpleSample.xml");//Save XML tree XDocument xd2 = XDocument.Load("SimpleSample.xml");//Load the document XElement rt = xd2.Element("MyElements");//Get the root element var xyz = from e in rt.Elements()//Select elements whose name have 5 chars where e.Name.ToString().Length == 5 select e; foreach (XElement x in xyz)//Display the selected elements Console.WriteLine(x.Name.ToString()); Console.WriteLine(""); foreach (XElement x in xyz) Console.WriteLine("Name: {0},color: {1},size: {2}", x.Name, x.Attribute("color").Value, x.Attribute("size").Value);//Get the attribute->Get the attribute's value } } }
Chapter21: Introduction to Asynchhronous Programming
Waiting Synchronously for Tasks in the Calling Method
/* Core: Waiting Synchronously for Tasks in the Calling Method Analysis: We’ll start by looking at a simple program that has a method called DoRun, which calls an async method twice, getting back two Task<int> objects in return. The method then continues on its way, checking and printing out whether the tasks are completed. It then goes to the end of the method and waits on the Console.Read call before completing. The Console.Read method waits for a character received from the keyboard. We put this here because otherwise the main method would exit before the asynchronous task was finished. The program, as written, doesn’t use the wait methods, but it contains a commented section in the middle of DoRun that contains the wait code, which we’ll use shortly, to compare against the results of this version. examples. Output: Case 3: • Call 2 completed: 479 ms • Call 2 completed: 533 ms • Task 1: Finished • Task 2: Finished */ using System; using System.Threading; using System.Net; using System.Threading.Tasks; using System.Diagnostics; namespaceAnonymous { classMyDownloadingString { Stopwatch sw = new Stopwatch(); publicvoidDoRun() { sw.Start(); Task<int> t1 = CountCharactersAsync(1,"https://www.aye.ink"); Task<int> t2 = CountCharactersAsync(2,"https://scrazy.cn");
/* Core: Waiting Asynchronously for Tasks in the async Method Analysis: In the previous section, you learned how to wait synchronously for Task completion. Sometimes, however, in your async method, you will want to wait on Tasks as your await expression. This allows your async method to return to the calling method but allows the async method to wait for completion of one or all of a set of tasks. The calls that allow this are the Task.WhenAll and Task.WhenAny methods. These methods are called combinators. The following code shows an example of using the Task.WhenAll method. This method waits asynchronously, without requiring time on the main thread, until all the Tasks associated with it are completed. Notice that the await expression’s task is the Task.WhenAll call. examples. Output: Case 2: • DoRun: Task Not Finished • CCA: T1 Finished • CCA: T2 Not Finished • DoRun: Result = 14387 */ using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks;
Although the nested namespace is inside the enclosing namespace, its members are not members of the enclosing namespace. A common misconception is that since the nested namespace is inside the enclosing namespace, the members of the nested namespace must be a subset of the enclosing namespace.This is not true; the namespaces are separate.
/* Core: Searching Down the Call Stack Analysis: 1. Main calls A, which calls B, which encounters a DivideByZeroException exception. 2. The system checks B’s catch section for a matching catch clause. Although it has one for IndexOutOfRangeException, it doesn’t have one for DivideByZeroException. 3. The system then moves down the call stack and checks A’s catch section, where it finds that A also doesn’t have a matching catch clause. 4. The system continues down the call stack and checks Main’s catch clause section, where it finds that Main does have a DivideByZeroException catch clause. 5. Although the matching catch clause has now been located, it is not executed yet. Instead, the system goes back to the top of the stack, executes B’s finally clause, and pops B from the call stack. 6. The system then moves to A, executes its finally clause, and pops A from the call stack. 7. Finally, Main’s matching catch clause is executed, followed by its finally clause. Execution then continues after the end of Main’s try statement Output: • finally clause in B() • finally clause in A() • catch clause in Main() • finally clause in Main() • After try statement in Main. • -- Keep running. */ using System; //using System.Text; namespaceAnonymous { classMyClass { publicvoidA() { try { B(); } catch(NullReferenceException) { Console.WriteLine("catch clause in A()"); } finally { Console.WriteLine("finally clause in A()"); } } voidB() { int x = 10,y = 0; try { x /= y; } catch(IndexOutOfRangeException) { Console.WriteLine("catch clause in B()"); } finally { Console.WriteLine("finally clause in B()"); } } } classProgram { staticvoidMain() { MyClass MCls = new MyClass(); try { MCls.A(); } //这边可以改成这个catch(IndexOutOfRangeException),感受一下区别,加深印象 catch(DivideByZeroException) { Console.WriteLine("catch clause in Main()"); } finally { Console.WriteLine("finally clause in Main()"); } Console.WriteLine("After try statement in Main."); Console.WriteLine("-- Keep running."); } } }
/* Core: Throwing Exceptions Analysis: For example, the following code defines a method called PrintArg, which takes a string argument and prints it out. Inside the try block, it first checks to make sure the argument is not null. If it is, it creates an ArgumentNullException instance and throws it. The exception instance is caught in the catch statement, and the error message is printed. Main calls the method twice: once with a null argument and then with a valid argument. Output: • Message: Value cannot be null. (Parameter 'arg') • Hi there! */ using System; //using System.Text; namespaceAnonymous { classMyClass { publicstaticvoidPrintArg(string arg) { try { if(arg == null) { ArgumentNullException myEx = new ArgumentNullException("arg"); throw myEx; } Console.WriteLine(arg); } catch (ArgumentNullException e) { Console.WriteLine($"Message: {e.Message}"); } } } classProgram { staticvoidMain() { string s = null; MyClass.PrintArg(s); MyClass.PrintArg("Hi there!"); } } }
/* Core: The Conditional Compilation Construsts Analysis: The following code demonstrates the #if...#elif...#else construct. The string containing the description of the version of the program is set to various values, depending on which compilation symbol is defined. Output: • Demo Version of Supergame Plus */ #define DemoVersionWithoutTimeLimit #pragmawarning disable//禁止输出所有警告 using System; namespaceAnonymous { classdemo { staticvoidMain() { constint intExpireLength = 30; string strVersionDesc = null; int intExpireCount = 0; #if DemoVersionWithTimeLimit intExpireCount = intExpireLength; strVersionDesc = "This version of Supergame Plus will expire in 30 days"; #elif DemoVersionWithoutTimeLimit strVersionDesc = "Demo Version of Supergame Plus"; #elif OEMVersion strVersionDesc = "Supergame Plus,distributed under license"; #else strVersionDesc = "The original Superagame Plus!!"; #endif Console.WriteLine(strVersionDesc); } } }
/* Core: TryParse Analysis: The important things to know about TryParse are the following: • Every built-in type that has a Parse method also has a TryParse method. • The TryParse method takes two parameters and returns a bool. − The first parameter is the string you’re trying to parse. − The second is an out parameter of a reference to a variable of the target type. − If TryParse succeeds, the parsed value is assigned to the out parameter, and it returns true. Otherwise, it returns false. Output: • String 28 was successfully parsed • String vt750 was not successfully parsed */ using System; //using System.Text; namespaceAnonymous { classProgram { staticvoidMain() { string parseResultSummary; string stringFirst = "28"; int intFirst; bool success = int.TryParse(stringFirst,out intFirst); parseResultSummary = success ? "was successfully parsed" : "was not successfully parsed"; Console.WriteLine($"String {stringFirst}{parseResultSummary}");