对象(C# 编程指南)
类或结构定义的作用类似于蓝图,指定该类型可以进行哪些操作。 从本质上说,对象是按照此蓝图分配和配置的内存块。 程序可以创建同一个类的多个对象。 对象也称为实例,可以存储在命名变量中,也可以存储在数组或集合中。 使用这些变量来调用对象方法及访问对象公共属性的代码称为客户端代码。 在 C# 等面向对象的语言中,典型的程序由动态交互的多个对象组成。
提示
静态类型的行为与此处介绍的不同。 有关更多信息,请参见 静态类和静态类成员(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();
}
}
/*
Output:
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();
}
}
/*
Output:
p1 Name = Alex Age = 9
p2 Name = Spencer Age = 7
p1 Name = Alex Age = 9
*/
p1 和 p2 的内存在线程堆栈上进行分配。 该内存随声明它的类型或方法一起回收。 这就是在赋值时复制结构的一个原因。 相比之下,当对类实例对象的所有引用都超出范围时,为该类实例分配的内存将由公共语言运行时自动回收(垃圾回收)。 无法像在 C++ 中那样明确地销毁类对象。 有关 .NET Framework 中的垃圾回收的更多信息,请参见垃圾回收。
提示
公共语言运行时中高度优化了托管堆上内存的分配和释放。 在大多数情况下,在堆上分配类实例与在堆栈上分配结构实例在性能开销上没有显著的差别。
对象标识与值相等性
在比较两个对象是否相等时,首先必须明确您是想知道两个变量是否表示内存中的同一对象,还是想知道这两个对象的一个或多个字段的值是否相等。 如果您要对值进行比较,则必须考虑这两个对象是值类型(结构)的实例,还是引用类型(类、委托、数组)的实例。
若要确定两个类实例是否引用内存中的同一位置(意味着它们具有相同的标识),可使用静态 Equals 方法。 (System.Object 是所有值类型和引用类型的隐式基类,其中包括用户定义的结构和类。)
若要确定两个结构实例中的实例字段是否具有相同的值,可使用 ValueType.Equals 方法。 由于所有结构都隐式继承自 System.ValueType,因此可以直接在对象上调用该方法,如下面的示例所示:
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.");
// Output: p2 and p1 have the same values.
Equals 的 System.ValueType 实现使用反射,因为它必须能够确定任何结构中有哪些字段。 在创建您自己的结构时,重写 Equals 方法可以提供针对您的类型的高效求等算法。
- 要确定两个类实例中字段的值是否相等,您可以使用 Equals 方法或 == 运算符。 但是,只有类通过已重写或重载提供关于那种类型对象的相等含义的自定义时,才能使用它们。 类也可能执行 IEquatable<T> 接口或 IEqualityComparer<T> 接口。 这两个接口都提供可用于测试值相等性的方法。 设计好重写 Equals 的类后,请务必遵循如何:为类型定义值相等性(C# 编程指南)和 Object.Equals(Object) 中介绍的准则。
相关章节
有关更多信息: