VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 枚举与结构体——用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();
	}
	}
	}

逐行讲解

  1. 枚举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,而非浮点的近似值)。
基础知识拓展

  1. 枚举与结构体的核心区别
特性 枚举(Enum) 结构体(Struct)
类型 值类型 值类型
底层 基于整数类型(默认int) 可包含多种类型字段/属性
用途 表示离散、互斥的状态集合 存储轻量、固定大小的数据
可变性 不可变(枚举值是常量) 可变(可修改字段/属性)
内存分配 栈分配(值类型) 栈分配(小数据时更高效)
  1. 枚举的高级用法
    字符串转换:Enum.ToString()可直接获取枚举名称(如OrderStatus.Paid.ToString()→"Paid");Enum.Parse("Shipped")可将字符串转为枚举值。
    Flags特性:若状态可组合(如“已支付且已发货”),可加[Flags]特性(需按位赋值,如Created=1<<0, Paid=1<<1),实现多状态标记。
  2. 结构体的最佳实践
    小数据优先:结构体适合存储小于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


相关教程