C# 定义和组织数据的方法 - struct(结构)
·
目录
在C# WinForm开发中,struct(结构)作为值类型数据结构,适合存储轻量、简单且具有"值语义"的数据(如坐标、简单信息记录等)。与class(引用类型)相比,struct更高效(栈分配),但不适合复杂数据或需要继承的场景。
一、struct的特性
- 值类型:存储在栈上(或嵌入在包含它的类型中),赋值时会复制完整数据(而非引用)。
- 不可变优先:建议定义为
readonly struct,确保数据创建后不可修改,避免值类型的副作用。 - 适用场景:小数据(如坐标、尺寸、简单信息记录)、需要频繁创建和复制的临时数据。
二、项目结构
在 C# 项目中,struct的文件组织方式通常遵循 “类型与文件对应” 的原则(便于维护),但也可以根据关联性合并到同一文件中。
方式一:合并到窗体文件(适合极小项目)
如果项目非常简单(仅一个窗体 + 几个简单结构),可以将struct直接定义在MainForm.cs中(放在MainForm类的外部或内部)。
伪代码:
namespace WinFormStructDemo
{
// 1. 先定义struct(放在MainForm类外部)
public readonly struct Point2D { /* 实现 */ }
public readonly struct UserInfo { /* 实现 */ }
public readonly struct OrderItem { /* 实现 */ }
// 2. 再定义窗体类
public partial class MainForm : Form
{
// 窗体逻辑
}
}
方式二 、单独文件存放(适合中小型项目)
将每个struct单独放在一个.cs文件中,文件名与struct名称一致(便于查找),与窗体类文件分开管理。
项目文件结构示例:

