C#基础10-结构体和枚举
1、结构体
(1)核心特性
- 值类型本质
- 内存分配在栈上(类在堆上)
- 赋值时复制完整数据副本(非引用)
csharp
复制代码
PointStruct p1 = new PointStruct(1, 2);
PointStruct p2 = p1; // p2是p1的独立副本
p2.X = 3; // p1.X仍为1
- 默认构造函数限制
- 不能自定义无参构造函数(编译器强制隐式生成)
- 有参构造函数必须初始化所有字段:
csharp
复制代码
public struct Point {
public int X;
public int Y;
public Point(int x, int y) {
X = x; // 必须初始化
Y = y; // 否则编译错误
}
}
csharp
复制代码
public struct BadExample {
public int Value = 10; // 编译错误
}
- 继承与多态
- 不支持继承其他结构体或类(隐式继承
System.ValueType
)
- 可实现接口(如
IComparable
)
(2)结构体 vs 类
特性 |
结构体(struct) |
类(class) |
类型 |
值类型(栈内存) |
引用类型(堆内存) |
赋值行为 |
复制完整数据副本 |
复制引用地址 |
null值 |
不可为null (除非Nullable<T> ) |
默认值为null |
内存开销 |
小对象高效(通常<16字节) |
大对象或长期存在对象更合适 |
构造函数 |
不能自定义无参构造 |
可自定义无参构造 |
字段初始化 |
声明时禁止赋初值 |
允许声明时初始化 |
继承 |
不支持 |
支持单继承和多态 |
(3)适用场景与最佳实践
- 优先使用结构体
- 轻量级数据对象(如坐标
Point
、颜色Color
)
- 数据大小小于16字节(避免复制开销过大)
- 需要高频创建销毁的临时对象(栈分配更快)
- 设计为不可变类型(避免值复制引发逻辑错误)
- 避免使用结构体
- 需继承或多态的场景
- 对象状态需频繁修改(值类型复制开销大)
- 大小超过16KB(栈内存限制)
- 性能优化技巧
- 避免频繁装箱拆箱(如存入
ArrayList
改用List<T>
)
- 只读结构体提升安全性:
csharp
复制代码
public readonly struct ImmutablePoint {
public int X { get; } // 只读属性
public int Y { get; }
}
(4)关键注意事项
- 慎用大型结构体 :超过16字节时,赋值复制可能比引用传递更慢
- 接口实现限制:通过接口访问结构体会触发装箱操作(性能损失)
- 默认值问题:未显式初始化时,数值类型为
0
,布尔类型为false
2、枚举
(1)基础定义与特性
- 核心作用
- 定义一组命名常量的值类型,用于表示有限且互斥的选项(如状态码、类型分类)。
- 默认基础类型为
int
,可显式指定为其他整数类型(byte
, long
等)。
- 声明语法
csharp
复制代码
public enum Season
{
Spring, // 默认值 0
Summer, // 默认值 1
Autumn = 2, // 显式赋值
Winter // 自动递增为 3
}
- 默认值规则
- 首成员未赋值时默认为 0,后续成员依次递增 1。
- 重要限制:枚举变量未初始化时默认值为 0(即使未定义值为 0 的成员)。
(2)高级用法:位标志(Flags)
- 通过
[Flags]
特性支持组合值,用于多选场景(如权限系统):
csharp
复制代码
[Flags]
public enum FileAccess
{
None = 0, // 避免使用 0 表示有效标志
Read = 1 << 0, // 二进制 0001 (值 1)
Write = 1 << 1, // 二进制 0010 (值 2)
Execute = 1 << 2 // 二进制 0100 (值 4)
}
// 使用按位或组合选项
var access = FileAccess.Read | FileAccess.Write; // 值 3 (二进制 0011)
Console.WriteLine(access); // 输出 "Read, Write"
- 关键要点:
- 成员值需为 2 的幂次方,确保位运算正确 。
- 避免用 0 表示有效标志(
None
除外)。
(3)枚举转换与操作
csharp
复制代码
int value = (int)Season.Summer; // 1
Season season = (Season)2; // Autumn
csharp
复制代码
string name = Season.Spring.ToString(); // "Spring"
Season parsed;
bool success = Enum.TryParse("Winter", out parsed); // true
csharp
复制代码
// 获取所有成员名
string[] names = Enum.GetNames(typeof(Season));
// 获取所有值
Array values = Enum.GetValues(typeof(Season));
// 获取基础类型
Type baseType = Enum.GetUnderlyingType(typeof(Season)); // System.Int32
(4)最佳实践与场景
- 适用场景
- 状态码(如 HTTP 状态
200_OK
, 404_NotFound
)。
- 分类选项(如用户等级
Gold
, Silver
)。
- 多选标志(权限控制、选项组合)。
- 设计建议
- 为成员添加 XML 注释或
Description
特性增强可读性。
- 避免重复值,强制显式赋值以提升代码可维护性。
csharp
复制代码
public enum UserLevel
{
[Description("金牌会员")]
Gold = 1,
[Description("银牌会员")]
Silver = 2
}
️(5)常见误区
- 未处理非法值 :使用
Enum.IsDefined
验证值合法性:
csharp
复制代码
Season invalid = (Season)10; // 合法但无对应成员
Console.WriteLine(invalid); // 输出 "10"
csharp
复制代码
if (Enum.IsDefined(typeof(Season), value)) { ... }
- 忽略默认值风险:未初始化的枚举变量值为 0,需确保 0 值有明确语义(如
None
)。
(6)枚举与描述转换
csharp
复制代码
public static class EnumExtensions
{
public static string GetDescription(this Enum value)
{
FieldInfo field = value.GetType().GetField(value.ToString());
DescriptionAttribute attribute = field.GetCustomAttribute<DescriptionAttribute>();
return attribute?.Description ?? value.ToString();
}
}
// 使用
UserLevel level = UserLevel.Gold;
Console.WriteLine(level.GetDescription()); // 输出 "金牌会员"