0Pricing
C# Academy · 课时

值相等性与解构

了解记录的结构相等性,重写 Equals/GetHashCode,并使用解构模式。

值相等性与解构 是 CoddyKit 上的免费 C# Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。

深入理解记录相等性

记录会自动生成 Equals、GetHashCode 以及 ==/!= 运算符,以结构化方式比较所有属性。理解其工作方式,有助于您正确使用记录,并在需要时进行 override。

自动生成的相等性如何工作

编译器会生成使用 EqualityComparer<T>.Default 比较每个属性的代码。对于引用类型属性,比较的是它们自身的 Equals,而不是引用相等性。

public record Address(string Street, string City);
public record Person(string Name, Address HomeAddress);

var a = new Person("Alice", new Address("123 Main", "NYC"));
var b = new Person("Alice", new Address("123 Main", "NYC"));

Console.WriteLine(a == b); // True — deep structural equality
// Equality checks:
// Name: "Alice" == "Alice" ✓
// HomeAddress: compares Address properties recursively ✓

重写生成的相等性逻辑

如果需要自定义相等性逻辑,例如只比较部分属性,可以在记录上重写 Equals 和 GetHashCode。

public record Customer(int Id, string Name, string Email)
{
    // Consider customers equal if they have the same ID,
    // regardless of name or email changes
    public virtual bool Equals(Customer? other)
        => other is not null && Id == other.Id;

    public override int GetHashCode() => Id.GetHashCode();
}

集合中的记录

由于记录通过结构相等性实现了 IEquatable<T>,因此它们可以正确用作字典键、用于 HashSets,也能与 LINQ 的 Distinct 和 GroupBy 正确协作。

public record Tag(string Name, string Color);

var tags = new List<Tag>
{
    new("C#", "blue"), new("C#", "blue"), new("dotnet", "purple")
};

var unique = tags.Distinct().ToList(); // [Tag(C#, blue), Tag(dotnet, purple)]

var set = new HashSet<Tag>(tags);
set.Contains(new Tag("C#", "blue")); // true

解构:基础知识

位置参数记录会自动生成 Deconstruct 方法,并为每个位置参数属性提供 out 参数。这支持类似元组的解包。

public record Point(double X, double Y);
public record RGB(byte R, byte G, byte B);

var p = new Point(3.0, 4.0);
var (x, y) = p; // deconstruct
Console.WriteLine($"{x}, {y}");

var color = new RGB(255, 128, 0);
var (r, g, b) = color;
Console.WriteLine($"R={r}, G={g}, B={b}");

模式匹配中的解构

在 switch 表达式中解构记录,可以实现强大的位置模式匹配。

public record Point(int X, int Y);

string Classify(Point p) => p switch
{
    (0, 0)           => "Origin",
    (int x, 0)       => $"X-axis at {x}",
    (0, int y)       => $"Y-axis at {y}",
    (int x, int y) when x == y => $"Diagonal at {x}",
    (int x, int y)   => $"Point ({x}, {y})"
};

Console.WriteLine(Classify(new Point(0, 0)));  // Origin
Console.WriteLine(Classify(new Point(3, 3)));  // Diagonal at 3

自定义 Deconstruct 方法

非位置参数记录(以及任何类)都可以手动添加 Deconstruct 方法。扩展方法也可以为现有类型添加解构功能。

public class Temperature
{
    public double Celsius { get; }
    public Temperature(double c) => Celsius = c;

    // Manual Deconstruct
    public void Deconstruct(out double celsius, out double fahrenheit)
    {
        celsius    = Celsius;
        fahrenheit = Celsius * 9 / 5 + 32;
    }
}

var t = new Temperature(100);
var (c, f) = t;
Console.WriteLine($"{c}°C = {f}°F"); // 100°C = 212°F

忽略解构的一部分

可以使用弃元(_)忽略解构中不需要的部分,其用法类似于元组弃元。

public record Order(int Id, string Customer, decimal Total, DateTime Date);

var order = new Order(42, "Alice", 99.99m, DateTime.UtcNow);

// Only want Id and Total
var (id, _, total, _) = order;
Console.WriteLine($"Order {id}: ${total}");

// In switch: ignore some fields
string Classify(Order o) => o switch
{
    (_, _, > 1000, _) => "High value",
    (_, _, > 100, _)  => "Medium value",
    _                 => "Low value"
};

属性模式与位置模式

记录同时支持位置模式(按位置匹配)和属性模式(按名称匹配)。对于具有许多属性的记录,属性模式更易于阅读。

public record Order(int Id, string Status, decimal Total);

// Positional pattern:
string DescribePos(Order o) => o switch
{
    (_, "Shipped", > 100) => "High-value shipped",
    _                     => "Other"
};

// Property pattern (more readable):
string DescribeProp(Order o) => o switch
{
    { Status: "Shipped", Total: > 100 } => "High-value shipped",
    { Status: "Cancelled" }             => "Cancelled",
    _                                   => "Other"
};

实际应用:Result 类型模式

使用记录和解构实现类似判别联合的 Result 类型,是一种函数式错误处理方式。

public abstract record Result<T>;
public record Success<T>(T Value) : Result<T>;
public record Failure<T>(string Error) : Result<T>;

Result<Order> PlaceOrder(CreateOrderRequest req)
{
    if (req.Quantity <= 0)
        return new Failure<Order>("Quantity must be positive");
    var order = CreateOrder(req);
    return new Success<Order>(order);
}

// Consuming:
var result = PlaceOrder(request);
switch (result)
{
    case Success<Order>(var order):
        return Results.Created($"/orders/{order.Id}", order);
    case Failure<Order>(var error):
        return Results.BadRequest(error);
}

快速检查

在解构表达式中使用弃元(_)会有什么作用?

总结:值相等性与解构

要点:

  • 自动生成的相等性会递归地按结构比较所有属性
  • 为记录重写 Equals/GetHashCode,以实现自定义标识逻辑
  • 记录作为 HashSet 元素和字典键时能够正确工作
  • 位置记录会自动生成 Deconstruct——使用 var (a, b) = record
  • 弃元 (_) 可在解构时忽略不需要的位置值
  • 对于复杂记录,属性模式 ({Status: "X"}) 比位置模式更易读

常见问题解答

「值相等性与解构」课时是免费的吗?

是的 — 「值相等性与解构」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。

「值相等性与解构」这节课中我会学到什么?

了解记录的结构相等性,重写 Equals/GetHashCode,并使用解构模式。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C# Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「值相等性与解构」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C# Academy 课中编写并运行代码吗?

能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 记录类型:基础与语法
  2. 使用 init 与 with 实现不可变性
  3. 值相等性与解构
  4. 领域驱动设计中的记录
← 返回 C# Academy