这两章内容比较多也很重要,但写的时候似乎精力有些不如前面了,后面可能会回来再补充,因为我读书也是第一次学,应该存在不少不足之处,后续也许会慢慢完善的。

9 Using Classes and Objects

9.1 Understanding Class Concepts

When you write programs in C#, you create two types of classes:

  1. Classes that are application programs with a Main() method. These classes can contain other methods that the Main() method calls, but these classes are not used as blueprints for objects.
  2. Classes from which you instantiate objects. To instantiate an object is to create a tangible example. An object is an instantiation of a class or an instance of a class. A class that is
    created to be a model for object instantiations can contain a Main() method, but it is not required.

A data component of a class that differs for each object is stored in an instance variable. An instance variable also is called a field(字段) to help distinguish it from other variables you might use.
The data field values of an object are also called its attributes and the set of all an object’s attributes is known as the object’s state.
这一节介绍的好像比较简略,详见第一章吧

9.2 Creating a Class from Which Objects Can Be Instantiated

When you create a class, you must assign a name to it and determine what data and methods will be part of the class.

字段的修饰符有new, public, protected, internal, private, static, readonly, volatile,默认是private和nonstatic

Using private fields within classes is an example of information hiding, a feature found in all object-oriented languages.The private data of a class should be changed or manipulated only by its own methods, not by methods that belong to other classes.

In contrast to a class’s private data fields, most instance methods are not usually private; they are public. The resulting private data/public method arrangement provides a means to control outside access to your data—only a class’s nonprivate methods can be used to access a class’s private data.

1
2
3
4
5
6
7
8
class Employee
{
private int idNumber;
public void WelcomeMessage()
{
WriteLine("Welcome from Employee #{0}", idNumber);
}
}

A class can contain fields that are objects of other classes. For example, you might create a class named Date that contains a month, day, and year and add two Date fields to an Employee class to hold the Employee’s birth date and hire date. You might also create a BusinessOffice class and declare Employee objects within it. Using an object as a field within another object is known as composition. The relationship created is also called a has-a relationship because one object “has an” instance of another.

9.3 Creating Objects

Every object name is a reference—that is, it holds a computer memory location where the fields for the object reside. The memory location holds the values for the object.

  • When you declare an integer as int myInteger;, you notify the compiler that an integer named myInteger will exist, and computer memory automatically is reserved for it at the same time—the exact amount of computer memory depends on the declared data type.
  • When you declare the myAssistant instance of the Employee class, you are notifying the compiler that you will use the identifier myAssistant. However, you are not yet setting aside computer memory in which the Employee named myAssistant can be stored—that is done only for the built-in, predefined types. To allocate the needed memory for the object, you must use the new operator. You used the new operator earlier in this course when you learned to set aside memory for an array.

Because the identifiers for objects are references to their memory addresses, you can call any class a reference type—in other words, a type that refers to a specific memory location. A reference type is a type that holds an address, as opposed to the predefined types such as int, double, and char, each of which is a value type and holds a value as opposed to an address.

人们普遍认为value types不是对象,而reference types是
数据类型也是类

1
2
3
int x = new int(); // 但会被初始化为0
// 等价于
int x; // 不会被初始化

int()是constructor(构造函数)
A constructor is a method that instantiates (creates an instance of) an object. 它的名字和类一样

After an object has been instantiated, its nonstatic public members can be accessed using the object’s identifier, a dot, and the nonstatic public member’s identifier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using static System.Console;
class Employee
{
private int idNumber;
public void WelcomeMessage()
{
WriteLine("Welcome from Employee #{0}", idNumber);
}
}

class CreateEmployee
{
static void Main()
{
Employee myAssistant = new Employee();
myAssistant.WelcomeMessage();
}
}

9.3.1 Passing Objects to Methods

A class represents a data type, and you can pass objects to methods just as you can pass variables that are simple data types.

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
using static System.Console;
class Employee
{
private int idNumber;
public void WelcomeMessage()
{
WriteLine("Welcome from Employee #{0}", idNumber);
}
}

