讲解C#面相对象编程中的类与对象的特性与概念
类
“类”是一种构造,通过使用该构造,您可以将其他类型的变量、方法和事件组合在一起,从而创建自己的自定义类型。类就像一个蓝图,它定义类型的数据和行为。如果类没有声明为静态类,客户端代码就可以创建赋给变量的“对象”或“实例”,从而使用该类。在对变量的所有引用都超出范围之前,该变量始终保持在内存中。所有引用都超出范围时,CLR 将标记该变量以供垃圾回收。如果类声明为静态类,则内存中只存在一个副本,并且客户端代码只能通过该类自身而不是“实例变量”访问该类。
声明类
类使用 class 关键字进行声明,如下面的示例所示:
public class Customer { //Fields, properties, methods and events go here... }
class 关键字前面是访问级别。由于在该例中使用 public,因此任何人都可以基于该类创建对象。类的名称位于 class 关键字的后面。定义的其余部分是类的主体,用于定义行为和数据。类的字段、属性、方法和事件统称为“类成员”。
创建对象
尽管有时类和对象可互换,但它们是不同的概念。类定义对象的类型,但它不是对象本身。对象是基于类的具体实体,有时称为类的实例。
通过使用 new 关键字(后跟对象将基于的类的名称)可以创建对象,如下所示:
Customer object1 = new Customer();
创建类的实例后,将向程序员传递回对该对象的引用。在前面的示例中,object1 是对基于 Customer 的对象的引用。此引用引用新对象,但不包含对象数据本身。实际上,可以在根本不创建对象的情况下创建对象引用:
Customer object2;
建议不要创建像这样的不引用对象的对象引用,因为在运行时通过这样的引用来访问对象的尝试将会失败。但是,可以创建这样的引用来引用对象,方法是创建新对象,或者将它分配给现有的对象,如下所示:
Customer object3 = new Customer(); Customer object4 = object3;
此代码创建了两个对象引用,它们引用同一个对象。因此,通过 object3 对对象所做的任何更改都将反映在随后使用的 object4 中。由于基于类的对象是按引用来引用的,因此类称为引用类型。
类继承
继承是通过使用“派生”来实现的,而派生意味着类是使用“基类”声明的,它的数据和行为从基类继承。通过在派生的类名后面追加冒号和基类名称,可以指定基类,如下所示:
public class Manager : Employee { // Employee fields, properties, methods and events are inherited // New Manager fields, properties, methods and events go here... }
当类声明基类时,它继承基类除构造函数以外的所有成员。
与 C++ 不同,C# 中的类只能直接从一个基类继承。但是,因为基类自身也可能继承自另一个类,所以类可以间接继承多个基类。而且,一个类可以直接实现一个以上的接口。
类可以声明为抽象类。抽象类包含具有签名定义但没有实现的抽象方法。抽象类不能进行实例化。只能通过实现抽象方法的派生类使用抽象类。相比之下,密封类不允许其他类从其派生。
类定义可在不同的源文件之间进行拆分。
描述
下面的示例中定义了一个公共类,其中包含一个字段、一个方法和一个称为构造函数的特殊方法。有关更多信息,请参见构造函数(C# 编程指南)。然后使用 new 关键字将该类实例化。
public class Person { // Field public string name; // Constructor that takes no arguments. public Person() { name = "unknown"; } // Constructor that takes one argument. public Person(string nm) { name = nm; } // Method public void SetName(string newName) { name = newName; } } class TestPerson { static void Main() { // Call the constructor that has no parameters. Person person1 = new Person(); Console.WriteLine(person1.name); person1.SetName("John Smith"); Console.WriteLine(person1.name); // Call the constructor that has one parameter. Person person2 = new Person("Sarah Jones"); Console.WriteLine(person2.name); // Keep the console window open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } }
输出:
unknown John Smith Sarah Jones
对象
类或结构定义的作用类似于蓝图,指定该类型可以进行哪些操作。从本质上说,对象是按照此蓝图分配和配置的内存块。程序可以创建同一个类的多个对象。对象也称为实例,可以存储在命名变量中,也可以存储在数组或集合中。使用这些变量来调用对象方法及访问对象公共属性的代码称为客户端代码。在 C# 等面向对象的语言中,典型的程序由动态交互的多个对象组成。
结构实例。. 选件类实例
由于类是引用类型,因此类对象的变量引用该对象在托管堆上的地址。如果将同一类型的第二个对象分配给第一个对象,则两个变量都引用该地址的对象。这一点将在本主题后面部分进行更详细的讨论。
类的实例是使用 new 运算符创建的。在下面的示例中,Person 为类型,person1 和 person 2 为该类型的实例(即对象)。
public class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } //Other properties, methods, events... } class Program { static void Main() { Person person1 = new Person("Leopold", 6); Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age); // Declare new person, assign person1 to it. Person person2 = person1; //Change the name of person2, and person1 also changes. person2.Name = "Molly"; person2.Age = 16; Console.WriteLine("person2 Name = {0} Age = {1}", person2.Name, person2.Age); Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age); // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } }
输出:
person1 Name = Leopold Age = 6 person2 Name = Molly Age = 16 person1 Name = Molly Age = 16
由于结构是值类型,因此结构对象的变量具有整个对象的副本。结构的实例也可以使用 new 运算符来创建,但这不是必需的,如下面的示例所示:
public struct Person { public string Name; public int Age; public Person(string name, int age) { Name = name; Age = age; } } public class Application { static void Main() { // Create struct instance and initialize by using "new". // Memory is allocated on thread stack. Person p1 = new Person("Alex", 9); Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age); // Create new struct object. Note that struct can be initialized // without using "new". Person p2 = p1; // Assign values to p2 members. p2.Name = "Spencer"; p2.Age = 7; Console.WriteLine("p2 Name = {0} Age = {1}", p2.Name, p2.Age); // p1 values remain unchanged because p2 is copy. Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age); // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } }
输出:
p1 Name = Alex Age = 9 p2 Name = Spencer Age = 7 p1 Name = Alex Age = 9
p1 和 p2 的内存在线程堆栈上进行分配。该内存随声明它的类型或方法一起回收。这就是在赋值时复制结构的一个原因。相比之下,当对类实例对象的所有引用都超出范围时,为该类实例分配的内存将由公共语言运行时自动回收(垃圾回收)。无法像在 C++ 中那样明确地销毁类对象。
对象标识与. 值相等性
在比较两个对象是否相等时,首先必须明确您是想知道两个变量是否表示内存中的同一对象,还是想知道这两个对象的一个或多个字段的值是否相等。如果您要对值进行比较,则必须考虑这两个对象是值类型(结构)的实例,还是引用类型(类、委托、数组)的实例。
若要确定两个类实例是否引用内存中的同一位置(意味着它们具有相同的标识),可使用静态 Equals 方法。(System.Object 是所有值类型和引用类型的隐式基类,其中包括用户定义的结构和类。)
若要确定两个结构实例中的实例字段是否具有相同的值,可使用 ValueType.Equals 方法。由于所有结构都隐式继承自 System.ValueType,因此可以直接在对象上调用该方法,如下面的示例所示:
// Person is defined in the previous example. //public struct Person //{ // public string Name; // public int Age; // public Person(string name, int age) // { // Name = name; // Age = age; // } //} Person p1 = new Person("Wallace", 75); Person p2; p2.Name = "Wallace"; p2.Age = 75; if (p2.Equals(p1)) Console.WriteLine("p2 and p1 have the same values.");
输出:
p2 and p1 have the same values.
Equals 的 System.ValueType 实现使用反射,因为它必须能够确定任何结构中有哪些字段。在创建您自己的结构时,重写 Equals 方法可以提供针对您的类型的高效求等算法。