当前位置:首页 > 技能相关 > C#与C++ > 正文内容

c#高级编程 类与结构 (一)

admin2年前 (2023-01-25)C#与C++2710 修订时间:2023-01-30 09:25:54

1、类

类和结构实际上都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法。类定义了类的每个对象(称为实例)可以包含什么数据和功能。

class PhoneCustomer
{
public const string DayOfSendingBill = "Monday";
public int CustomerID;
public string FirstName;
public string LastName;
}

1.1 成员

类包含成员,成员可以是静态或实例成员。静态成员属于类;实例成员属于对象。静态字段的值对每个对象都是相同的。而每个对象的实例字段都可以有不同的值。静态成员关联了static修饰符。

成员
说明
字段

字段是类的数据成员,它是类型的一个变量,该类型是类的一个成员

常量

常量与类相关(尽管它们没有static修饰符)。编译器使用真实值代替常量

方法
方法是与特定类相关联的函数
属性

属性是可以从客户端访问的函数组,其访问方式与访问类的公共字段类似。C#为读写类中的属性提供了专用语法,所以不必使用那些名称中嵌有Get或Set的方法。因为属性的这种语法不同于一般函数的语法,所有在客户端代码中,虚拟的对象被当作实际的东西

构造函数

构造函数是在实例化对象时自动调用的特殊函数。它们必须与所属的类同名,且不能有返回类型。构造函数用于初始化字段的值

索引器
索引器允许对象用访问数组的方式访问。
运算符

运算符执行的最简单的操作就是加法和减法。在两个整数相加时,严格地说,就是对整数使用“+”运算符。C#还允许指定把已有的运算符应用于自己的类(运算符重载)。

事件

事件是类的成员,在发生某些行为(如修改类的字段或属性,或者进行了某种形式的用户交互操作)时,它可以让对象通知调用方。

客户可以包含所谓“事件处理程序”的代码来响应该事件。

析构函数

析构函数或终结器的语法类似于构造函数的语法,但是在CLR检测到不再需要某个对象时调用它。它们的名称与类相同,但前面有一个“~”符号。不可能预测什么时候调用终结器。

类型

类可以包含内部类。如果内部类型只和外部类型结合使用,就很有趣

1.1.1 字段

字段是与类相关的变量。

var customer1 = new PhoneCustomer();
customer1.FirstName = "Simon";

1.1.2 常量

常量与类的关联方式和变量与类的关联方式相同。使用const关键字来声明常量。

class PhoneCustomer
{
public const string DayOfSendingBill = "Monday";
public int CustomerID;
public string FirstName;
public string LastName;
}

最好不把字段声明为public。如果修改类的公共成员,使用这个公共成员的每个调用程序也需要更改。

1.1.3 属性

属性的概念是:它是一个方法或一对方法,在客户端代码看来,它(们)是一个字段。

下面把前面示例中变量名为_firstName的名字字段改为私有。FirstName属性包含get和set访问器,来检索和设置支持字段的值:

class PhoneCustomer
{
    private string _firstName;
    public string FirstName
    {
       get { return _firstName; }
       set { firstName = value; }
     }
}

get访问器不带任何参数,且必须返回属性声明的类型。也不应为set访问器指定任何显式参数,但编译器假定它带一个参数,其类型也与属性相同,并表示为value。

1.1.4 方法

正式的C#术语区分函数和方法。在C#术语中,“函数成员”不仅包含方法,也包含类或结构的一些非数据成员,如索引器、运算符、构造函数和析构函数等,甚至还有属性。这些都不是数据成员,字段、常量和事件才是数据成员。

1.1.4.1 方法的声明

方法的定义包括任意方法修饰符(如方法的可访问性)、返回值的类型, 然后依次是方法名、输入参数的列表(用圆括号括起来)和方法体(用花括号括起来)。

[modifiers] return_type MethodName([parameters])
{
// Method body
}

每个参数都包括参数的类型名和在方法体中的引用名称。但如果方法有返回值,则return语句就必须与返回值一起使用,以指定出口点,例如:

public bool IsSquare(Rectangle rect)
{
return (rect.Height == rect.Width);
}

如果方法没有返回值,就把返回类型指定为void,因为不能省略返回类型。如果方法不带参数,仍需要在方法名的后面包含一对空的圆括号()。此时return语句就是可选的——当到达右花括号时,方法会自动返回。

1.1.4.2 表达式体方法

如果方法的实现只有一个语句,C# 6为方法定义提供了一个简化的语法:表达式体方法。使用新的语法,不需要编写花括号和return关键字,而使用运算符= >(lambda操作符)区分操作符左边的声明和操作符右边的实现代码。

lambda操作符的右侧定义了方法的实现代码。不需要花括号和返回语句。返回的是语句的结果,该结果的类型必须与左边方法声明的类型相同,在下面的代码片段中,该类型是bool:

public bool IsSquare(Rectangle rect) => rect.Height == rect.Width;
1.1.4.3 调用方法

在下面的例子中,说明了类的定义和实例化、方法的定义和调用的语法。类Math定义了静态成员和实例成员:

public class Math
{
public int Value { get; set; }
public int GetSquare() => Value * Value;
public static int GetSquareOf(int x) => x * x;
public static double GetPi() => 3.14159;
}

Program类利用Math类,调用静态方法,实例化一个对象,来调用实例成员