class CreateTwoEmployees
{
static void Main()
{
Employee aWorker = new Employee();
Employee anotherWorker = new Employee();
DisplayEmployeeData("First", aWorker);
DisplayEmployeeData("Second", anotherWorker);
}
static void DisplayEmployeeData(string order,
Employee emp)
{
WriteLine("\n{0} employee’s message:", order);
emp.WelcomeMessage();
}
}

When you pass an object to a method, you pass a reference. Therefore, any change made to an object parameter in a method also affects the object used as an argument in the calling method.

Because the method accepts an Employee parameter, it must be no more accessible than the Employee class, which is also internal. This restriction preserves the security provided for the Employee class’s nonpublic members.

9.4 Creating Properties

Frequently, methods you call with an object are used to alter the states of its fields. In the Employee class, you might need to change the value for an ID number.

1
2
3
4
public void SetIdNumber(int number)
{
idNumber = number;
}

创建properties就是做类似的工作的

A property is a member of a class that provides access to a field of a class; properties define how fields will be set and retrieved(检索). C# programmers refer to properties as smart fields because they provide a combination of the best features of private fields and public methods:

  • Like public methods, properties protect private data from outside access.
  • Like fields, property names are used in the same way simple variable names are used. When you create properties, the syntax in your client programs becomes more natural and easier to understand.

Properties can have accessor methods that specify the statements that execute when a class’s fields are accessed. Specifically, a property can contain a set accessor for setting an object’s fields and a get accessor for retrieving the stored value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Employee
{
priavte int idNumber;
public int IdNumber
{
get
{
return idNumber;
}
set
{
idNumber = value;
}
}
public void WelcomeMessage()
{
WriteLine("Welcome from Employee #{0}", IdNumber);
// 注意这里用的是property IdNumber
}
}

这里idNumber称为IdNumber的backing field
如果一个property有set accessor,称它可以被written to,如果有get accessor,称它可以被read from,如果它只有get accessor,那么称它为read-only property

accessors跟methods不完全一样

  1. A set accessor acts like a method that accepts a parameter and assigns it to a variable, but you do not include the parameter in a parameter list. Instead, the parameter is accessed using an automatically supplied parameter named value.
  2. A get accessor returns the value of the field associated with the property, but you do not code a return type; the return type of a get accessor is implicitly the type of the property in which it is contained.

事实上,使用set和get很直接

1
2
3
4
Employee myChef = new Employee();
myChef.IdNumber = 2345; // 使用了set

WriteLine("ID number is {0}", myChef.IdNumber); // 使用了get

让类的字段保持private并使用accessors去操作它们,让数据保持隐藏的状态,这在面向对象的编程是很重要的特点。此外,accessor可以让你控制类中的数据值,例如,如果你不希望idNumber超过500,可以

1
2
3
4
5
6
7
8
9
10
11
set
{
if(value < 500)
{
idNumber = value;
}
else
{
idNumber = 500;
}
}

不过,property不能作为ref或out参数传入方法

1
2
3
4
5
int.TryParse(ReadLine(), out myChef.IdNUmber); // 不编译

int temp;
int.TryParse(ReadLine(), out temp);
myChef.IdNumber = temp;

9.4.1 Using Auto-Implemented Properties

一个简单的property写法

1
2
3
4
5
public int IdNumber {get; set;}

public int IdNumber {get; private set;} // read-only
public int IdNumber {private get; set;} // could set but not retrieve

A property written in this format is an auto-implemented property. The property’s implementation (its set of working statements) is created for you automatically with the assumptions that the set accessor should simply assign a value to the appropriate field and the get accessor should simply return the field.
只有同时使用get和set才能用,而且不用声明对应的backing field,编译器会自动生成一个

9.5 More About public and private Access Modifiers

