C# 中的枚举和结构体






4.84/5 (60投票s)
C# 提供的两种常被忽视的值类型及其使用场景
引言
当您使用 C# 时,几乎所有东西都是堆对象。只有基本的原生类型(如 int
)被视为值类型。但在 C# 中,有两种值类型比初看之下更有用,它们是 enum
(枚举)和 struct
(结构体)类型。很少有教程会涵盖这些主题,但它们有各自的用途。而且它们都比类更高效,当它们满足您的要求时,您可以用它们来代替类以提高性能。
Enums
枚举基本上是一组命名的常量。它们在 C# 中使用 enum
关键字声明。每个 enum
类型自动派生自 System.Enum
,因此我们可以对我们的枚举使用 System.Enum
方法。枚举是值类型,在栈上创建,而不是在堆上。您无需使用 new
来创建 enum
类型。声明一个 enum
有点像设置数组的成员,如下所示。
enum Rating {Poor, Average, Okay, Good, Excellent}
您可以像传递普通对象一样将枚举传递给成员函数。您也可以对枚举执行算术运算。例如,我们可以编写两个函数,一个用于递增我们的 enum
,另一个用于递减我们的 enum
。
Rating IncrementRating(Rating r)
{
if(r == Rating.Excellent)
return r;
else
return r+1;
}
Rating DecrementRating(Rating r)
{
if(r == Rating.Poor)
return r;
else
return r-1;
}
这两个函数都以 Rating
对象作为参数,并返回一个 Rating
对象。现在我们可以从其他地方调用这些函数。
for (Rating r1 = Rating.Poor;
r1 < Rating.Excellent ;
r1 = IncrementRating(r1))
{
Console.WriteLine(r1);
}
Console.WriteLine();
for (Rating r2 = Rating.Excellent;
r2 > Rating.Poor;
r2 = DecrementRating(r2))
{
Console.WriteLine(r2);
}
这是一个示例代码片段,展示了如何对我们的 Enum 对象调用 System.Enum
方法。我们调用 GetNames
方法,它检索枚举中常量的名称数组。
foreach(string s in Rating.GetNames(typeof(Rating)))
Console.WriteLine(s);
枚举的用途
很多时候,我们会遇到类方法接受自定义选项作为参数的情况。比如,我们有一个文件访问类,其中有一个文件打开方法,该方法有一个参数,可能是只读模式、写入模式、读写模式、创建模式和追加模式中的一种。您可能会考虑在类中为这些模式添加五个静态成员字段。这是错误的方法!声明并使用枚举,它更高效,而且我认为是更好的编程实践。
结构体
在 C++ 中,struct
(结构体)在所有方面都与 class
(类)几乎相同,唯一的区别在于成员的默认访问修饰符。在 C# 中, struct
是 class
的一个“瘦弱的、可怜的”版本。我不确定这是为什么,但也许他们决定在结构体和类之间划清界限。以下是类和结构体在功能上的一些显著差异。
- 结构体是栈对象,无论您如何尝试,都无法在堆上创建它们。
- 结构体不能继承自其他结构体,但可以派生自接口。
- 您不能为
struct
声明默认构造函数,您的构造函数必须带有参数。 - 只有当您使用
new
创建struct
时,构造函数才会被调用;如果您只是声明struct
,就像声明int
这样的原生类型一样,您必须在能够使用该struct
之前显式设置每个成员的值。
struct Student : IGrade
{
public int maths;
public int english;
public int csharp;
//public member function
public int GetTot()
{
return maths+english+csharp;
}
//We have a constructor that takes an int as argument
public Student(int y)
{
maths = english = csharp = y;
}
//This method is implemented because we derive
//from the IGrade interface
public string GetGrade()
{
if(GetTot() > 240 )
return "Brilliant";
if(GetTot() > 140 )
return "Passed";
return "Failed";
}
}
interface IGrade
{
string GetGrade();
}
好了,现在让我们看看如何使用我们的 struct
。
Student s1 = new Student();
Console.WriteLine(s1.GetTot());
Console.WriteLine(s1.GetGrade());
//Output
0
Failed
这里调用了默认构造函数。这是自动实现的,我们无法拥有自己的无参默认构造函数。默认无参构造函数会将所有值初始化为其零等价物。这就是为什么我们得到 0 作为总计。
Student s2;
s2.maths = s2.english = s2.csharp = 50;
Console.WriteLine(s2.GetTot());
Console.WriteLine(s2.GetGrade());
//Output
150
Passed
因为我们没有使用 new,所以构造函数没有被调用。在所有这些愚蠢的特性中,这一项无疑会赢得年度竞赛。我看不出有什么合理的理由必须这样。无论如何,您必须初始化所有成员字段。如果您注释掉进行初始化的那一行,您将收到一个编译器错误:使用了未分配的局部变量 's2'。
Student s3 = new Student(90);
Console.WriteLine(s3.GetTot());
Console.WriteLine(s3.GetGrade());
//Output
270
Brilliant
这次我们使用了带有 int
作为参数的自定义构造函数。
何时使用结构体
因为结构体是值类型,所以它们比类更容易处理,也更高效。当您发现主要使用一个类来存储一组值时,您应该用结构体替换这些类。当您声明结构体数组时,由于它们是在堆上创建的,因此效率会进一步提高。因为如果它们是类,每个类对象都需要在堆上分配内存,并且它们的引用会被存储。实际上,.NET 框架中的许多类实际上是结构体。例如, System.Drawing.Point
实际上是一个 struct
而不是一个 class
。