using static System.Console;
namespace MathSample
{
class Program
{
static void Main()
{// Try calling some static functions.
WriteLine($"Pi is {Math.GetPi()}");
int x = Math.GetSquareOf(5);
WriteLine($"Square of 5 is {x}");
// Instantiate a Math object
var math = new Math(); // instantiate a reference type
// Call instance members
math.Value = 30;
WriteLine($"Value field of math variable contains {math.Value}");
WriteLine($"Square of 30 is {math.GetSquare()}");
}
}

运行MathSample示例,会得到如下结果:

Pi is 3.14159
Square of 5 is 25
Value field of math variable contains 30
Square of 30 is 900

可以看出,Math类包含一个属性和一个方法,该属性包含一个数字,该方法计算该数字的平方。这个类还包含两个静态方法,一个返回pi的值,另一个计算作为参数传入的数字的平方。

1.1.4.4 方法的重载

C#支持方法的重载——方法的几个版本有不同的签名(即,方法名相同,但参数的个数和/或数据类型不同)。为了重载方法,只需要声明同名但参数个数或类型不同的方法即可:

class ResultDisplayer{
public void DisplayResult(string result)
{
// implementation
}
public void DisplayResult(int result)
{
// implementation
}
}

注意: 对于方法重载,仅通过返回类型不足以区分重载的版本。仅通过参数名称也不足以区分它们。需要区分参数的数量和/或类型。

1.1.4.5 命名的参数

调用方法时,变量名不需要添加到调用中。然而,如果有如下的方法签名,用于移动矩形:

public void MoveAndResize(int x, int y, int width, int height)

用下面的代码片段调用它,就不能从调用中看出使用了什么数字,这些数字用于哪里:

r.MoveAndResize(30, 40, 20, 40);

可以改变调用,明确数字的含义:

r.MoveAndResize(x:
30, y:
40, width:
20, height:
40);

任何方法都可以使用命名的参数调用。只需要编写变量名,后跟一个冒号和所传递的值。

1.1.4.6 可选参数

参数也可以是可选的。必须为可选参数提供默认值。可选参数还必须是方法定义的最后的参数:

public void TestMethod(int notOptionalNumber, int optionalNumber = 42)
{
WriteLine(optionalNumber + notOptionalNumber);
}

这个方法可以使用一个或两个参数调用。传递一个参数,编译器就修改方法调用,给第二个参数传递42。

TestMethod(11);
TestMethod(11, 22);

可以定义多个可选参数,如下所示:

public void  TestMethod(int n,int opt1 = 11,int opt2 =22,int opt3 =33)
{
WriteLine(n + opt1 + opt2 + opt3);
}

通过多个可选参数,命名参数的特性就会发挥作用。使用命名参数,可以传递任何可选参数,例如,下面的例子仅传递最后一个参数:

TestMethod(1, opt3: 4);
1.1.4.7 个数可变的参数

使用可选参数,可以定义数量可变的参数。然而,还有另一种语法允许传递数量可变的参数——这个语法没有版本控制问题。

声明数组类型的参数(示例代码使用一个int数组),添加params关键字,就可以使用任意数量的int参数调用该方法。

public void AnyNumberOfArguments(params int[] data)
{
foreach (var x in data)
{
WriteLine(x);
}
}

AnyNumberOfArguments方法的参数类型是int[],可以传递一个int数组,或因为

params关键字,可以传递一个或任何数量的int值:

AnyNumberOfArguments(1);
AnyNumberOfArguments(1, 3, 5, 7, 11, 13);

如果应该把不同类型的参数传递给方法,可以使用object数组:

public void AnyNumberOfArguments(params object[] data)
{
// etc.
}

现在可以使用任何类型调用这个方法:

AnyNumberOfArguments("text", 42);

如果params关键字与方法签名定义的多个参数一起使用,则params只能使用一次,而且它必须是最后一个参数:

WriteLine(string format, params object[] arg);

1.1.5 构造函数

声明基本构造函数的语法就是声明一个与包含的类同名的方法,但该方法没有返回类型:

public class MyClass
{
public MyClass()
{
}
}

一般情况下,如果没有提供任何构造函数,编译器会在后台生成一个默认的构造函数。

构造函数的重载遵循与其他方法相同的规则。换言之,可以为构造函数提供任意多的重载,只要它们的签名有明显的区别即可:

public MyClass() // zeroparameter constructor
{
// construction code
}public MyClass(int number) // another overload
{
// construction code
}

如果提供了带参数的构造函数,编译器就不会自动提供默认的构造函数。只有在没有定义任何构造函数时,编译器才会自动提供默认的构造函数。

1.1.5.1 从构造函数中调用其他构造函数

在一个类中有几个构造函数,以容纳某些可选参数,这些构造函数包含一些共同的代码。例如,下面的情况:

class Car
{
private string _description;
private uint _nWheels;
public Car(string description, uint nWheels)
{
_description = description;
_nWheels = nWheels;
}
public Car(string description)
{
_description = description;
_nWheels = 4;
}

这两个构造函数初始化相同的字段,显然,最好把所有的代码放在一个地方。C#有一个特殊的语法,称为构造函数初始化器,可以实现此目的:

class Car
{
private string _description;
private uint _nWheels;
public Car(string description, uint nWheels)
{
_description = description;
_nWheels = nWheels;
}
public Car(string description): this(description, 4)
{
}

这里,this关键字仅调用参数最匹配的那个构造函数。注意,构造函数初始化器在构造函数的函数体之前执行。现在假定运行下面的代码:

var myCar = new Car("Proton Persona");

在本例中,在带一个参数的构造函数的函数体执行之前,先执行带两个参数的构造函数(但在本例中,因为在带一个参数的构造函数的函数体中没有代码,所以没有区别)。

C#构造函数初始化器可以包含对同一个类的另一个构造函数的调用(使用前面介绍的语法),也可以包含对直接基类的构造函数的调用(使用相同的语法,但应使用base关键字代替this)。初始化器中不能有多个调用。

1.1.5.2 静态构造函数

C#的一个新特征是也可以给类编写无参数的静态构造函数。这种构造函数只执行一次,而前面的构造函数是实例构造函数,只要创建类的对象,就会执行它。

class MyClass
{
static MyClass()
{
// initialization code
}
// rest of class definition
}

编写静态构造函数的一个原因是,类有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性。

在C#中,通常在第一次调用类的任何成员之前执行静态构造函数,确保静态构造函数至多运行一次。

注意,静态构造函数没有访问修饰符。出于同样原因,静态构造函数不能带任何参数,一个类也只能有一个静态构造函数。

很显然,静态构造函数只能访问类的静态成员,不能访问类的实例成员。

1.1.6 只读成员

如果不希望在初始化后修改数据成员,就可以使用readonly关键字。

为了保证对象的字段不能改变,字段可以用readonly修饰符声明。带有readonly修饰符的字段只能在构造函数中分配值。

。它与const修饰符不同。编译器通过const修饰符,用其值取代了使用它的变量。编译器知道常量的值。只读字段在运行期间通过构造函数指定。

与常量字段相反,只读字段可以是实例成员。使用只读字段作为类成员时,需要把static修饰符分配给该字段。

如果有一个用于编辑文档的程序,因为要注册,所以需要限制可以同时打开的文档数。现在假定要销售该软件的不同版本,而且顾客可以升级他们的版本,以便同时打开更多的文档。显然,不能在源代码中对最大文档数进行硬编码,而是需要一个字段来表示这个最大文档数。这个字段必须是只读的——每次启动程序时,从注册表键或其他文件存储中读取。代码如下所示:

public class DocumentEditor
{
private static readonly uint s_maxDocuments;
static DocumentEditor()
{s_maxDocuments = DoSomethingToFindOutMaxNumber();
}
}
1.1.6.1  只读属性

在属性定义中省略set访问器,就可以创建只读属性。因此,如下代码把Name变成只读属性:

private readonly string _name;
public string Name
{
get
{
return _name;
}
}

用readonly修饰符声明字段,只允许在构造函数中初始化属性的值。

同样,在属性定义中省略get访问器,就可以创建只写属性。但是,这是不好的编程方式,因为这可能会使客户端代码的作者感到迷惑。一般情况下,如果要这么做,最好使用一个方法替代。

1.1.6.2 自动实现的只读属性

C# 6提供了一个简单的语法,使用自动实现的属性创建只读属性,访问只读字段。这些属性可以使用属性初始化器来初始化。

public string Id { get; } = Guid.NewGuid().ToString();

在后台,编译器会创建一个只读字段和一个属性,其get访问器可以访问这个字段。初始化器的代码进入构造函数的实现代码,并在调用构造函数体之前调用。当然,只读属性也可以在构造函数中初始化,如下面的代码片段所示:

public class Person
{
public Person(string name)
{
Name = name;
}
public string Name { get; }
}
1.1.6.3 表达式体属性

C# 6中与属性相关的另一个扩展是表达式体属性。类似于表达式体方法,表达式体属性不需要花括号和返回语句。表达式体属性是带有get访问器的属性,但不需要编写get关键字。只是get访问器的实现后跟lambda操作符。对于Person类,FullName属性使用表达式体属性实现,通过该属性返回FirstName和LastName属性值的组合:

public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
public string FullName => $"{FirstName} {LastName}";
}
1.1.6.4 不可变的类型

如果对象没有任何可以改变的成员,只有只读成员,它就是一个不可变类型。其内容只能在初始化时设置。这对于多线程是非常有用的,因为多个线程可以访问信息永远不会改变的同一个对象。因为内容不能改变,所以不需要同步。

不可变类型的一个例子是String类。这个类没有定义任何允许改变其内容的成员。诸如ToUpper(把字符串更改为大写)的方法总是返回一个新的字符串,但传递到构造函数的原始字符串保持不变。

拓扑:C# 匿名类型 、C# 可空类型

2、结构

结构与类非常相似,主要的区别是使用关键字struct代替class来声明结构。

public struct Dimensions
{
public double Length { get; set; }
public double Width { get; set; }
}

为结构定义函数与为类定义函数完全相同。下面的代码说明了结构的构造函数和属性:

public struct Dimensions
{public double Length { get; set; }
public double Width { get; set; }
public Dimensions(double length, double width)
{
Length = length;
Width = width;
}
public double Diagonal => Math.Sqrt(Length * Length + Width * Width);
}

结构是值类型,不是引用类型。它们存储在栈中或存储为内联(如果它们是存储在堆中的另一个对象的一部分),其生存期的限制与简单的数据类型一样。

结构不支持继承。

对于结构,构造函数的工作方式有一些区别。如果没有提供默认的构造函数,编译器会自动提供一个,把成员初始化为其默认值。

使用结构,可以指定字段如何在内存中布局。

因为结构实际上是把数据项组合在一起,所以有时大多数或者全部字段都声明为public。

2.1 结构是值类型

虽然结构是值类型,但在语法上常常可以把它们当作类来处理。例如,在上面的Dimensions类的定义中,可以编写下面的代码:

var point = new Dimensions();
point.Length = 3;
point.Width = 6;

注意,因为结构是值类型,所以new运算符与类和其他引用类型的工作方式不同。

new运算符并不分配堆中的内存,而是只调用相应的构造函数,根据传送给它的参数,初始化所有的字段。对于结构,可以编写下述完全合法的代码:

Dimensions point;
point.Length = 3;
point.Width = 6;

结构遵循其他数据类型都遵循的规则:在使用前所有的元素都必须进行初始化。在结构上调用new运算符,或者给所有的字段分别赋值,结构就完全初始化了。当然,如果结构定义为类的成员字段,在初始化包含的对象时,该结构会自动初始化为0。

但当把结构作为参数传递给方法时,应把它作为ref参数传递,以避免性能损失——此时只传递了结构在内存中的地址,这样传递速度就与在类中的传递速度一样快了。但如果这样做,就必须注意被调用的方法可以改变结构的值。

2.2 结构和继承

结构不是为继承设计的。这意味着:它不能从一个结构中继承。唯一的例外是对应的结构最终派生于类System.Object。因此,结构也可以访问System.Object的方法。在结构中,甚至可以重写System.Object中的方法——如重写ToString()方法。

结构的继承链是:每个结构派生自System.ValueType类,System.ValueType类又派生自System.Object。ValueType并没有给Object添加任何新成员,但提供了一些更适合结构的实现方式。注意,不能为结构提供其他基类:每个结构都派生自ValueType。

2.3 结构的构造函数

为结构定义构造函数的方式与为类定义构造函数的方式相同。

前面说过,默认构造函数把数值字段都初始化为0,且总是隐式地给出,即使提供了其他带参数的构造函数,也是如此。

在C# 6中,也可以实现默认的构造函数,为字段提供初始值(这一点在早期的C#版本中未实现)。为此,只需要初始化每个数据成员:

public Dimensions()
{
Length = 0;Width = 1;
}
public Dimensions(double length, double width)
{
Length = length;
Width = width;
}

另外,可以像类那样为结构提供Close()或Dispose()方法。

拓展:

        1、C# 按值和按引用传递参数(ref)

3、类与结构

结构不同于类,因为它们不需要在堆上分配空间(类是引用类型,总是存储在堆(heap)上),而结构是值类型,通常存储在栈(stack)上,另外,结构不支持继承。

对于类和结构,都使用关键字new来声明实例,这个关键字创建对象并对其进行初始化。在下面的例子中,类和结构的字段值都默认为0:

var myCustomer = new PhoneCustomer(); // works for a class
var myCustomer2 = new PhoneCustomerStruct(); // works for a struct

在大多数情况下,类要比结构常用得多。因此,我们先讨论类,然后指出类和结构的区别,以及选择使用结构而不使用类的特殊原因。但除非特别说明,否则就可以假定用于类的代码也适用于结构。

4、部分类

partial关键字允许把类、结构、方法或接口放在多个文件中。一般情况下,某种类型的代码生成器生成了一个类的某部分,所以把类放在多个文件中是有益的。

partial关键字的用法是:把partial放在class、struct或interface关键字的前面。在下面的例子中,SampleClass类驻留在两个不同的源文件SampleClassAutogenerated.cs和SampleClass.cs中:

//SampleClassAutogenerated.cs
partial class SampleClass
{
public void MethodOne() { }
}
//SampleClass.cs
partial class SampleClass
{
public void MethodTwo() { }
}

编译包含这两个源文件的项目时,会创建一个SampleClass类,它有两个方法MethodOne()和MethodTwo()。

如果声明类时使用了下面的关键字,则这些关键字就必须应用于同一个类的所有部分:

● public     ● private   ● protected   ● internal

● abstract  ● sealed    ● new            ● 一般约束

在嵌套的类型中,只要partial关键字位于class关键字的前面,就可以嵌套部分类。在把部分类编译到类型中时,属性、XML注释、接口、泛型类型的参数属性和成员会合并。有如下两个源文件:

// SampleClassAutogenerated.cs
[CustomAttribute]
partial class SampleClass: SampleBaseClass, ISampleClass
{
public void MethodOne() { }
}

// SampleClass.cs
[AnotherAttribute]
partial class SampleClass: IOtherSampleClass
{
public void MethodTwo() { }
}

编译后,等价的源文件变成:

[CustomAttribute]
[AnotherAttribute]
partial class SampleClass: SampleBaseClass, ISampleClass, IOtherSampleClass
{
public void MethodOne() { }public void MethodTwo() { }
}

注意: 尽管partial关键字很容易创建跨多个文件的巨大的类,且不同的开发人员处理同一个类的不同文件,但该关键字并不用于这个目的。在这种情况下,最好把大类拆分成几个小类,一个类只用于一个目的。

部分类可以包含部分方法。如果生成的代码应该调用可能不存在的方法,这就是非常有用的。扩展部分类的程序员可以决定创建部分方法的自定义实现代码,或者什么也不做。下面的代码片段包含一个部分类,其方法MethodOne调用APartialMethod方法。APartialMethod方法用partial关键字声明;因此不需要任何实现代码。如果没有实现代码,编译器将删除这个方法调用:

//SampleClassAutogenerated.cs
partial class SampleClass
{
public void MethodOne()
{
APartialMethod();
}
public partial void APartialMethod();
}

部分方法的实现可以放在部分类的任何其他地方,如下面的代码片段所示。有了这个方法,编译器就在MethodOne内创建代码,调用这里声明的APartialMethod:

// SampleClass.cs
partial class SampleClass: IOtherSampleClass
{
public void APartialMethod()
{
// implementation of APartialMethod}
}

部分方法必须是void类型,否则编译器在没有实现代码的情况下无法删除调用。

5、扩展方法

扩展方法是给对象添加功能的另一个选项,在不能使用继承时,也可以使用这个选项(例如类是密封的)。

注意: 扩展方法也可以用于扩展接口。

扩展方法是静态方法,它是类的一部分,但实际上没有放在类的源代码中。

假设希望用一个方法扩展string类型,该方法计算字符串中的单词数。GetWordCount方法利用String.Split方法把字符串分割到字符串数组中,使用Length属性计算数组中元素的个数:

public static class StringExtension
{
public static int GetWordCount(this string s) => s.Split().Length;
}

使用this关键字和第一个参数来扩展字符串。这个关键字定义了要扩展的类型。即使扩展方法是静态的,也要使用标准的实例方法语法。注意,这里使用fox变量而没有使用类型名来调用GetWordCount ()。

string fox = "the quick brown fox jumped over the lazy dogs down " + "9876543210 times";
int wordCount = fox.GetWordCount(); 
Console.WriteLine($"{wordCount} words");
Console.Read();

在后台,编译器把它改为调用静态方法:

int wordCount = StringExtension.GetWordCount(fox);

使用实例方法的语法,而不是从代码中直接调用静态方法,会得到一个好得多的语法。这个语法还有一个好处:该方法的实现可以用另一个类取代,而不需要更改代码—只需要运行新的编译器。

编译器如何找到某个类型的扩展方法?

this关键字必须匹配类型的扩展方法,而且需要打开定义扩展方法的静态类所在的名称空间。如果把StringExtensions类放在名称空间Wrox. Extensions中,则只有用using指令打开Wrox.Extensions,编译器才能找到GetWordCount方法。

如果类型还定义了同名的实例方法,扩展方法就永远不会使用。类中已有的任何实例方法都优先。

当多个同名的扩展方法扩展相同的类型,打开所有这些类型的名称空间时,编译器会产生一个错误,指出调用是模棱两可的,它不能决定在多个实现代码中选择哪个。然而,如果调用代码在一个名称空间中,这个名称空间就优先。

注意: 语言集成查询(Language Integrated Query, LINQ)利用了许多扩展方法。

6、Object类

所有的.NET类最终都派生自System.Object。实际上,如果在定义类时没有指定基类,编译器就会自动假定这个类派生自Object。

本章没有使用继承,所以前面介绍的每个类都派生自System.Object(如前所述,对于结构,这个派生是间接的:结构总是派生自System.ValueType, System.ValueType又派生自System.Object)。

其实际意义在于,除了自己定义的方法和属性等外,还可以访问为Object类定义的许多公有的和受保护的成员方法。这些方法可用于自己定义的所有其他类中。

下面将简要总结每个方法的作用:

ToString()方法

获取对象的字符串表示的一种便捷方式。

当只需要快速获取对象的内容,以进行调试时,就可以使用这个方法。在数据的格式化方面,它几乎没有提供选择:例如,在原则上日期可以表示为许多不同的格式,但DateTime.ToString()没有在这方面提供任何选择。如果需要更复杂的字符串表示,例如,考虑用户的格式化首选项或区域性,就应实现IFormattable接口。

GetHashCode()方法

如果对象放在名为映射(也称为散列表或字典)的数据结构中,就可以使用这个方法。处理这些结构的类使用该方法确定把对象放在结构的什么地方。如果希望把类用作字典的一个键,就需要重写GetHashCode()方法。

Equals()(两个版本)和ReferenceEquals()方法

注意有3个用于比较对象相等性的不同方法,这说明.NET Framework在比较相等性方面有相当复杂的模式。这3个方法和比较运算符“==”在使用方式上有微妙的区别。而且,在重写带

一个参数的虚Equals()方法时也有一些限制,因为System.Collections名称空间中的一些基类要调用该方法,并希望它以特定的方式执行。

Finalize()方法

它最接近C++风格的析构函数,在引用对象作为垃圾被回收以清理资源时调用它。Object中实现的Finalize()方法实际上什么也没有做,因而被垃圾回收器忽略。如果对象拥有对未托管资源的引用,则在该对象被删除时,就需要删除这些引用,此时一般要重写Finalize()。垃圾收集器不能直接删除这些对未托管资源的引用,因为它只负责托管的资源,于是它只能依赖用户提供的Finalize()。

GetType()方法

返回从System.Type派生的类的一个实例,因此可以提供对象成员所属类的更多信息,包括基本类型、方法、属性等。System.Type还提供了.NET的反射技术的入口点。、

MemberwiseClone()方法

这是System.Object中唯一没有在本书的其他地方详细论述的方法。不需要讨论这个方法,因为它在概念上相当简单,它只复制对象,并返回对副本的一个引用(对于值类型,就是一个装箱的引用)。注意,得到的副本是一个浅表复制,即它复制了类中的所有值类型。如果类包含内嵌的引用,就只复制引用,而不复制引用的对象。这个方法是受保护的,所以不能用于复制外部的对象。该方法不是虚方法,所以不能重写它的实现代码。

 您阅读本篇文章共花了: 

免责声明
本站内容均为博客主本人日常使用记录的存档,如侵犯你的权益请联系:lifei@zaiheze.com 546262132@qq.com 沟通删除事宜。本站仅带访问端口形式使用,已杜绝搜索引擎爬取。

扫描二维码推送至手机访问。

版权声明:本文由LIFEI - blog发布,如需转载请注明出处。

本文链接:http://www.lifeiai.com/?id=204

分享给朋友:

相关文章

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。