When you define a named constant within a class, it is always static, even though you cannot use the keyword static as a modifier. In other words, the field belongs to the entire class, not to any specific instance of the class. Static and nonstatic fields differ as follows:

  1. When you create a nonstatic field in a class and create 25 objects, then 25 fields are stored.
  2. When you create a static field, only one copy is stored for the entire class, no matter how many objects you instantiate.
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
using static System.Console;
class Carpet
{
public const string MOTTO = "Our carpets are quality-made";
private int length;
private int width;
private int area;
public int Length
{
get
{
return length;
}
set
{
length = value;
CalcArea();
}
}
public int Width
{
get
{
return width;
}
set
{
width = value;
CalcArea();
}
}
public int Area
{
get
{
return area;
}
}
private void CalcArea()
{
area = Length * Width;
}
}

class TestCarpet
{
static void Main()
{
Carpet aRug = new Carpet();
aRug.Width = 12;
aRug.Length = 14;
Write("The {0} X {1} carpet ", aRug.Width, aRug.Length);
WriteLine("has an area of {0}", aRug.Area);
WriteLine("Our motto is: {0}", Carpet.MOTTO);
}
}

注意Area只能读,而在Width或Length改变后,都会调用CalcArea()更新area
MOTTO是常量,不能被改变,而且是静态的,使用时是Carpet.MOTTO

9.6 Understanding the this Reference

假如我们有一个类叫Book,每一个Book对象不需要存每个property和method的拷贝值,不同的对象调用函数的结果未必一样,因为它们的数据成员可能不一样,事实上每一个实例方法和property accessor都会被传入一个reference,叫this reference, which is the address of the invoking object.

When you call the method myBook.AdvertisingMessage(), you automatically pass the this reference to the method so that it knows which instance of Book to use. Only nonstatic methods receive a this reference.
当然也可以不写this,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using static System.Console;
class Book
{
private string title;
private int numPages;
private double price;
public string Title
{
get
{
return title;
}
set
{
title = value;
}
}
public void AdvertisingMessage()
{
WriteLine("Buy it now: {0}", this.Title); // this可以省略
}
}

有时必须写this,避免混淆

1
2
3
4
5
6
7
8
9
10
11
12
class Book
{
private double tax;
private double price;
public void SetPriceAndTax(double price)
{
// 参数price和字段price不同
const double TAX_RATE = 0.07;
this.price = price;
tax = price * TAX_RATE;
}
}

9.7 Understanding Constructors

A constructor is a method that instantiates an object. If you do not write a constructor for a class, then each class you create is automatically supplied with a public constructor
with no parameters.
自动创建的constructor会给对象的data fields赋初值

  • 数字设为0
  • 字符设为’\0’
  • 布尔值设为false
  • reference设为null

constructor可以重载(overload)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Employee
{
public int IdNumber {get; set;}
public double Salary {get; set;}
public Employee()
{
// 无参数,default constructor
IdNumber = 999;
Salary = 0;
}
public Employee(int empId)
{
IdNumber = empId;
Salary = 0;
}
public Employee(char code)
{
IdNumber = 111;
Salary = 100000;
}
}

为了避免代码重复,可以使用constructor initializer, a clause that indicates another instance of a class constructor should be executed before any statements in the current constructor body.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Employee
{
public int IdNumber { get; set; }
public double Salary { get; set; }
public Employee() : this(999, 0)
{
}
public Employee(int empId) : this(empId, 0)
{
}
public Employee(int empId, double sal)
{
IdNumber = empId;
Salary = sal;
}
public Employee(char code) : this(111, 100000)
{
}
}

这里只有第三个Employee在干活,比如第一个Employee实际上是(根据this里的两个参数)先调用第三个Employee,传入参数empId和0,然后再执行第一个Employee里的内容

readonly修饰符跟const很相似,它用来赋常量值,但是const必须定义时赋值,readonly可以定义时赋值或者在constructor赋值

9.8 Using Object Initializers

An object initializer allows you to assign values to any accessible members or properties of a class at the time of instantiation without calling a constructor with parameters.

