-
元组与记录——C#编写简化数据传输对象
第一部分:C#基础入门
9.元组与记录——简化数据传输对象
实例介绍
在日常开发中,数据传输对象(DTO)是连接不同模块的常用工具,用于传递结构化数据(如API返回的用户信息、数据库查询结果)。传统的DTO实现需要定义类、编写属性、构造函数,甚至重写Equals和GetHashCode以支持值相vb.net教程C#教程python教程SQL教程access 2010教程
等性,样板代码冗余且繁琐。
C# 7+引入的元组(Tuple)和C#9+引入的记录(Record),为简化DTO提供了高效解决方案:
元组:轻量级,支持快速创建临时数据结构,无需定义类;
记录:面向不可变数据,自动生成值相等性、解构方法和ToString,大幅减少样板代码。
本节通过“用户信息传输”场景,对比传统DTO、元组、记录的实现差异,带你掌握这两种工具的核心用法。
需求分析
设计一个用户信息传输工具,核心需求如下:
1.减少样板代码:避免手动编写属性、构造函数、相等性方法;
2.支持不可变数据:防止传输过程中意外修改数据;
3.方便解构:快速提取数据成员,无需逐个访问属性;
4.值相等性:基于数据内容比较两个对象是否相等(而非引用地址);
5.可读性:成员有明确命名,避免“Item1、Item2”的模糊表述。
代码实现
-
传统DTO实现(对比基准)
csharp
using System;
// 传统用户DTO类
public class UserDTO
{
// 不可变属性(C#9+支持init修饰符)
public string Name { get; init; }
public int Age { get; init; }
public string Email { get; init; }
// 构造函数
public UserDTO(string name, int age, string email)
{
Name = name;
Age = age;
Email = email;
}
// 重写Equals:基于属性值比较
public override bool Equals(object obj)
{
return obj is UserDTO dto &&
Name == dto.Name &&
Age == dto.Age &&
Email == dto.Email;
}
// 重写GetHashCode:结合属性值生成哈希码
public override int GetHashCode()
{
return HashCode.Combine(Name, Age, Email);
}
// 重写ToString:友好输出
public override string ToString()
{
return $"UserDTO: Name={Name}, Age={Age}, Email={Email}";
}
}
-
元组实现(C#7+)
csharp
// 匿名值元组:可读性低,依赖位置访问
var anonymousTuple = ("张三", 25, "zhangsan@example.com");
Console.WriteLine(anonymousTuple.Item1); // 输出"张三"
// 命名值元组:为成员命名,提升可读性
var namedTuple = (Name: "张三", Age: 25, Email: "zhangsan@example.com");
Console.WriteLine(namedTuple.Name); // 输出"张三"
// 解构元组:快速提取所有成员
var (name, age, email) = namedTuple;
Console.WriteLine($"Name: {name}, Age: {age}, Email: {email}");
-
记录实现(C#9+)
csharp
// Positional Record:构造参数自动转为不可变属性
public record UserRecord(string Name, int Age, string Email);
// 测试记录特性
var user1 = new UserRecord("张三", 25, "zhangsan@example.com");
var user2 = new UserRecord("张三", 25, "zhangsan@example.com");
// 1. 值相等性:默认基于属性值比较
Console.WriteLine(user1.Equals(user2)); // True(传统类默认False)
// 2. with表达式:修改部分属性(创建新实例,原实例不变)
var updatedUser = user1 with { Age = 26 };
Console.WriteLine(updatedUser.Age); // 26
//3. 解构记录:自动生成Deconstruct方法
var (rName, rAge, rEmail) = user1;
Console.WriteLine($"Record: {rName}, {rAge}, {rEmail}");
//4. ToString:自动生成友好输出
Console.WriteLine(user1); // 输出"UserRecord { Name = 张三, Age =25, Email=zhangsan@example.com }"
逐行讲解
-
传统DTO类
不可变属性:用init修饰符确保属性只能在构造或初始化时赋值,防止数据被意外修改;
构造函数:手动初始化所有属性,保证数据完整性;
Equals重写:对比所有属性值,实现值相等性(传统类默认是引用相等);
GetHashCode重写:结合属性值生成哈希码,确保在哈希表(如Dictionary)中正确使用;
ToString重写:输出友好的字符串表示,便于调试。 -
元组实现
匿名值元组:用括号包裹值,成员通过Item1、Item2访问,适合临时、简单场景;
命名值元组:在括号内为成员命名(如Name: "张三"),编译器自动生成对应属性,可读性显著提升;
解构:通过var (name, age, email) = tuple语法快速提取所有成员,无需逐个访问。 -
记录实现
Positional Record:用record关键字+构造参数定义,参数自动转为不可变属性(init-only);
值相等性:记录默认重写Equals和GetHashCode,基于所有属性值比较,而非引用地址;
with表达式:创建新实例并修改指定属性(原实例不变),符合不可变数据的设计原则;
解构:自动生成Deconstruct方法,支持与元组相同的解构语法;
ToString:自动生成包含所有属性的字符串,无需手动重写。
基础知识拓展 -
元组类型对比
C#中的元组分为两类,核心差异在于存储方式和用途:
类型 语法示例 存储类型 适用场景
ValueTuple<T1,...> (string, int, string) 值类型 临时数据传输、方法返回多值
Tuple<T1,...> Tuple.Create("张三",25) 引用类型 兼容旧版本(C#7前),少用
命名元组是ValueTuple的语法糖,编译器会生成对应类型,并为成员添加别名(如Name对应Item1)。
2. 记录vs传统类vs元组
三者的核心差异如下表:
| 特性 | 传统类(Class) | 记录(Record) | 元组(Tuple) |
|---|---|---|---|
| 样板代码 | 多 | 少 | 无 |
| 不可变性 | 手动实现 | 默认支持 | 支持(值元组) |
| 值相等性 | 需重写Equals | 默认支持 | 默认支持 |
| 可读性 | 高 | 高 | 低/中 |
| 方法支持 | 支持 | 支持 | 不支持 |
| 继承 | 支持(类继承类) | 支持(记录继承记录) | 不支持 |
-
记录的高级特性
Init-only属性:记录的属性默认是init修饰符,只能在构造或用with修改,确保数据不可变;
继承:记录可以继承其他记录(如public record StudentRecord(string Name, int Age, string StudentId) : UserRecord(Name, Age));
自定义方法:记录可以添加方法、事件等,如:
csharp
public record UserRecord(string Name, int Age, string Email)
{
public bool IsAdult() => Age >= 18;
}
显式解构:手动重写Deconstruct方法,自定义解构逻辑:
csharp
public record UserRecord(string Name, int Age, string Email)
{
public void Deconstruct(out string name, out int age)
{
name = Name.ToUpper();
age = Age;
}
}
-
适用场景总结
元组:适合临时、简单的数据传输(如方法返回多值、短期中间结果);
记录:适合长期、稳定的DTO(如API返回数据、数据库实体);
传统类:适合需要可变状态或复杂逻辑的业务对象。
总结
元组和记录是C#简化数据传输对象的利器,它们分别解决了不同场景的痛点:
元组:以“无样板代码”为核心,快速构建临时数据结构;
记录:以“不可变性+值相等性”为核心,替代传统DTO类,减少冗余代码。
在实际开发中,应根据数据的生命周期和复杂度选择合适的工具:临时数据用元组,稳定DTO用记录,复杂业务逻辑用传统类。掌握这两种工具,能显著提升代码的简洁性和可读性。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49431.html
最新更新
元组与记录——C#编写简化数据传输对象
动态类型——C#编写COM组件交互
表达式树——C#测试动态查询构建器
LINQ高级查询——C#进行复杂数据分组与聚
异步IO操作——C#编写关于大文件多线程下
异步编程基础——C#开发Task异步方法设计
事件驱动编程——C#开发实时消息通知系
委托与Lambda——C#简化事件处理逻辑
泛型集合自定义——C#关于类型安全的队
Excel操作——用C#开发学生成绩表生成(
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比










