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.

Chapter 5: Classes:The Basics

Access The Class Members
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
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;

namespace Anonymous
{
class DaysTemp //Declare the class
{
public int High,Low; //Declare the instance fields
public int Average() //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
}
}
class Program
{
static void Main(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()}");
}
}
}

Chapter 6: Methods

Value Parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
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;

namespace Anonymous
{
class MyClass
{
public int Val = 20; // Initialize the field to 20.
}
class Program
{
static void MyMethod( 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 }");
}
static void Main()
{
MyClass a1 = new MyClass();
int a2 = 10;
MyMethod( a1, a2 ); // Call the method.
Console.WriteLine($"a1.Val: { a1.Val }, a2: { a2 }");
}
}
}
Reference Parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
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;

namespace Anonymous
{
class MyClass
{
public int Val = 20; // Initialize the field to 20.
}
class Program
{
static void MyMethod(ref MyClass f1,ref 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 }");
}
static void Main()
{
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
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;

namespace Anonymous
{
class MyClass
{
public int Val = 20; // Initialize the field to 20.
}
class Program
{
static void MyMethod(out MyClass f1,out int f2)
{
f1 = new MyClass();
f1.Val = 25;
f2 = 15;
}
static void Main( )
{
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}");
}
}
}
Reference Types As Value Parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
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;

namespace Anonymous
{
class MyClass
{
public int Val = 20; // Initialize the field to 20.
}
class Program
{
static void RefAsParameter( MyClass f1 )
{
f1.Val = 50;
Console.WriteLine($"After member assignment: { f1.Val }");
f1 = new MyClass();
Console.WriteLine($"After new object creation: { f1.Val }");
}
static void Main( )
{
MyClass a1 = new MyClass();
Console.WriteLine($"Before method call: { a1.Val }");
RefAsParameter( a1 );
Console.WriteLine($"After method call: { a1.Val }");
}
}
}
Reference Types As Reference Parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
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;

namespace Anonymous
{
class MyClass
{
public int Val = 20; // Initialize the field to 20.
}
class Program
{
static void RefAsParameter( ref MyClass f1 )
{
f1.Val = 50;
Console.WriteLine($"After member assignment: { f1.Val }");
f1 = new MyClass();
Console.WriteLine($"After new object creation: { f1.Val }");
}
static void Main( )
{
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
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;

namespace Anonymous
{
class C1
{
private int _myValue = 10;//Backing Field:memory allocated
public int MyValue//Property:no memory allocated
{
set{_myValue = value;}//Sets the value of field theRealValue
get{return _myValue;}//Gets the value of the field
}
}
class Program
{

static void Main( )
{
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);
}
}
}
Static Properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
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;
using static Anonymous.Trivial;
namespace Anonymous
{
class Trivial
{
public static int MyValue
{
get;//auto-properties
set;//auto-properties
}
public void PrintValue()
{
Console.WriteLine("Value from inside:{0}",MyValue);
}
}
class Program
{

static void Main( )
{
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
}
}
}

Static Constructor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
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;
namespace Anonymous
{
class RandomNumberClass
{
private static Random RandomKey; // Private static field
static RandomNumberClass() // Static constructor
{
RandomKey = new Random(); // Initialize RandomKey
}
public int GetRandomNumber()
{
return RandomKey.Next();
}
}
class Program
{
static void Main()
{
RandomNumberClass a = new RandomNumberClass();
RandomNumberClass b = new RandomNumberClass();
Console.WriteLine("Next Random #: {0}", a.GetRandomNumber());
Console.WriteLine($"Next Random #: { b.GetRandomNumber() }");
}
}
}
Declare an indexer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
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;
namespace Anonymous
{
class Employee
{
private string LastName;
private string FirstName;
private string CityOfBirth;
public string this[int index]
{
set
{
switch(index)
{
case 0:LastName = value;break;
case 1:FirstName = value;break;
case 2:CityOfBirth = value;break;
default: throw new ArgumentOutOfRangeException("index");
}
}
get
{
switch(index)
{
case 0: return LastName;
case 1:return FirstName;
case 2:return CityOfBirth;
default:throw new ArgumentOutOfRangeException("index");
}
}
}
}
class Program
{
static void Main()
{
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
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;
namespace Anonymous
{
class SomeClass
{
public string Field1 = "Field1 -- In the base class";
public void PrintField1()
{
Console.WriteLine(Field1);
}
}
class OtherClass : SomeClass
{
new public string Field1 = "Field1 -- In the derived class";
new public void PrintField1()
{
Console.WriteLine(Field1);
}
public void PrintBaseField1()
{
Console.WriteLine(base.Field1);//To an elegant program,wo need to use 'base' less.
}
}
class Program
{
static void Main()
{
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')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*
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;
namespace Anonymous
{
class MyBaseClass
{
virtual public void Print()
{
Console.WriteLine("This is the base class");
}
}
class MyDerivedClass : MyBaseClass
{
override public void Print()
{
Console.WriteLine("This is the derived class");
}
}
class SecondDerived1 : MyDerivedClass
{
override public void Print()
{
Console.WriteLine("Override:This is the second derived class");
}
}
class SecondDerived2 : MyDerivedClass
{
new public void Print()
{
Console.WriteLine("New:This is the second derived class");
}
}
class Program
{
static void Main()
{
SecondDerived1 derived1 = new SecondDerived1();
MyBaseClass mybc1 = (MyBaseClass)derived1;

derived1.Print();
mybc1.Print();
/*********************************************/
SecondDerived2 derived2 = new SecondDerived2();
MyBaseClass mybc2 = (MyBaseClass)derived2;

derived2.Print();
mybc2.Print();
}
}
}
Abstract Class And Abstract Method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
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;
namespace Anonymous
{
abstract class MyBase
{
public int SideLength = 10;//The data member-fields and constants
const int TriangleSideCount = 3;//-cannot be declared as abstract
abstract public void PrintStuff(string s);//Abstact method:It must not have an implementation code block
abstract public int MyInt{get;set;}//Abstract property
public int PerimeterLength()
{
return TriangleSideCount * SideLength;
}
}
class MyClass : MyBase
{
public override void PrintStuff(string s)//Override abstract method
{
Console.WriteLine(s);
}
private int _myInt;
public override int MyInt//Override abstract property
{
get
{
return _myInt;
}
set
{
_myInt = value;
}
}
}
class Program
{
static void Main(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
}
}
}

Chapter9: Expressions and Operators

User-Defined Type Conversions

1.With an implicit conversion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
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;
namespace Anonymous
{
class LimitedInt
{
const int MaxValue = 100;
const int MinValue = 0;
private int mTheValue = 0;
public int TheValue//Property:Convert rules
{
get
{
return mTheValue;
}
set
{
if(value < MinValue)
mTheValue = 0;
else
mTheValue = value > MaxValue
? MaxValue
: value;
}
}
public static implicit operator int(LimitedInt li)//Convert to int type
{
return li.TheValue;
}
public static implicit operator LimitedInt(int x)//Convert to LimitedInt type
{
LimitedInt li = new LimitedInt();
li.TheValue = x;
return li;
}
}

class Program
{
static void Main(string[] args)
{
LimitedInt li = 500;//Convert 500 to LimitedInt
int value = li;//Convert LImitedInt to int
Console.WriteLine($"li:{li.TheValue},value:{value}");
}
}
}

2.With an explicit conversion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
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;
namespace Anonymous
{
class LimitedInt
{
const int MaxValue = 100;
const int MinValue = 0;
private int mTheValue = 0;
public int TheValue//Property:Convert rules
{
get
{
return mTheValue;
}
set
{
if(value < MinValue)
mTheValue = 0;
else
mTheValue = value > MaxValue
? MaxValue
: value;
}
}
public static explicit operator int(LimitedInt li)//Convert to int type
{
return li.TheValue;
}
public static explicit operator LimitedInt(int x)//Convert to LimitedInt type
{
LimitedInt li = new LimitedInt();
li.TheValue = x;
return li;
}
}

class Program
{
static void Main(string[] args)
{
LimitedInt li = (LimitedInt)500;//Convert 500 to LimitedInt
int value = (int)li;//Convert LImitedInt to int
Console.WriteLine($"li:{li.TheValue},value:{value}");
}
}
}
Operator Overloading
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*
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;
namespace Anonymous
{
class LimitedInt
{
const int MaxValue = 100;
const int MinValue = 0;
public static 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;
}
public static LimitedInt operator -(LimitedInt x,LimitedInt y)
{
LimitedInt li = new LimitedInt();
li.TheValue = x.TheValue - y.TheValue;
return li;
}
public static LimitedInt operator +(LimitedInt x,double y)
{
LimitedInt li = new LimitedInt();
li.TheValue = x.TheValue + (int)y;
return li;
}
private int _theValue = 0;
public int 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;
}
}
}

class Program
{
static void Main(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}");

li3 = -li1;
Console.WriteLine($"li3:{li3.TheValue}");

li3 = li2 - li1;//Properity:larger than 0 lower than 100 set the real value
Console.WriteLine($"li3:{li3.TheValue}");

li3 = li1 - li2;//Properity:lower than 0 set 0
Console.WriteLine($"li3:{li3.TheValue}");

li3 = li1 + plusnumber;//Properity:larger than 0 lower than 100 set the real value
Console.WriteLine($"li3:{li3.TheValue}");


}
}
}

Chapter10: Statements

This chapter only records the knowledge points that are difficult to understand and seldom used at ordinary times.

The Using Statement

1.ONE FORM:
using(ResType Resource = new ResType(...)) Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
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;
namespace Anonymous
{
class Program
{
static void Main()
{
//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.

Chapter11: Structs

Differece Between Struct And Class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*
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;
namespace Anonymous
{
class CSimple
{
public int X,Y;
}
struct Simple
{
public int X,Y;
}
class Program
{
static void Main()
{
/*
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}");

cs1.X = ss1.X = 5;
cs1.Y = ss1.Y = 10;
Console.WriteLine($"Assign Value:ss1.X:{ss1.X},ss1.Y:{ss1.Y},cs1.X:{cs1.X},cs1.Y:{cs1.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}");
}
}
}

Chapter12: Enumerations

Bit Flags
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
Core: Using Bit Flags
Analysis:
• If somewhere do not understand,read the book Chapter Enum
Output:
• Option settings:
• Use Single Deck - True
• Use Large Pictures - False
• Use Fancy Numbers - True
• Use Animation - True
• Use Animation and FancyNumbers - False
*/
using System; //Using directive,not using statement;
using System.IO;
namespace Anonymous
{
[Flags]
enum CardDeckSettings:uint
{
SingleDeck = 0x01, //bit 0
LargePictures = 0x02,//bit 1
FancyNumbers = 0x04,//bit 2
Animation = 0x08 //bit 3
}
class MyClass
{
bool UseSingleDeck = false,
UseLargePictures = false,
UseFancyNumbers = false,
UseAnimation = false,
UseAnimationAndFancyNumbers = false;
public void SetOptions(CardDeckSettings ops)
{
UseSingleDeck = ops.HasFlag(CardDeckSettings.SingleDeck);
UseLargePictures = ops.HasFlag(CardDeckSettings.LargePictures);
UseFancyNumbers = ops.HasFlag(CardDeckSettings.FancyNumbers);
UseAnimation = ops.HasFlag(CardDeckSettings.Animation);
CardDeckSettings testFlags = CardDeckSettings.Animation | CardDeckSettings.FancyNumbers;
UseAnimationAndFancyNumbers = ops.HasFlag(testFlags);
}
public void PrintOptions()
{
Console.WriteLine("Option settings:");
Console.WriteLine($"Use Single Deck - {UseSingleDeck}");
Console.WriteLine($"Use Large Pictures - {UseLargePictures}");
Console.WriteLine($"Use Fancy Numbers - {UseFancyNumbers}");
Console.WriteLine($"Use Animation - {UseAnimation}");
Console.WriteLine($"Use Animation and FancyNumbers - {UseAnimationAndFancyNumbers}");
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
CardDeckSettings ops = CardDeckSettings.SingleDeck | CardDeckSettings.FancyNumbers;
mc.SetOptions(ops);
mc.PrintOptions();

}
}
}

Chapter13: Arrays

Subarrays in Jagged Arrays

Since the subarrays in a jagged array are themselves arrays, it’s possible to have rectangular arrays inside jagged arrays.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
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;
namespace Anonymous
{

class Program
{
static void Main()
{
int[][,] Arr; //Declare an array of 2D arrays
Arr = new int[3][,];//Instantiate an array of three 2D arrays
Arr[0]= new int[,]{{10,20},{100,200}};
Arr[1]= new int[,]{{30,40,50},{300,400,500}};
Arr[2]= new int[,]{{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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
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;
namespace Anonymous
{
//Define a delegate type with no return value and no parameters.
delegate void PrintFunction();
class Test
{
public void Print1()
{
Console.WriteLine("Print1 -- instance");
}
public static void Print2()
{
Console.WriteLine("Print2 -- static")
}
}
class Program
{
static void Main()
{
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");

}
}
}

Chapter15: Events

The Usage Of Event
  • Nonstandard Event Usage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
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;
namespace Anonymous
{
delegate void Handler(); //1.Delegate Type Declaration

/*****Pbulisher******/
class Incrementer
{
public event Handler CountedADozen;//2.Event Declaration:Create and publish an event
public void DoCount()
{
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*****/
class Dozens
{
public int DozensCount{get;private set;}
public Dozens(Incrementer incrementer)
{
DozensCount = 0;
incrementer.CountedADozen += IncrementDozensCount;//4.Subscribe to the event
}
void IncrementDozensCount()//5.Declare the event handler
{
DozensCount++;
}
}
/*****Main Program*****/
class Program
{
static void Main()
{
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*
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;
namespace Anonymous
{
//delegate void Handler(); //No more need,because we use the system-defined EventHandler delegate

/*****Pbulisher******/
class Incrementer
{
public event EventHandler CountedADozen;//2.Event Declaration:Create and publish an event
public void DoCount()
{
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*****/
class Dozens
{
public int DozensCount{get;private set;}
public Dozens(Incrementer incrementer)
{
DozensCount = 0;
incrementer.CountedADozen += IncrementDozensCount;//4.Subscribe to the event
}
void IncrementDozensCount(object source,EventArgs e)//5.Declare the event handler:The signature of the event handler must match that of the delegate
{
DozensCount++;
}
}
/*****Main Program*****/
class Program
{
static void Main()
{
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);
}
}
}
Passing Data by Extending EventArgs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
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;
namespace Anonymous
{
public class IncrementerEventArgs : EventArgs//Custom class derived from EventArgs
{
public int IterationCount{get;set;}//Stores an integer
}
class Incrementer
{
public event EventHandler<IncrementerEventArgs> CountedADozen;//Generic delegate using custom class
public void DoCount()
{
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
}
}
}
class Dozens
{
public int DozensCount{get;private set;}
public Dozens(Incrementer incrementer)
{
DozensCount = 0;
incrementer.CountedADozen += IncrementDozensCount;
}
void IncrementDozensCount(object source,IncrementerEventArgs e)
{
Console.WriteLine($"Incremented at iteration:{e.IterationCount} in {source.ToString()}");
DozensCount++;
}
}
class Program
{
static void Main()
{
Incrementer incrementer = new Incrementer();
Dozens dozensCounter = new Dozens(incrementer);
incrementer.DoCount();
Console.WriteLine($"Number of dozens = {dozensCounter.DozensCount}");
}
}

}

Chapter16: Interfaces

An Interface Is a Reference Type
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
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;
namespace Anonymous
{
interface IIfc1
{
void PrintOut(string s);
}
class MyClass : IIfc1
{
public void PrintOut(string s)
{
Console.WriteLine($"Calling through: {s}");
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
mc.PrintOut("object");

IIfc1 ifc = (IIfc1)mc;
ifc.PrintOut("interface");

IIfc1 ift = mc as IIfc1;
ift.PrintOut("interface");
}
}
}
Different Classes Implementing an Interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*
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;
namespace Anonymous
{
interface ILiveBirth//Declare interface
{
string BabyCalled();
}
class Animal//Base class Animal
{

}
class Cat : Animal, ILiveBirth//Declare class Cat
{
string ILiveBirth.BabyCalled()
{
return "kitten";
}
}
class Dog : Animal, ILiveBirth//Declare class Dog
{
string ILiveBirth.BabyCalled()
{
return "puppy";
}
}
class Bird : Animal//Declare class Bird
{

}
class Program
{
static void Main()
{
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.

A User-Defined Conversion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
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;
namespace Anonymous
{
class Person
{
public string Name;
public int Age;
public Person(string name,int age)
{
Name = name;
Age = age;
}
public static implicit operator int(Person p)//Convert Person to int
{
return p.Age;
}
public static implicit operator Person(int i)//Convert int to Person
{
return new Person("Nemo",i);
}
}
class Program
{
static void Main()
{
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}");
}
}
}

Chapter18: Generics

Using Generics
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/*
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;
namespace Anonymous
{
class MyStack<T>//泛型
{
T[] StackArray;
int StackPointer = 0;
public void Push(T x)//进栈方法
{
if(!IsStackFull)
StackArray[StackPointer++] = x;
}
public T Pop()//出栈方法
{
return (!IsStackEmpty)
? StackArray[--StackPointer]
: StackArray[0];
}
const int MaxStack = 10;//这个数组最大容纳是个元素
bool IsStackFull {get{return StackPointer >= MaxStack;}}//大于等于十个元素,说明数组被存满了
bool IsStackEmpty {get{return StackPointer <= 0;}}//小于等于零个元素,说明数组被清空了
public MyStack()
{
StackArray = new T[MaxStack];//实例化数组对象
}
public void Print()//打印方法
{
for(int i = StackPointer-1;i >= 0;i--)
Console.WriteLine($"Value: {StackArray[i]}");
}
}
class Program
{
static void Main()
{
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();
*/

/*
//Example two:发现当数组里面元素小于等于0时,确实啥也没有打印出来
StackInt.Print();
*/
/*
//Example three:发现出栈原则是先进后出,后进先出
StackInt.Push(1);
StackInt.Push(2);
StackInt.Push(3);
StackInt.Pop();
StackInt.Print();
*/

StackInt.Push(3);
StackInt.Push(5);
StackInt.Push(7);
StackInt.Print();

StackString.Push("This is fun");
StackString.Push("Hi there");
StackString.Print();
}
}
}
A Generic Method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
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;
namespace Anonymous
{
class Simple//Non-generic class
{
static public void ReverseAndPrint<T>(T[] arr)//Generic method
{
Array.Reverse(arr);
foreach(T item in arr)
Console.Write($"{item.ToString()},");
Console.WriteLine("");
}
}
class Program
{
static void Main()
{
//Create arrays of various types.
var intArray = new int[]{3,5,7,9,11};
var stringArray = new string[]{"first","second","third"};
var doubleArray = new double[]{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
}
}
}
Covariance and Contravariance

参考:.NET 4.0中的泛型协变和反变

Chapter19: Enumerations and Iterators

Using an Iterator to Create an Enumeratror
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
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;
namespace Anonymous
{
class MyClass
{
public IEnumerator<string> GetEnumerator()
{
return BlackAndWhite();//Returns the enumerator
}
public IEnumerator<string> BlackAndWhite()//Iterator
{
yield return "black";
yield return "gray";
yield return "white";
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
foreach(string shade in mc)
Console.WriteLine(shade);
}
}
}
Using an Iterator to Create an Enumerable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
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;
namespace Anonymous
{
class MyClass
{
public IEnumerator<string> GetEnumerator()
{
IEnumerable<string> myEnumerable = BlackAndWhite();//Get enumerable
return myEnumerable.GetEnumerator();//Get enumerator
}
public IEnumerable<string> BlackAndWhite()//Iterator
{
yield return "black";
yield return "gray";
yield return "white";
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
foreach(string shade in mc)
Console.WriteLine(shade);

foreach(string shade in mc.BlackAndWhite())
Console.WriteLine(shade);
}
}
}

Chapter20: Introduction to LINQ

The From Clause And The Join Clause
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
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;
namespace Anonymous
{
class Program
{
public class Student
{
public int StID;
public string LastName;
}
public class CourseStudent
{
public string CourseName;
public int 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},
};
static void Main()
{
//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}");
}
}
}
Using a Delegate Parameter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
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;
namespace Anonymous
{
class Program
{
static bool IsOdd(int x)//Method to be used by the delegate object
{
return x % 2 == 1;//Return true if x is odd
}
static void Main()
{
int[] intArray = new int[]{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}");
}
}
}
Using a lambda Expression Parameter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
Core: Example Using a lambda Expression Parameter
Analysis:
Output:
• Count of odd numbers: 4
*/
using System;
using System.Linq;
namespace Anonymous
{
class Program
{
static void Main()
{
int[] intArray = new int[]{3,4,5,6,7,9};
var countOdd = intArray.Count(x => x % 2 == 1);//Lambda expression
Console.WriteLine($"Count of odd numbers: {countOdd}");
}
}
}
Using a Anonymous Method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
Core: Example Using a Anonymous Method
Analysis:
Output:
• Count of odd numbers: 4
*/
using System;
using System.Linq;
namespace Anonymous
{
class Program
{
static void Main()
{
int[] intArray = new int[]{3,4,5,6,7,9};
Func<int,bool> myDel = delegate(int x)
{
return x % 2 == 1;
};
var countOdd = intArray.Count(myDel);//Lambda expression
Console.WriteLine($"Count of odd numbers: {countOdd}");
}
}
}
Create,Saving,Loading,and Displaying an XML Document
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
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;
namespace Anonymous
{
class Program
{
static void Main()
{
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
}
}
}
Using LINQ Queries with LINQ to XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
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;
namespace Anonymous
{
class Program
{
static void Main()
{
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
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;
namespace Anonymous
{
class MyDownloadingString
{
Stopwatch sw = new Stopwatch();
public void DoRun()
{
sw.Start();
Task<int> t1 = CountCharactersAsync(1,"https://www.aye.ink");
Task<int> t2 = CountCharactersAsync(2,"https://scrazy.cn");

//wait nothing,run immediately // Case 1
Task.WaitAll(t1,t2);//Case 2
//Task.WaitAny(t1,t2);//Case 3
Console.WriteLine("Task 1: {0}Finished",t1.IsCompleted ? "" : "Not ");
Console.WriteLine("Task 2: {0}Finished",t2.IsCompleted ? "" : "Not ");

Console.Read();//防止异步方法完成后Main就退出了
}
private async Task<int> CountCharactersAsync(int id,string site)
{
WebClient wc = new WebClient();
string result = await wc.DownloadStringTaskAsync(new Uri(site));
Console.WriteLine("Call {0} completed: {1,4:N0} ms",id,sw.Elapsed.TotalMilliseconds);
return result.Length;
}
}
class Program
{
static void Main()
{
MyDownloadingString ds = new MyDownloadingString();
ds.DoRun();
}
}
}
Waiting Asynchronously for Tasks in the async Method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
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;

namespace Anonymous
{
class MyDownloadingString
{
public void DoRun()
{
Task<int> t = CountCharactersAsync("https://www.baidu.com","https://www.google.com");
Console.WriteLine("DoRun: Task {0}Finished",t.IsCompleted ? "" : "Not ");
Console.WriteLine("DoRun: Result = {0}",t.Result);
}
private async Task<int> CountCharactersAsync(string site1,string site2)
{
WebClient wc1 = new WebClient();
WebClient wc2 = new WebClient();
Task<string> t1 = wc1.DownloadStringTaskAsync(new Uri(site1));
Task<string> t2 = wc2.DownloadStringTaskAsync(new Uri(site2));
List<Task<string>> tasks = new List<Task<string>>();
tasks.Add(t1);
tasks.Add(t2);
//await Task.WhenAll(tasks);//Case 1
await Task.WhenAny(tasks);//Case 2
Console.WriteLine("CCA: T1 {0}Finished",t1.IsCompleted ? "" : "Not ");
Console.WriteLine("CCA: T2 {0}Finished",t2.IsCompleted ? "" : "Not ");
return t1.IsCompleted ? t1.Result.Length : t2.Result.Length;
}
}
class Program
{
static void Main()
{
MyDownloadingString ds = new MyDownloadingString();
ds.DoRun();
}
}
}

Chapter22: Namespaces and Assemblies

这章仅供参考了解

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

Chapter23: Exceptions

Searching Down the Call Stack
  • 弄清楚try-catch-finally处理意外发生的几种使用方法
  • 以及处理异常的一般逻辑,可以从下面的代码中感受到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/*
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;
namespace Anonymous
{
class MyClass
{
public void A()
{
try
{
B();
}
catch(NullReferenceException)
{
Console.WriteLine("catch clause in A()");
}
finally
{
Console.WriteLine("finally clause in A()");
}
}
void B()
{
int x = 10,y = 0;
try
{
x /= y;
}
catch(IndexOutOfRangeException)
{
Console.WriteLine("catch clause in B()");
}
finally
{
Console.WriteLine("finally clause in B()");
}
}
}
class Program
{
static void Main()
{
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.");
}
}
}
Throwing Exceptions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
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;
namespace Anonymous
{
class MyClass
{
public static void PrintArg(string arg)
{
try
{
if(arg == null)
{
ArgumentNullException myEx = new ArgumentNullException("arg");
throw myEx;
}
Console.WriteLine(arg);
}
catch (ArgumentNullException e)
{
Console.WriteLine($"Message: {e.Message}");
}
}
}
class Program
{
static void Main()
{
string s = null;
MyClass.PrintArg(s);
MyClass.PrintArg("Hi there!");
}
}
}

Chapter24: Preprocessor Directives

The Conditional Compilation Construsts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
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
#pragma warning disable//禁止输出所有警告
using System;
namespace Anonymous
{
class demo
{
static void Main()
{
const int 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);
}
}
}

Chapter25: Reflection and Attributes

学会使用一些常见的.net内置的Attributes并且能够自定义Attributes

Chapter26: What’s New in C# 6 and 7s

这章仅供参考了解

Chapter27: Other Topics

Parsing Strings to Data Value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
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;
namespace Anonymous
{
class Program
{
static void Main()
{
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}");

string stringSecond = "vt750";
int intSecond;
success = int.TryParse(stringSecond,out intSecond);
parseResultSummary = success ? "was successfully parsed" : "was not successfully parsed";
Console.WriteLine($"String {stringSecond} {parseResultSummary}");
}
}
}
Method Main

只有以下四种形式为合法的主程序入口:

  • static void Main() {…}
  • static void Main( string[] args) {…}
  • static int Main() {…}
  • static int Main( string[] args) {…}
More

还有一些比较高级的操作内存回收,COM等以后进一步学习。