1
2
3
4
5
6
7
8
Employee aWorker = new Employee{IdNumber = 101};
Employee aWorker = new Employee(){IdNumber = 101};

Employee myAssistant = new Employee {IdNumber = 202, Salary = 25.00};
// 等价于
Employee myAssistant = new Employee();
myAssistant.IdNumber = 202;
myAssistant.Salary = 25.00;

先执行构造函数,再赋值IdNumber

9.9 Overloading Operators

运算符可以重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Book
{
public Book(string title, int pages, double price)
{
Title = title;
NumPages = pages;
Price = price;
}
public static Book operator +(Book first, Book second)
{
const double EXTRA = 10.00;
string newTitle = first.Title + " and " + second.Title;
int newPages = first.NumPages + second.NumPages;
double newPrice;
if (first.Price > second.Price)
newPrice = first.Price + EXTRA;
else
newPrice = second.Price + EXTRA;
return (new Book(newTitle, newPages, newPrice));
}
public string Title { get; set; }
public int NumPages { get; set; }
public double Price { get; set; }
}

operator+()是一个method,实现了Books类型的“加法”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using static System.Console;
class AddBooks
{
static void Main()
{
Book book1 = new Book("Silas Marner", 350, 15.95);
Book book2 = new Book("Moby Dick", 250, 16.00);
Book book3;
book3 = book1 + book2;
WriteLine("The new book is \"{0}\"", book3.Title);
WriteLine("It has {0} pages and costs {1}",
book3.NumPages, book3.Price.ToString("C"));
}
}

可以看到直接用+就行,重载运算符时,至少要有一个参数的类型是这个类

9.10 Declaring an Array of Objects

对象也可以有数组,这时数组里的元素是对象的地址

1
Employee[] empArray = {new Employee(123), new Employee(456)};

同样地我们可以使用Sort()等方法,但问题是要比较只能比较对象的某一个field

An interface is a data type, like a class, except it contains one or more empty methods (and perhaps other members) that can be used by any class.
Each method is an abstract method, which is a method with no body. A class that uses an interface must provide definitions to override the interface’s abstract method definitions. Unlike a class, an interface cannot contain instance fields, and you cannot create objects from it.

A class that implements an interface must override the interface’s methods. When one method overrides another, it takes precedence over the method and is used instead of the original version. When a method overrides another, it has the same signature as the method it overrides. Note that overriding is different from overloading; overloaded methods have different signatures.

接口里的方法是空的,使用接口的类都必须包含这个接口里的方法,每个类可以使用相同的函数名,但函数体未必一样,这就实现了多态性
例如

1
2
3
4
interface IComparable
{
int CompareTo(Object o);
}

Object is a class—the most generic(一般的) of all classes. Every Employee object you create is not only an Employee but also an Object. By using the type Object as a parameter, the CompareTo() method can accept anything.

类要使用这个接口,格式见下面的代码,

When you create a class that contains an IComparable.CompareTo() method, the method is an instance method and receives a this reference to the object used to call it. A second object is passed to the method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Employee : IComparable // 格式
{
public int IdNumber { get; set; }
public double Salary { get; set; }
int IComparable.CompareTo(Object o) // 格式
{
int returnVal;
Employee temp = (Employee)o; // 要转换
if (this.IdNumber > temp.IdNumber)
returnVal = 1;
else
if (this.IdNumber < temp.IdNumber)
returnVal = -1;
else
returnVal = 0;
return returnVal;
}
}

这个方法是一个实例方法,它属于一个Employee对象,注意并不是传进的参数,传进的参数是来比较的,它属于的对象就是this指向的对象

1
2
int answer = worker1.CompareTo(worker2);
// 这里的worker1就是this

9.11 Understanding Destructors

A destructor contains the actions you require when an instance of a class is destroyed. Most often, an instance of a class is destroyed when it goes out of scope. As with constructors, if you do not explicitly create a destructor for a class, C# automatically provides one.

