-
枚举与结构体——用C#开发订单状态管理系统
第一部分:C#基础入门
实例9:枚举与结构体——订单状态管理系统
实例介绍
订单状态管理是电商系统的核心场景:订单从创建到完成需经历待支付、已支付、已发货、已完成等离散状态,而订单信息(如订单号、金额、状态)是一组轻量、固定的字段组合。
本实例通过枚举(Enum) 管理订单状态(确保类型安全,避免vb.net教程C#教程python教程SQL教程access 2010教程
魔法数字),结构体(Struct) 存储订单核心信息(值类型特性适合小数据存储),结合状态转换逻辑与金额计算,让你掌握这两个特性的核心用法,写出清晰、高效的业务代码。
需求分析
1.定义订单状态:用枚举表示互斥的订单状态(待支付、已支付、已发货、已完成、已取消);
2.存储订单信息:用结构体封装订单号、用户ID、金额、状态、优惠金额等轻量字段;
3.状态转换逻辑:实现合法的状态流转(如待支付→已支付,已支付→已发货,禁止非法流转如已完成→取消);
4.金额计算:计算实际支付金额(订单金额 - 优惠金额,优惠为空则按0处理);
5.订单展示:友好输出订单详情(含状态、金额等信息)。
代码实现
csharp
using System;
namespace EnumAndStructDemo
{
// 1. 订单状态枚举:离散状态,类型安全
public enum OrderStatus
{
Created = 0, // 待支付(默认值)
Paid = 1, // 已支付
Shipped = 2, // 已发货
Completed = 3, // 已完成
Cancelled = 4 // 已取消
}
// 2. 订单信息结构体:轻量、值类型存储
public struct OrderInfo
{
// 订单核心字段(用init-only属性确保不可变字段,C#9+)
public string OrderId { get; init; }
public int UserId { get; init; }
public decimal Amount { get; init; } // 金额用decimal确保精度
public decimal? Discount { get; init; } // 优惠可选
// 状态字段(可变,因为订单状态会流转)
public OrderStatus Status { get; set; }
// 计算实际支付金额
public decimal GetActualAmount()
{
return Amount - (Discount ?? 0m); // 优惠为空则按0处理
}
// 重写ToString:友好展示订单详情
public override string ToString()
{
return $"订单号:{OrderId}
用户ID:{UserId}
原始金额:{Amount:C}
优惠金额:{Discount?.ToString("C") ?? "无"}
实际支付:{GetActualAmount():C}
当前状态:{Status}";
}
}
// 3. 订单状态管理类:处理合法状态流转
public static class OrderManager
{
// 支付订单(需待支付状态)
public static void PayOrder(ref OrderInfo order)
{
if (order.Status != OrderStatus.Created)
throw new InvalidOperationException($"当前状态[{order.Status}]无法支付,需待支付状态");
order.Status = OrderStatus.Paid;
}
// 发货订单(需已支付状态)
public static void ShipOrder(ref OrderInfo order)
{
if (order.Status != OrderStatus.Paid)
throw new InvalidOperationException($"当前状态[{order.Status}]无法发货,需已支付状态");
order.Status = OrderStatus.Shipped;
}
// 完成订单(需已发货状态)
public static void CompleteOrder(ref OrderInfo order)
{
if (order.Status != OrderStatus.Shipped)
throw new InvalidOperationException($"当前状态[{order.Status}]无法完成,需已发货状态");
order.Status = OrderStatus.Completed;
}
// 取消订单(仅待支付/已支付可取消)
public static void CancelOrder(ref OrderInfo order)
{
if (order.Status != OrderStatus.Created && order.Status != OrderStatus.Paid)
throw new InvalidOperationException($"当前状态[{order.Status}]无法取消,仅待支付/已支付可取消");
order.Status = OrderStatus.Cancelled;
}
}
// 4. 主程序:演示订单流转
class Program
{
static void Main(string[] args)
{
// 创建订单(用init-only属性初始化不可变字段)
OrderInfo order = new OrderInfo
{
OrderId = "ORD20240520001",
UserId = 1001,
Amount = 299.9m,
Discount = 30.5m,
Status = OrderStatus.Created
};
Console.WriteLine("=== 初始订单 ===");
Console.WriteLine(order.ToString());
Console.WriteLine();
try
{
// 支付订单
OrderManager.PayOrder(ref order);
Console.WriteLine("=== 支付后订单 ===");
Console.WriteLine(order.ToString());
Console.WriteLine();
// 发货订单
OrderManager.ShipOrder(ref order);
Console.WriteLine("=== 发货后订单 ===");
Console.WriteLine(order.ToString());
Console.WriteLine();
// 完成订单
OrderManager.CompleteOrder(ref order);
Console.WriteLine("=== 完成后订单 ===");
Console.WriteLine(order.ToString());
Console.WriteLine();
// 尝试取消已完成订单(触发异常)
// OrderManager.CancelOrder(ref order);
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"操作失败:{ex.Message}");
}
Console.ReadKey();
}
}
}
逐行讲解
-
枚举OrderStatus的定义
csharp
public enum OrderStatus
{
Created = 0, Paid =1, Shipped=2, Completed=3, Cancelled=4
}
为什么用枚举? 离散状态(如订单状态)用枚举可确保类型安全:禁止传入非法值(如用5表示不存在的状态),避免魔法数字(如用0代表待支付)带来的维护问题。
底层实现:枚举默认基于int类型,显式赋值(如Created=0)让状态与整数映射更清晰,也可指定其他整数类型(如byte节省内存)。
2. 结构体OrderInfo的设计
csharp
public struct OrderInfo
{
public string OrderId { get; init; } // init-only:创建后不可修改
public decimal Amount { get; init; } // decimal:钱的计算必须用decimal(避免浮点误差)
public OrderStatus Status { get; set; } // 可变:状态会流转
...
}
结构体的优势:订单信息是轻量、固定大小的数据(订单号、金额等),用结构体(值类型)存储可避免堆分配,提升性能。
init-only属性:C#9+特性,确保订单号、金额等核心字段创建后不可修改,避免意外篡改。
decimal类型:金额计算不能用float/double(精度丢失),decimal精度高达28位,适合金融场景。
3. 状态转换的核心逻辑
csharp
public static void PayOrder(ref OrderInfo order)
{
if (order.Status != OrderStatus.Created)
throw new InvalidOperationException(...);
order.Status = OrderStatus.Paid;
}
ref关键字:结构体是值类型,默认传递拷贝。用ref可直接修改原结构体(否则修改的是拷贝,原订单状态不变)。
状态合法性校验:订单只能从待支付转为已支付,非法状态直接抛出异常,确保业务逻辑正确。
4. 金额计算方法
csharp
public decimal GetActualAmount()
{
return Amount - (Discount ?? 0m);
}
空合并运算符:优惠金额Discount是decimal?(可选),用??处理空值(为空时按0计算)。
精度保证:全程用decimal,避免金额计算误差(如299.9-30.5=269.4,而非浮点的近似值)。
基础知识拓展
- 枚举与结构体的核心区别
| 特性 | 枚举(Enum) | 结构体(Struct) |
|---|---|---|
| 类型 | 值类型 | 值类型 |
| 底层 | 基于整数类型(默认int) | 可包含多种类型字段/属性 |
| 用途 | 表示离散、互斥的状态集合 | 存储轻量、固定大小的数据 |
| 可变性 | 不可变(枚举值是常量) | 可变(可修改字段/属性) |
| 内存分配 | 栈分配(值类型) | 栈分配(小数据时更高效) |
-
枚举的高级用法
字符串转换:Enum.ToString()可直接获取枚举名称(如OrderStatus.Paid.ToString()→"Paid");Enum.Parse("Shipped")可将字符串转为枚举值。
Flags特性:若状态可组合(如“已支付且已发货”),可加[Flags]特性(需按位赋值,如Created=1<<0, Paid=1<<1),实现多状态标记。 -
结构体的最佳实践
小数据优先:结构体适合存储小于16字节的数据(超过则值传递开销大于引用传递)。
不可变设计:尽量用init-only属性让结构体不可变(除需动态修改的字段如状态),避免值类型的意外拷贝问题。
避免无参构造函数:结构体默认有无参构造函数(C#10前不可自定义),建议用带参构造函数确保字段初始化。
扩展思考
1.状态流转配置化:用字典存储允许的状态转换(如new Dictionary<OrderStatus, List>{{Created, new List{Paid, Cancelled}}}),让状态流转更灵活。
2.结构体序列化:用System.Text.Json序列化订单结构体,存储到文件或数据库(如JsonSerializer.Serialize(order))。
3.枚举本地化:用资源文件存储枚举的中文名称(如“待支付”代替“Created”),提升用户体验。
4.结构体与类的选择:若订单信息包含大量明细(如商品列表),应改用类(引用类型),避免结构体过大导致的性能问题。
总结
枚举与结构体是C#处理特定场景的“利器”:
枚举:让离散状态管理更安全、清晰,告别魔法数字;
结构体:让轻量数据存储更高效,避免堆分配;
两者结合:可快速实现订单状态管理这类场景,写出既安全又高效的代码。
掌握它们的核心用法,你就能应对更多业务场景,提升代码质量。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49400.html?1765264391










