在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:设计窗体布局

使用的控件有:TextBoxDataGridViewButtonLabel

在这里插入图片描述

步骤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格式化为货币
        }

    }
}

五、测试结果

在这里插入图片描述

在这里插入图片描述

六、说明

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

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