析构函数不能有参数,所以一个类只能最多有一个析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
class Employee
{
public int IdNumber { get; set; }
public Employee(int empID)
{
IdNumber = empID;
WriteLine("Employee object {0} created", IdNumber);
}
~Employee()
{
WriteLine("Employee object {0} destroyed!", IdNumber);
}
}

析构函数会自动调用,事实上一个对象如果不能再被任何代码使用,也就是"goes out of scope",它就具备了被析构的资格,实际的析构过程可能发生在对象具有被析构的资格后的任何时刻

9.12 GUI

10 Introduction to Inheritance

10.1 Understanding Inheritance

Inheritance is the principle that you can apply your knowledge of a general category to more specific objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Employee
{
private double salary;
public int IdNum { get; set; }
public double Salary
{
get
{
return salary;
}
set
{
const double MIN = 20_000;
if (value < MIN)
salary = MIN;
else
salary = value;
}
public string GetGreeting()
{
string greeting = "Hello. I am employee #" + IdNum;
return greeting;
}
}

A class that is used as a basis for inheritance, such as Employee, is called a base class. When you create a class that inherits from a base class (such as CommissionEmployee), it is a derived class or extended class.

派生类还能再派生,一个派生出来的子类的一系列父类称作这个子类的祖先,继承有传递性,子类继承它所有祖先的全部成员(特征+方法)

10.2 Extending Class

1
2
3
4
class CommissionEmployee : Employee
{
public double CommissionRate {get; set;}
}

Each CommissionEmployee object automatically contains all the fields, properties, and methods of the base class; you then can add new components to the new, derived class.

10.3 Using protected Access Specifier

对于佣金员工,我们想把他的工资设置为0,只能通过Salary这个property,但是Employee类的property里salary最低也是2000,而且尽管子类也有salary这个field,但实际上它不能使用父类的private field,这怎么办呢

The solution is to create the salary field in the parent class with protected access, which provides you with an intermediate level of security between public and private access.

A protected data field or method can be used within its own class or in any classes extended from that class, but it cannot be used by “outside” classes.

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
class Employee
{
protected double salary;
public int IdNum {get; set;}
public double Salary
{
get
{
return salary;
}
set
{
const double MIN = 20_000;
if(value < MIN)
{
salary = MIN;
}
else
{
salary = value;
}
}
}
public string GetGreeting()
{
string greeting = "Hello, I am employee #" + IdNum;
return greeting;
}
}

class CommissionEmployee : Employee
{
private double commissionRate;
public double CommissionRate
{
get
{
return commissionRate;
}
set
{
commissionRate = value;
salary = 0; // 可以直接使用protected salary field
}
}
}

10.4 Overriding Base Class Members

虽然派生类拥有基类的所有properties和methods,但可能对于特定的对象实现方法不一样,使用相同的方法或属性名,但实现不同,这被称作多态(polymorphism)

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
class Student
{
private const double RATE = 55.75;
protected int credits;
protected double tuition;
public string Name { get; set; }
public virtual int Credits
{
get
{
return credits;
}
set
{
credits = value;
tuition = credits * RATE;
}
}
public double Tuition
{
get
{
return tuition;
}
}
}

class ScholarshipStudent : Student
{
public override int Credits
{
get {return credits}
set
{
credits = value;
tuition = 0;
}
}
}

Credits is declared to be virtual. A virtual method (or property) is one that can be overridden by a method with the same signature in a child class. When a method overrides another, it takes precedence over the method.

The Credits property is declared with the override modifier because it has the same header (that is, the same signature—the same name and parameter list) as a property in its parent class. The Credits property is said to override and hide its counterpart in the parent class. (You could do the same thing with a method.)

In C#, you can use either new or override when defining a derived class member that has the same name as a base class member, but you cannot use both together. When you write a statement such as ScholarshipStudent s1 = new ScholarshipStudent();, you won’t notice the difference.
However, if you use new when defining the derived class Credits property and write a statement such as Student s2 = new ScholarshipStudent(); (using Student as the type), then s2.Credits accesses the base class property. On the other hand, if you use override when defining Credits in the derived class, then s2.Credits uses the derived class property

10.4.1 Accessing Base Class Methods and Properties from a Derived 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
class Employee
{
protected double salary;
public int IdNum {get; set;}
public double Salary
{
get
{
return salary;
}
set
{
const double MIN = 20_000;
if(value < MIN)
{
salary = MIN;
}
else
{
salary = value;
}
}
}
public string GetGreeting()
{
string greeting = "Hello, I am employee #" + IdNum;
return greeting;
}
}

class CommissionEmployee : Employee
{
private double commissionRate;
public double CommissionRate
{
get
{
return commissionRate;
}
set
{
commissionRate = value;
salary = 0;
}
}
new public string GetGreeting()
{
string greeting = base.GetGreeting();
greeting += "\nI work on commission.";
return greeting;
}
}

子类可以使用base.GetGreeting()来获得基类的GetGreeting()版本,使用new关键词是因为基类并没有加上virtual,不能使用override,派生类的方法与基类方法同名,就只能用new

10.5 Using Implicit Reference Conversions

Every derived object “is a” specific instance of both the derived class and the base class. In other words, myCar “is a” Car as well as a Vehicle, and myDog “is a” Dog as well as a Mammal.
You can assign a derived class object to an object of any of its superclass types. When you do, C# makes an implicit conversion from derived class to base class.

When a derived class object is assigned to its ancestor’s data type, the conversion can more specifically be called an implicit reference conversion. This term is more accurate because it emphasizes the difference between numerical conversions and reference objects.
When you assign a derived class object to a base class type, the object is treated as though it had only the characteristics defined in the base class and not those added in the child class definition.

When a CommissionEmployee class inherits from Employee, an object of either type can be passed to a method that accepts an Employee parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using static System.Console;
class DemoCommissionEmployee3
{
static void Main()
{
Employee clerk = new Employee();
CommissionEmployee salesperson = new CommissionEmployee();
clerk.IdNum = 234;
salesperson.IdNum = 345;
DisplayGreeting(clerk);
DisplayGreeting(salesperson);
}
public static void DisplayGreeting(Employee emp)
/*
Both an Employee and a CommissionEmployee can be passed to this method.
*/
{
WriteLine("Hi there from #" + emp.IdNum);
WriteLine(emp.GetGreeting());
}
}

10.6 Using the Object Class

Every class you create in C# derives from a single class named System.Object. In other words, the Object class type in the System namespace is the ultimate base class, or root class, for all other types.
实际上所有类都从这个类派生而来

1
2
3
4
5
6
7
8
9
class Employee
{
// ...
}
// 等价于
class Employee : Object
{
// ...
}

The keyword object is an alias for the System.Object class.
Because every class descends from Object, every object “is an” Object. As proof, you can write a method that accepts an argument of type Object, and it will accept arguments of any type.

Object类包含一个构造函数、一个析构函数和四个public且非静态的方法

10.6.1 GetType()

返回对象的类型
If you have created an Employee object named someWorker, then the following statement displays Employee:
WriteLine(someWorker.GetType());

If an object’s class is defined in a namespace, then GetType() returns a string composed of the namespace, a dot, and the class name.

10.6.2 ToString()

Object类自带的方法并不是很有用,例如ToString()只会返回这个对象的类的名字(字符串),这样就跟GetType()一样了,所以我们可以根据特别的类来相应override

1
2
3
4
public override string ToString()
{
return (getType() + ": " + IdNum + " " + Name);
}

10.6.3 Equals()

The Equals() method compares objects for reference equality. Reference equality occurs when two reference type objects refer to the same object. The Equals() method returns true if two Objects have the same memory address—that is, if one object is a reference to the other and both are literally the same object.

如果要override这个函数,要满足

  • Its header should be as follows (you can use any identifier for the Object parameter, 即o无所谓):
    public override bool Equals(Object o)
  • It should return false if the argument is null.
  • It should return true if an object is compared to itself.
  • It should return true only if both of the following are true:
    oneObject.Equals(anotherObject)
    anotherObject.Equals(oneObject)
  • If neObject.Equals(anotherObject) returns true and oneObject.Equals(aThirdObject) returns true, then anotherObject.Equals(aThirdObject) should also be true.

10.6.4 GetHashCode()

如果重写了Equals(),也要重写GetHashCode(),因为这个方法如果不重写,就是基于内存地址生成数字(哈希码),两个不同的对象可能内容一样,但地址不一样会导致哈希码也不一样

重写GetHashCode()时要根据业务内容来返回哈希码,比如员工的不同IdNumber对应不同的哈希码

10.7 Working with Base Class Constructors

当实例化一个派生类的对象时,基类的constructor会先执行,然后派生类的constructor执行,因此,实例化任何一个对象时,Object类的constructor都会执行
如果我们创建了一个基类和一个派生类,那么创建派生类对象时,有三个constructor依次执行,Object类、基类、派生类,可以试一下

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
using static System.Console;
class DemoCommissionEmployee4
{
static void Main()
{
CommissionEmployee salesperson = new CommissionEmployee();
}
}
class Employee
{
private int idNum;
protected double salary;
public Employee()
{
WriteLine("Employee constructed");
}
}
class CommissionEmployee : Employee
{
private double commissionRate;
public CommissionEmployee()
{
WriteLine("CommissionEmployee constructed");
}
}

如果基类只有需要参数的构造函数,那么必须保证子类能够提供参数,因为子类也要调用基类的构造函数
例如我们建立了一个Employee类,它有一个需要两个参数的构造函数,那么子类CommissionEmployee的构造函数可能这么写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public CommissionEmployee() : base(1234, "XXXX")
{
// ...
}

public CommissionEmployee(int id, string name) : base(id, name)
{
// ...
}

public CommissionEmployee(int id, string name, double rate) : base(id, name)
{
CommissionRate = rate;
// rate is used within child constructor
// two parameters passed to base constructor
// ...
}

10.8 Creating and Using Abstract Classes

A class that is used to instantiate objects is a concrete class. A class that you create only to extend from, but not to instantiate from, is an abstract class. An abstract class is one from which you cannot create concrete objects but from which you can inherit.

Abstract classes are like regular classes in that they can contain data fields and methods. The difference is that you cannot create instances of abstract classes by using the new operator. Rather, you create abstract classes simply to provide a base class from which other objects can be derived.

抽象类可以包含抽象方法,也可以包含不抽象的方法,也可以不包含方法,如果一个类包含抽象方法,任何它的派生类都需要

  1. 通过给出具体的body来override这个抽象方法
  2. 可以声明这个方法是abstract的,然后它的子类必须实现这个方法,这时这个类也必须是抽象类,因为抽象方法不能在抽象类或接口外

new是基类和派生类同名代码可以各写各的,但是用哪个要看创建对象时左边的类型

virtual跟override搭配使用,没有virtual就不能用override,virtual可以重写也可以不重写

abstract必须重写方法,重写的方法要带上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
abstract class Animal
{
private string name;
public Animal (string name)
{
this.name = name;
}
public string Name
{
get
{
return name;
}
}
public abstract string Speak();
}

class Dog : Animal
{
public Dog(string name) : base(name)
{

}
public override string Speak()
{
return "woof";
}
}
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
using static System.Console;
class DemoAnimals
{
static void Main()
{
Dog spot = new Dog("Spot");
WriteLine(spot.Name + " says " + spot.Speak());
}
}

// 等价于

using static System.Console;
class DemoAnimals2
{
static void Main()
{
Dog spot = new Dog("Spot");
DisplayAnimal(spot);
}
public static void DisplayAnimal(Animal creature)
// 这里传进来Animal或它的子类类型的参数都可以
{
WriteLine(creature.Name + " says " + creature.Speak());
// 调用方法时,virtual和abstract都是调用子类的方法,new要看类型
}
}

10.9 Creating and Using Interfaces

When you create a Patent class for each product for which your company holds a patent(专利), you might want to include product information as well as information about the employee who was responsible for the invention. In this situation, it would be convenient to inherit fields and methods from both the Product and Employee classes. The ability to inherit from more than one class is called multiple inheritance.

C#中无法进行多重继承,但可以通过接口来实现类似的效果
An interface is a collection of methods (and perhaps other members) that can be used by any class as long as the class provides a definition to override the interface’s abstract definitions. Within an abstract class, some methods can be abstract, while others need not be. Within an interface, all methods are abstract.

Abstract classes and interfaces are similar in that you cannot instantiate concrete objects from either one. Abstract classes differ from interfaces in that abstract classes can contain nonabstract methods, but all methods within an interface must be abstract. A class can inherit from only one base class (whether abstract or not), but it can implement any number of interfaces.
例如

1
2
class Child : Parent, IWorkable, Iplayable
// 继承Parent类,实现两个接口
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
using static System.Console;
class DemoWorking
{
static void Main()
{
Employee bob = new Employee("Bob");
Dog spot = new Dog("Spot");
Cat puff = new Cat("Puff");
WriteLine(bob.Name + " says " + bob.Work());
WriteLine(spot.Name + " says " + spot.Work());
WriteLine(puff.Name + " says " + puff.Work());
}
}
public interface IWorkable
{
string Work();
}
class Employee : IWorkable
{
public Employee(string name)
{
Name = name;
}
public string Name { get; set; }
public string Work()
{
return "I do my job";
}
}
abstract class Animal : IWorkable
{
public Animal(string name)
{
Name = name;
}
public string Name { get; set; }
public abstract string Work();
}
class Dog : Animal
{
public Dog(string name) : base(name)
{
}
public override string Work()
{
return "I watch the house";
}
}
class Cat : Animal
{
public Cat(string name) : base(name)
{
}
public override string Work()
{
return "I catch mice";
}
}

接口是一种类型,如果对象所属的类实现了接口,编译器就认为它属于这个接口类型,可以理解为它可以实现接口中的那些方法,这似乎在描述它的一系列能力,所以上面的程序还可以这样改

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
using static System.Console;
class DemoWorking
{
static void Main()
{
Employee bob = new Employee("Bob");
Dog spot = new Dog("Spot");
Cat puff = new Cat("Puff");
WriteLine(bob.Name + " says " + bob.Work());
WriteLine(spot.Name + " says " + spot.Work());
WriteLine(puff.Name + " says " + puff.Work());
}
public void WorkDeclaring(IWorkable item)
// 传进来的参数都“可以工作”(workable)
{
WriteLine(item.Name + " says " + item.Work());
}
}
public interface IWorkable
{
string Name { get; } // 加入Name
// 这里并不是属性那节中说的自动accessor
// 只是接口成员声明的一部分,说实现接口的类必须提供一个能读取Name的getter访问器
string Work();
}
class Employee : IWorkable
{
public Employee(string name)
{
Name = name;
}
public string Name { get; set; }
public string Work()
{
return "I do my job";
}
}
abstract class Animal : IWorkable
{
public Animal(string name)
{
Name = name;
}
public string Name { get; set; }
public abstract string Work();
}
class Dog : Animal
{
public Dog(string name) : base(name)
{
}
public override string Work()
{
return "I watch the house";
}
}
class Cat : Animal
{
public Cat(string name) : base(name)
{
}
public override string Work()
{
return "I catch mice";
}
}

Beginning programmers sometimes find it difficult to decide when to create an abstract base class and when to create an interface. You can follow these guidelines:

  1. Typically, you create an abstract class when you want to provide some data or methods that derived classes can inherit, but you want the subclasses to override some specific methods that
    you declare to be abstract.
  2. You create an interface when you want derived classes to override every method.

In other words, you inherit from an abstract base class when the class you want to create “is a” subtype, and you use an interface when the class you want to create will act like the interface.