三、struct定义
1:坐标点(Point2D)
用于存储控件位置、绘图坐标等,需包含X/Y值和基础计算逻辑。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormStructDemo
{
//class Point2D
//{
//}
// 定义只读坐标结构(不可变)
public readonly struct Point2D
{
// 只读属性(确保不可变)
public int X { get; }
public int Y { get; }
// 构造函数:必须初始化所有字段
public Point2D(int x, int y)
{
X = x;
Y = y;
}
// 计算两点之间的距离(示例方法)
public double DistanceTo(Point2D other)
{
int dx = X - other.X;
int dy = Y - other.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
// 重写ToString,方便在控件中显示
public override string ToString()
{
return $"坐标: ({X}, {Y})";
}
}
}
2:用户信息(UserInfo)
存储简单的用户数据(ID、姓名、年龄、可变字节数组),适合表单临时存储。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormStructDemo
{
//class UserInfo
//{
//}
// 定义用户信息结构
public readonly struct UserInfo
{
public int Id { get; } // 用户ID
public string Name { get; } // 姓名
public int Age { get; } // 年龄
public byte[] bValue { get; } //可变字节数组
// 构造函数:带参数校验
public UserInfo(int id, string name, int age, byte[] value)
{
Id = id;
// 校验非空
Name = name ?? throw new ArgumentNullException(nameof(name), "姓名不能为空");
// 校验年龄范围
Age = age > 0 ? age : throw new ArgumentOutOfRangeException(nameof(age), "年龄必须大于0");
bValue = value ?? throw new ArgumentNullException(nameof(value), "字节数组不能为空");
}
// 重写ToString,用于在TextBox中显示
public override string ToString()
{
return $"用户信息:ID={Id},姓名={Name},年龄={Age},字节数组长度={bValue.Length}";
}
}
}
3:订单明细(OrderItem)
存储订单中的商品信息,包含计算总价的逻辑。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormStructDemo
{
//class OrderItem
//{
//}
// 定义订单明细结构
public readonly struct OrderItem
{
public int ProductId { get; } // 商品ID
public string ProductName { get; } // 商品名称
public decimal UnitPrice { get; } // 单价
public int Quantity { get; } // 数量
// 构造函数:参数校验
public OrderItem(int productId, string productName, decimal unitPrice, int quantity)
{
ProductId = productId;
ProductName = productName ?? throw new ArgumentNullException(nameof(productName));
UnitPrice = unitPrice >= 0 ? unitPrice : throw new ArgumentOutOfRangeException(nameof(unitPrice));
Quantity = quantity > 0 ? quantity : throw new ArgumentOutOfRangeException(nameof(quantity));
}
// 计算总价(只读属性)
public decimal TotalPrice => UnitPrice * Quantity;
// 重写ToString,用于列表展示
public override string ToString()
{
return $"{ProductName}(ID:{ProductId}):{Quantity}件 × {UnitPrice:C} = {TotalPrice:C}";
}
}
}
四、在WinForm中使用 struct
步骤1:设计窗体布局
使用的控件有:TextBox、DataGridView、Button、Label。

步骤2:窗体代码实现
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinFormStructDemo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// 初始化DataGridView(设置列标题)
dgvOrderItems.AutoGenerateColumns = true; // 自动生成列
}
//加载数据按钮点击事件
private void btnLoadData_Click(object sender, EventArgs e)
{
// 1. 处理坐标点
Point2D formLocation = new Point2D(100, 50); // 窗体初始位置
this.Location = new System.Drawing.Point(formLocation.X, formLocation.Y); // 应用到窗体
txtPoint.Text = formLocation.ToString();
// 2. 处理用户信息
try
{
byte[] bValue = { 0x01,0x02,0x03 };
UserInfo user = new UserInfo(1001, "张三", 30, bValue); // 创建用户实例
txtUser.Text = user.ToString();
Console.WriteLine($"{user.bValue.Length}{user.bValue[0]}{user.bValue[1]}{user.bValue[2]}");
}
catch (Exception ex)
{
MessageBox.Show("用户信息错误:" + ex.Message);
}
// 3. 处理订单明细(绑定到DataGridView)
List<OrderItem> orderItems = new List<OrderItem>
{
new OrderItem(1, "笔记本电脑", 5999.99m, 1),
new OrderItem(2, "无线鼠标", 99.99m, 2),
new OrderItem(3, "机械键盘", 299.99m, 1)
};
dgvOrderItems.DataSource = orderItems; // 绑定列表到表格
}
// 计算订单总价按钮点击事件
private void btnCalculate_Click(object sender, EventArgs e)
{
// 从DataGridView获取订单明细列表
var orderItems = dgvOrderItems.DataSource as List<OrderItem>;
if (orderItems == null || orderItems.Count == 0)
{
txtTotal.Text = "无订单数据";
return;
}
// 计算总价(使用LINQ)
decimal total = orderItems.Sum(item => item.TotalPrice);
txtTotal.Text = $"订单总价:{total:C}"; // C格式化为货币
}
}
}
五、测试结果


六、说明
- 定义结构体:使用
struct关键字,可以包含字段、属性、方法和构造函数 - 值类型特性:结构体是值类型,赋值时创建副本
- 性能考虑:小型数据结构使用结构体可以提高性能
- 不可变性:考虑将结构体设计为不可变的
- 方法参数:使用
ref关键字可以避免结构体复制 - 集合使用:结构体可以存储在 List、数组等集合中
- 数据验证:在属性 setter 或构造函数中添加数据验证逻辑
- 不可变struct:测试例程均使用
readonly struct,属性仅包含get访问器,确保数据创建后无法修改,避免值类型赋值时的意外修改(值类型复制后修改副本不影响原实例)。 - 值类型特性:若尝试修改struct实例的属性(如
user.Name = "李四"),编译器会报错(因readonly限制);若需修改,需重新创建实例(user = new UserInfo(...))。 - 控件绑定:
List<OrderItem>可直接绑定到DataGridView,表格会自动根据struct的属性生成列(需设置AutoGenerateColumns = true)。 - 适用边界:若数据复杂(如包含大量字段、需要继承或多态),建议使用
class;仅轻量、简单数据用struct。
更多推荐



所有评论(0)