init / record / required:让 C# 对象一次成型

标签init record required with表达式 不可变性 数据模型 DTO

目录

  • 1. `init` 访问器:让不可变对象的创建更灵活
    • 1.1. 概念
      • 1.1.1. 语法
      • 1.1.2. 语义
    • 1.2. 设计初衷:解决什么问题?
    • 1.3. 使用方法
      • 1.3.1. 在对象初始化器中赋值(主要场景)
      • 1.3.2. 在构造函数中赋值
      • 1.3.3. 错误用法:初始化后赋值
    • 1.4. 内部实现原理
    • 1.5. 与相关特性的对比
    • 1.6. 高级主题与注意事项
      • 1.6.1. 与 `readonly` 字段的配合
      • 1.6.2. 继承中的使用
      • 1.6.3. “浅”不可变性(Shallow Immutability)
      • 1.6.4. 其他注意事项
  • 2. `record` 类型:为数据而生的引用类型
    • 2.1. 概念
    • 2.2. `record class`:引用类型记录
      • 2.2.1. 基础语法与编译器生成的内容
      • 2.2.2. 继承
      • 2.2.3. 示例:结合继承与模式匹配
      • 2.2.4. 与普通 `class` 的内存与性能对比
        • 内存布局
        • 关键差异及其影响
        • 总结与最佳实践
    • 2.3. `record struct`:值类型记录 (C# 10.0+)
    • 2.4. `record class` vs `record struct`
    • 2.5. `with` 表达式:非破坏性修改
      • 2.5.1. 概述
      • 2.5.2. 语法
      • 2.5.3. 适用类型
      • 2.5.4. 实现原理
      • 2.5.5. 示例
        • 基本用法
        • 嵌套记录的使用
        • 在普通 `struct` 中启用 `with` 表达式
      • 2.5.6. 典型应用场景
    • 2.6. 特性总结
    • 2.8. 典型应用场景
  • 3. `required` 修饰符:强制初始化的契约
    • 3.1. 概念
    • 3.2. 基本用法
    • 3.3. 与构造函数的协作
      • 3.3.1. 使用 `[SetsRequiredMembers]` 特性
      • 3.3.2. 多个构造函数场景
      • 3.3.3. 建议
    • 3.4. 高级主题与注意事项
      • 3.4.1. `required` 与 `init`
      • 3.4.2. `required` 与字段
      • 3.4.3. `required` 与继承
      • 3.4.4. 反射与序列化
    • 3.5. C# 11 之前的替代方案
    • 3.6. 典型应用场景
    • 3.7. 限制与常见问题
  • 4. 相关特性回顾
  • 5. 最佳实践与设计建议
  • 6. 总结

1. init 访问器:让不可变对象的创建更灵活

官方文档:init 关键字 - C# reference | Microsoft Learn

C# 语言中,属性(Property)的 set 访问器可以被标记为 init,形成所谓的 “init-only setter”(仅初始化 set 访问器)。

1.1. 概念

init 访问器是 C# 9.0(.NET 5)中引入的一个关键特性,旨在以更灵活的方式支持不可变性。

  • 关键字: init
  • 核心目标: 在提供灵活的对象初始化方式的同时,保证对象状态在初始化后的不可变性。

1.1.1. 语法

init 访问器只能用于属性或索引器的 set 位置:

public class Person
{public string Name { get; init; }   // 自动生成 readonly 字段public int    Age  { get; init; }
}

1.1.2. 语义

init 访问器限制了属性只能在对象初始化阶段(即对象构造器或对象初始化器中)被赋值一次。初始化完成后,任何再次赋值的尝试都会导致编译错误(CS8852)。

1.2. 设计初衷:解决什么问题?

  1. 痛点:在 init 出现之前,实现不可变属性通常依赖 readonly 字段配合只有 get 访问器的属性。这种方式虽然保证了不可变性,但只能在构造函数中赋值,写法繁琐,且无法利用对象初始化器 {} 的简洁语法。
  2. 解决方案init 访问器将“不可变性”与“简洁初始化”完美结合。它既保证了属性在初始化后不可修改,又允许调用方使用对象初始化器方便地一次性设置多个属性值。

1.3. 使用方法

1.3.1. 在对象初始化器中赋值(主要场景)

var person = new Person
{FirstName = "John", // ✅ 正确:在初始化阶段LastName = "Doe",   // ✅ 正确:在初始化阶段Age = 30            // ✅ 正确:在初始化阶段
};

1.3.2. 在构造函数中赋值

public class Person
{public Person(string firstName){FirstName = firstName; // ✅ 正确:构造函数也属于初始化阶段}public string FirstName { get; init; }public string LastName { get; init; }
}

1.3.3. 错误用法:初始化后赋值

var person = new Person { FirstName = "John", LastName = "Doe" };
person.Age = 30; // ❌ 编译错误:CS8852 

1.4. 内部实现原理

  1. 编译器会为每个 init 属性生成一个隐藏的 readonly 字段。
  2. init 访问器在编译后会生成一个普通的 set 方法,但会附加一个特殊的特性标记 [System.Runtime.CompilerServices.IsExternalInit]
  3. 编译器在 IL(中间语言)层面进行检查:只有在对象的构造阶段(如构造函数 .ctor 或对象初始化器生成的辅助方法)才允许调用这个被标记的 set 方法。
  4. 由于这只是一个编译器层面的标记,包含 init 属性的程序集在旧版运行时(如 .NET Framework)上也能加载和运行,只是旧版本的 C# 编译器无法识别和编译 init 语法。

1.5. 与相关特性的对比

特性赋值时机后续可改适用场景
get-only构造函数纯不可变
init构造函数 + 对象初始化器需要对象初始化器语法糖
private set任何成员方法内部可变,外部只读
record 的 init同 initrecord 自动生成 init
required任何初始化阶段(C#11)强制必须赋值

1.6. 高级主题与注意事项

1.6.1. 与 readonly 字段的配合

public class Person
{private readonly string _firstName; // 底层的只读字段public string FirstName{get => _firstName;init => _firstName = value; // init 可以给 readonly 字段赋值}
}

1.6.2. 继承中的使用

基类中定义的 init 属性可以在派生类的构造函数或对象初始化器中进行赋值。

public class Student : Person
{public string Major { get; init; }
}var student = new Student
{FirstName = "Jane", // ✅ 可以初始化基类的 init 属性LastName = "Smith",Major = "Computer Science"
};

1.6.3. “浅”不可变性(Shallow Immutability)

init 只保证属性的引用本身不能被重新赋值。如果属性是一个引用类型(如 List<T>),其内部状态仍然是可变的。

public class Company
{public List<string> Employees { get; init; } = new List<string>();
}var company = new Company { Employees = new List<string> { "John" } };
// 不能再给 Employees 属性分配一个新的列表
// company.Employees = new List<string>(); // ❌ 编译错误// 但是可以修改列表里面的内容!
company.Employees.Add("Jane"); // ✅ 这不会报错!破坏了逻辑上的不可变性Console.WriteLine(company.Employees.Count); // 输出:2
using System.Collections.Immutable;public class Company
{public ImmutableList<string> Employees { get; init; } = ImmutableList<string>.Empty;
}var company = new Company { Employees = ImmutableList.Create("John") };
// 任何修改操作都会返回一个新的集合,原集合不会被改变
var newCompany = company.Employees.Add("Jane"); 

1.6.4. 其他注意事项

  • 反射:可以通过反射绕过编译器的限制来修改 init 属性,但这会破坏其不可变性契约,应谨慎使用。
  • 序列化:现代序列化库(如 System.Text.JsonNewtonsoft.Json 13.0+)都支持反序列化到 init 属性。
using System;
using System.Reflection;
using System.Text.Json;
using Newtonsoft.Json;// 定义包含init属性的模型
public class Person
{public string Name { get; init; }public int Age { get; init; }
}class Program
{static void Main(){// 1. 反射示例Console.WriteLine("=== 反射示例 ===");var person = new Person { Name = "Alice", Age = 30 };Console.WriteLine($"原始值: {person.Name}, {person.Age}");// 通过反射修改init属性var property = typeof(Person).GetProperty("Name");property?.SetValue(person, "Bob");Console.WriteLine($"反射修改后: {person.Name}, {person.Age}");Console.WriteLine("\n=== 序列化示例 ===");// 2. System.Text.Json 序列化Console.WriteLine("1. System.Text.Json 反序列化:");var json = @"{""Name"":""Tom"", ""Age"":25}";var person1 = System.Text.Json.JsonSerializer.Deserialize<Person>(json);Console.WriteLine($"反序列化结果: {person1?.Name}, {person1?.Age}");// 3. Newtonsoft.Json 序列化(需要NuGet包 Newtonsoft.Json 13.0+)Console.WriteLine("2. Newtonsoft.Json 反序列化:");var person2 = JsonConvert.DeserializeObject<Person>(json);Console.WriteLine($"反序列化结果: {person2?.Name}, {person2?.Age}");}
}
=== 反射示例 ===
原始值: Alice, 30
反射修改后: Bob, 30=== 序列化示例 ===
1. System.Text.Json 反序列化:
反序列化结果: Tom, 25
2. Newtonsoft.Json 反序列化:
反序列化结果: Tom, 25

2. record 类型:为数据而生的引用类型

官方文档:record 类型 - C# reference | Microsoft Learn

2.1. 概念

  1. 引入版本:C# 9.0 ( .NET 5)、 C# 10.0 ( **.NET **6)
  2. record 是 一种特殊的引用类型(或从 C# 10.0 开始的值类型),专为封装数据而设计。
  3. 它的特性是值语义(value semantics)和不可变性(immutability)。
  4. 当使用主构造函数声明 record 时,编译器会自动为每个参数生成公共的 init-only 属性,这些参数被称为位置参数

2.2. record class:引用类型记录

默认情况下,record 关键字创建的是一个引用类型记录 (record class)。

2.2.1. 基础语法与编译器生成的内容

简洁声明:

public record Person(string FirstName, string LastName);

反编译后查看代码

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;// Token: 0x02000002 RID: 2
[NullableContext(1)]
[Nullable(0)]
public class Person : IEquatable<Person>
{// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250public Person(string FirstName, string LastName){this.FirstName = FirstName;this.LastName = LastName;base..ctor();}// Token: 0x17000001 RID: 1// (get) Token: 0x06000002 RID: 2 RVA: 0x00002067 File Offset: 0x00000267[CompilerGenerated]protected virtual Type EqualityContract{[CompilerGenerated]get{return typeof(Person);}}// Token: 0x17000002 RID: 2// (get) Token: 0x06000003 RID: 3 RVA: 0x00002073 File Offset: 0x00000273// (set) Token: 0x06000004 RID: 4 RVA: 0x0000207B File Offset: 0x0000027Bpublic string FirstName { get; set; }// Token: 0x17000003 RID: 3// (get) Token: 0x06000005 RID: 5 RVA: 0x00002084 File Offset: 0x00000284// (set) Token: 0x06000006 RID: 6 RVA: 0x0000208C File Offset: 0x0000028Cpublic string LastName { get; set; }// Token: 0x06000007 RID: 7 RVA: 0x00002098 File Offset: 0x00000298[CompilerGenerated]public override string ToString(){StringBuilder stringBuilder = new StringBuilder();stringBuilder.Append("Person");stringBuilder.Append(" { ");if (this.PrintMembers(stringBuilder)){stringBuilder.Append(' ');}stringBuilder.Append('}');return stringBuilder.ToString();}// Token: 0x06000008 RID: 8 RVA: 0x000020E4 File Offset: 0x000002E4[CompilerGenerated]protected virtual bool PrintMembers(StringBuilder builder){RuntimeHelpers.EnsureSufficientExecutionStack();builder.Append("FirstName = ");builder.Append(this.FirstName);builder.Append(", LastName = ");builder.Append(this.LastName);return true;}// Token: 0x06000009 RID: 9 RVA: 0x0000211E File Offset: 0x0000031E[NullableContext(2)][CompilerGenerated]public static bool operator !=(Person left, Person right){return !(left == right);}// Token: 0x0600000A RID: 10 RVA: 0x0000212A File Offset: 0x0000032A[NullableContext(2)][CompilerGenerated]public static bool operator ==(Person left, Person right){return left == right || (left != null && left.Equals(right));}// Token: 0x0600000B RID: 11 RVA: 0x00002140 File Offset: 0x00000340[CompilerGenerated]public override int GetHashCode(){return (EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<FirstName>k__BackingField)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<LastName>k__BackingField);}// Token: 0x0600000C RID: 12 RVA: 0x00002180 File Offset: 0x00000380[NullableContext(2)][CompilerGenerated]public override bool Equals(object obj){return this.Equals(obj as Person);}// Token: 0x0600000D RID: 13 RVA: 0x00002190 File Offset: 0x00000390[NullableContext(2)][CompilerGenerated]public virtual bool Equals(Person other){return this == other || (other != null && this.EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(this.<FirstName>k__BackingField, other.<FirstName>k__BackingField) && EqualityComparer<string>.Default.Equals(this.<LastName>k__BackingField, other.<LastName>k__BackingField));}// Token: 0x0600000F RID: 15 RVA: 0x000021F3 File Offset: 0x000003F3[CompilerGenerated]protected Person(Person original){this.FirstName = original.<FirstName>k__BackingField;this.LastName = original.<LastName>k__BackingField;}// Token: 0x06000010 RID: 16 RVA: 0x00002215 File Offset: 0x00000415[CompilerGenerated]public void Deconstruct(out string FirstName, out string LastName){FirstName = this.FirstName;LastName = this.LastName;}
}

等效于手写下面这个复杂的类:

public record Person
{// 编译器生成的构造函数public Person(string firstName, string lastName){FirstName = firstName;LastName = lastName;}// 编译器生成的属性public string FirstName { get; init; }public string LastName { get; init; }// 编译器生成的解构方法public void Deconstruct(out string firstName, out string lastName){firstName = FirstName;lastName = LastName;}// 编译器生成的Equals方法(基于值的相等性比较)public virtual bool Equals(Person? other){return other is not null &&FirstName == other.FirstName &&LastName == other.LastName;}// 编译器生成的GetHashCode方法public override int GetHashCode(){return HashCode.Combine(FirstName, LastName);}// 编译器生成的ToString方法public override string ToString(){return $"Person {{ FirstName = {FirstName}, LastName = {LastName} }}";}// 编译器生成的复制方法(用于with表达式)protected virtual Person With(bool overwrite, ref Person result){// 实际实现更复杂,这里简化表示if (overwrite){result = this;return result;}return new Person(FirstName, LastName);}// 编译器生成的克隆操作符(用于with表达式)public Person Clone() => this with { };
}

编译器自动生成的关键成员:

  • 主构造函数:用于初始化所有位置属性。
  • init-only 属性:为每个位置参数生成一个公共的 init-only 属性。
  • 值相等性成员:
    • 重写 Equals(object)GetHashCode() 方法,实现基于所有公共属性值的比较。
    • 实现 IEquatable<T> 接口。
  • ToString() 方法:重写 ToString(),输出包含类型名称和所有公共属性值的格式化字符串。
  • Deconstruct() 方法:生成一个解构方法,方便将 record 的属性解构到单独的变量中。
  • with 表达式支持:生成一个隐藏的“拷贝构造函数”和一个 Clone() 方法,以支持非破坏性修改。

2.2.2. 继承

record class 支持继承。派生 record 会继承基 record 的所有成员,并且值相等性比较会包含所有基类和派生类的属性。

public record Employee(string Name, string LastName, string Company) : Person(Name, LastName);

2.2.3. 示例:结合继承与模式匹配

using System;
using System.Collections.Generic;// 基类 record - 图形
public abstract record Shape(string Color, bool IsFilled);// 派生 record - 圆形
public record Circle(string Color, bool IsFilled, double Radius) : Shape(Color, IsFilled)
{public double Area => Math.PI * Radius * Radius;
}// 派生 record - 矩形
public record Rectangle(string Color, bool IsFilled, double Width, double Height) : Shape(Color, IsFilled)
{public double Area => Width * Height;
}// 派生 record - 三角形
public record Triangle(string Color, bool IsFilled, double Base, double Height) : Shape(Color, IsFilled)
{public double Area => 0.5 * Base * Height;
}class Program
{static void Main(){// 创建图形列表var shapes = new List<Shape>{new Circle("Red", true, 5.0),new Rectangle("Blue", false, 4.0, 6.0),new Triangle("Green", true, 3.0, 4.0),new Circle("Yellow", false, 3.0),new Rectangle("Black", true, 5.0, 5.0)};// 使用模式匹配处理不同图形foreach (var shape in shapes){Console.WriteLine(ProcessShape(shape));}Console.WriteLine();// 计算总面积double totalArea = CalculateTotalArea(shapes);Console.WriteLine($"Total area of all shapes: {totalArea:F2}");}// 使用模式匹配处理不同图形static string ProcessShape(Shape shape) => shape switch{Circle c when c.Radius > 4 => $"Large circle with area {c.Area:F2}",Circle c => $"Circle with radius {c.Radius} and area {c.Area:F2}",Rectangle r when r.Width == r.Height => $"Square with side {r.Width} and area {r.Area:F2}",Rectangle r => $"Rectangle with area {r.Area:F2}",Triangle t => $"Triangle with area {t.Area:F2}",_ => "Unknown shape"};// 计算所有图形的总面积static double CalculateTotalArea(List<Shape> shapes){double total = 0;foreach (var shape in shapes){total += shape switch{Circle c => c.Area,Rectangle r => r.Area,Triangle t => t.Area,_ => 0};}return total;}
}
Large circle with area 78.54
Rectangle with area 24.00
Triangle with area 6.00
Circle with radius 3 and area 28.27
Square with side 5 and area 25.00Total area of all shapes: 141.81

2.2.4. 与普通 class 的内存与性能对比

虽然 record classclass 都是引用类型,在堆上分配,但它们的设计哲学导致了不同的性能特征。

内存布局

recordclass 实例在内存中的基本布局是相同的:变量存储一个指向堆上对象的引用。

示例内存表示:

// 无论是 class 还是 record,变量 'p' 都在栈上存储一个指向堆的地址
PersonRecord p1 = new PersonRecord("John", "Doe"); // record
PersonClass p2 = new PersonClass("John", "Doe");  // class// 栈 (Stack)     堆 (Heap)
// [ p1: 0x1234 ] -> [ 0x1234: PersonRecord { FirstName="John", LastName="Doe" } ]
// [ p2: 0x5678 ] -> [ 0x5678: PersonClass { FirstName="John", LastName="Doe" } ]
关键差异及其影响
特性record class普通 class对内存和性能的影响
不可变性默认不可变。位置参数生成的属性是 init-only默认可变。属性通常有 set 访问器。record
• 安全:对象状态不会意外改变,适合在多线程间共享,无需额外锁,减少并发开销。
• 性能:由于不可变,编译器、运行时和 GC 可以进行更多优化(如更积极的栈上分配逃逸分析)。

class
• 灵活:可随时修改状态。
• 开销:在多线程环境中需要同步机制(如 lock),带来性能开销。
值语义相等性基于值。比较所有属性的值。编译器生成 EqualsGetHashCode基于引用。比较内存地址(除非重写 Equals/GetHashCode)。record
• 计算开销:比较两个 record 需要遍历并比较所有属性的值。对于属性很多的 recordEqualsGetHashCode 的调用(如在 HashSet 或字典中)会比简单的引用比较更耗时。
• 哈希码:GetHashCode 需要基于所有属性计算,也可能更耗时,但保证了基于值的正确性。
with 表达式核心特性。用于非破坏性修改。不支持。record
• 分配开销:original with { Prop = value } 总会创建一个全新的对象。频繁使用可能会增加 GC 压力,因为会产生更多短期存活的对象。
• 功能优势:这是实现不可变数据模型的必要代价,带来了状态清晰、线程安全等好处。
ToString 方法编译器自动生成,输出所有属性及其值。默认返回类型名称。record
• 计算开销:生成格式化字符串需要计算和内存分配。如果频繁调用(如在日志中),可能会有性能影响。不过,这通常是开发阶段的行为。
总结与最佳实践
  1. 性能权衡
    • **record**的代价:Equals/GetHashCode 的计算成本、with 表达式带来的额外分配。
    • **record**的收益:线程安全、代码简洁、意图清晰、易于推理。
  2. 内存分配成本:单次 new 操作的成本基本相同。
  3. GC 压力record 的不可变性(特别是 with 表达式)可能会产生更多短期对象,从而增加 GC 压力。不过,现代 .NET GC 对处理这类对象的效率非常高。
  4. 最佳实践
    • 优先使用record:对于 DTO、值对象、API 模型等数据载体,优先使用 record
    • 性能敏感场景需测量:在高性能场景下,如果怀疑 record 是瓶颈,请使用 Benchmark.NET 等工具进行性能分析,不要凭空猜测。

2.3. record struct:值类型记录 (C# 10.0+)

使用 record struct 关键字可以定义值类型的记录,它结合了 struct 的性能优势和 record 的便利性。

示例声明:

public readonly record struct Point(double X, double Y, double Z);

等效的 struct 声明:

public record struct Point
{public double X { get; init; }public double Y { get; init; }public double Z { get; init; }
}

说明:

  • record struct 是值类型,分配在栈上(或作为其他对象的一部分),适用于轻量级数据结构。
  • 默认情况下,record struct 的位置属性是可变的get; set;),这与 record class 不同。可以通过添加 readonly 关键字 (public readonly record struct Point) 使其变为完全不可变。

2.4. record class vs record struct

特性record classrecord struct
类型引用类型值类型
设计哲学安全第一(类比法律文书)性能与控制第一(类比实验草稿)
核心目标代表一个完整的、身份由其值定义的实体。 安全共享和线程安全是首要目标。代表一个轻量的数据快照。 提供灵活性,让开发者根据场景选择可变性或不可变性。
位置属性默认行为不可变(init-only properties)可变(除非显式设为 readonly
使用 readonly不适用(已经是不可变的)添加 `readonly` 关键字可使其变为完全不可变,与 `record class` 行为一致。
继承支持不支持
典型场景领域模型、DTO、API 参数/返回、消息、哈希表键高性能计算、图形、游戏开发中的数学对象(坐标、向量等)、 任何需要结构体性能且想获得记录便利性的场景

  • 区别record class引用类型record struct值类型。这是决定两者所有差异的根本原因,决定了它们在内存中的存储方式、赋值行为以及默认的相等性比较逻辑。
  • 可变性
    • record class 默认提供不可变性,这是其设计核心,旨在创建安全、可共享的数据模型。
    • record struct 默认是可变的,提供了灵活性。如果需要不可变性,必须显式使用 readonly record struct 声明。
  • 继承record class 支持继承,可以构建层次化的数据模型。record struct 是值类型,不支持继承。
  • 选择建议
    • 当需要表示一个逻辑上的“实体”(如一个人、一张订单),并且希望它是线程安全的、可作为字典键或用于基于值的比较时,应选择 record class
    • 当你需要定义一个轻量级的、数据量小的数据结构(如一个点、一个颜色RGB值),并且优先考虑性能(减少堆内存分配和垃圾回收)和灵活性时,应选择 record structreadonly record struct

2.5. with 表达式:非破坏性修改

2.5.1. 概述

with 表达式是为不可变数据模型设计的核心特性。

它允许你基于一个现有实例,创建一个新的、被修改过的副本,而原始实例保持不变。这种行为称为非破坏性突变(non-destructive mutation)

2.5.2. 语法

var newRecord = existingRecord with { Property1 = value1, Property2 = value2, ... };

2.5.3. 适用类型

  • record class
  • record struct
  • 任何提供了“拷贝构造函数”的 类型。

2.5.4. 实现原理

编译器会自动为 record 类型生成一个受保护的拷贝构造函数和一个用于支持 with 表达式的克隆方法。当你使用 with 表达式时,编译器会生成调用这些成员的代码来创建新对象。

public record Person
{// 编译器生成的主构造函数和属性public Person(string FirstName, string LastName, int Age){this.FirstName = FirstName;this.LastName = LastName;this.Age = Age;}public string FirstName { get; init; }public string LastName { get; init; }public int Age { get; init; }// 编译器生成的受保护拷贝构造函数protected Person(Person existing){// 复制所有字段this.FirstName = existing.FirstName;this.LastName = existing.LastName;this.Age = existing.Age;}// 编译器生成的用于支持 `with` 的合成方法(“Clone”方法)// 这个方法不是直接调用的,而是被 `with` 表达式使用的桥梁。public virtual Person With() => new Person(this); // 调用拷贝构造// ... 其他生成的成员(Equals, GetHashCode, ToString, Deconstruct)...
}

2.5.5. 示例

基本用法
public record Person(string FirstName, string LastName, int Age);// 创建原始对象
var originalPerson = new Person("John", "Doe", 30);// 使用 with 表达式创建新对象,仅修改 Age 属性
var youngPerson = originalPerson with { Age = 25 };Console.WriteLine(originalPerson); // 输出: Person { FirstName = John, LastName = Doe, Age = 30 }
Console.WriteLine(youngPerson);    // 输出: Person { FirstName = John, LastName = Doe, Age = 25 }// 同时修改 FirstName 和 Age
var modifiedPerson = originalPerson with { FirstName = "Jane", Age = 28 };
Console.WriteLine(modifiedPerson); // 输出: Person { FirstName = Jane, LastName = Doe, Age = 28 }
嵌套记录的使用
public record Address(string Street, string City);
public record Employee(string Name, Address Address, string Department);var emp1 = new Employee("John", new Address("123 Main St", "Seattle"), "IT");// 修改嵌套记录的属性
var emp2 = emp1 with { Address = emp1.Address with { City = "Redmond" } };Console.WriteLine(emp1);
// 输出: Employee { Name = John, Address = Address { Street = 123 Main St, City = Seattle }, Department = IT }Console.WriteLine(emp2);
// 输出: Employee { Name = John, Address = Address { Street = 123 Main St, City = Redmond }, Department = IT }
在普通 struct 中启用 with 表达式

通过提供一个自定义的拷贝构造函数,你也可以让普通的 struct 支持 with 表达式。这对于需要 struct 的性能但又想获得 with 表达式便利性的场景非常有用。

public struct Vertex
{public double X { get; init; }public double Y { get; init; }public double Z { get; init; }// 计算得到的属性public double Magnitude { get; } // 没有 setter 或 init-only// 主构造函数public Vertex(double x, double y, double z){X = x;Y = y;Z = z;// Magnitude 需要在构造函数中计算Magnitude = Math.Sqrt(X * X + Y * Y + Z * Z);}// !!!自定义复制构造函数!!!// 参数必须与结构体类型相同,通常命名为 'original'public Vertex(Vertex original){// 1. 复制所有字段/属性this = original; // 这是关键的一步:使用现有实例进行整体赋值// 2. 然后你可以在这里进行任何自定义逻辑// 例如:日志记录、通知、或者重新计算依赖于其他字段的只读属性(但此例中不需要,因为 this=original 已经复制了 Magnitude)// Console.WriteLine("A copy of Vertex was created.");}public override string ToString() => $"({X}, {Y}, {Z}) with magnitude {Magnitude:F2}";
}class Program
{static void Main(){Vertex v1 = new Vertex(1, 2, 2);Console.WriteLine(v1); // 输出: (1, 2, 2) with magnitude 3.00// 由于定义了复制构造函数,Vertex 现在支持 with 表达式Vertex v2 = v1 with { X = 5 }; // 修改 X,Y 和 Z 保持不变Console.WriteLine(v2); // 输出: (5, 2, 2) with magnitude 5.74// 编译器会将上面的 with 表达式转换为类似下面的代码:// Vertex temp = new Vertex(v1); // 调用自定义的复制构造函数// temp.X = 5;                   // 在对象初始化器中设置属性// Vertex v2 = temp;}
}

2.5.6. 典型应用场景

  • 状态更新:在函数式或响应式编程中,用于创建新的状态,而非修改现有状态。
  • 数据转换:基于一个模板对象,生成多个只有细微差别的相似对象。
  • 测试数据构建:轻松创建测试数据的各种变体。

2.6. 特性总结

  1. 简洁的语法:通过位置参数极大减少了定义数据模型的样板代码。
  2. 值语义:自动实现基于所有公共属性的相等性比较。
  3. 不可变性record class 默认生成 init-only 属性,鼓励不可变的设计模式。
  4. 非破坏性修改:通过 with 表达式,可以安全、便捷地创建对象的修改副本。
  5. 内置格式化:自动重写的 ToString() 方法便于调试和日志记录。
  6. 继承支持record class 支持继承,并且派生的 record 也能正确地进行值比较。
  7. 正确性与可维护性:不可变性和值语义有助于避免因意外状态修改而导致的错误,使代码更容易推理和测试。
  8. 线程安全:不可变对象在多线程环境中可以安全共享,无需额外的同步锁。

2.8. 典型应用场景

  • 数据传输对象 (DTOs)

    在不同层或服务之间传递数据的理想选择。

  • 领域模型中的值对象 (Value Objects)

    MoneyAddress 等,这些对象的身份由其属性值决定,而非唯一的 ID。

  • API 请求/响应模型

    在 Web API 中定义输入和输出的数据结构。

  • CQRS 中的命令和查询

    在 MediatR 等库中,record 是定义命令和查询的完美选择。

  • 事件/消息

    在事件驱动或微服务架构中,用于定义不可变的事件或消息体。

3. required 修饰符:强制初始化的契约

3.1. 概念

  • 引入版本:C# 11 (.NET 7)
  • 核心目标:强制调用方在创建对象时,必须为标记为 required 的成员(属性或字段)显式赋值。
  • 作用:将“必须赋值”的契约从构造函数参数扩展到了对象初始化器,增强代码的健壮性,尤其是在与可空引用类型(NRT)结合使用时。

3.2. 基本用法

public class Person
{public required string FirstName { get; init; }public required string LastName  { get; init; }public int? Age { get; init; }     // 不标记则为可选
}
// 正确:使用对象初始化器为所有 required 成员赋值
var validPerson = new Person { FirstName = "John", LastName = "Doe" };// 编译错误!CS9035: 缺少所需成员 "Person.FirstName" 的初始化
// var invalidPerson = new Person { LastName = "Doe" };// 编译错误!CS9035: 缺少所需成员 "Person.LastName" 的初始化
// var invalidPerson2 = new Person { FirstName = "John" };// 编译错误!不能使用传统的构造函数(除非构造函数自己也设置了这些值)
// var invalidPerson3 = new Person();

3.3. 与构造函数的协作

当类中同时存在 required 成员和自定义构造函数时,需要特别处理。

3.3.1. 使用 [SetsRequiredMembers] 特性

如果一个构造函数已经确保为所有 required 成员都赋了值,你可以使用 [SetsRequiredMembers] 特性来通知编译器。这样,调用该构造函数的代码就不再需要使用对象初始化器来为 required 成员赋值。

using System.Diagnostics.CodeAnalysis;var person = new Person("John"); // Error: Name is required
public class Person
{public required string Name { get; init; }public required int Age { get; init; }[SetsRequiredMembers]public Person(string name){Name = name;}
}

3.3.2. 多个构造函数场景

  • 未标记 [SetsRequiredMembers] 的构造函数

    如果调用了一个没有此特性的构造函数,编译器仍然会要求你在对象初始化器中为所有 required 成员赋值(即使构造函数内部已经为部分成员赋值)。

  • 构造函数链

    如果一个构造函数通过 : this(): base() 调用了另一个标记了 [SetsRequiredMembers] 的构造函数,那么它也隐式地满足了 required 成员的赋值要求。

public class Person
{public required string FirstName { get; init; }public required string LastName { get; init; }public int? Age { get; init; }public string? Title { get; set; }// 构造函数1:无参构造函数public Person() { }// 构造函数2:只设置FirstNamepublic Person(string firstName){FirstName = firstName;}// 构造函数3:设置所有required属性[SetsRequiredMembers]public Person(string firstName, string lastName){FirstName = firstName;LastName = lastName;}// 构造函数4:设置所有required属性和可选属性[SetsRequiredMembers]public Person(string firstName, string lastName, int age){FirstName = firstName;LastName = lastName;Age = age;}
}

// 编译错误:缺少对 required 成员的初始化
// var p1 = new Person();// 编译错误:缺少对 LastName 的初始化
// var p2 = new Person("John");// 编译错误:LastName 必须在初始化列表中初始化
// var p3 = new Person("John") { LastName = "Doe" };// 正确:构造函数标记了 [SetsRequiredMembers]
var p4 = new Person("John", "Doe");// 正确:构造函数标记了 [SetsRequiredMembers]
var p5 = new Person("John", "Doe", 30);

3.3.3. 建议

  1. 优先使用主构造函数:对于 record 类型,尽可能通过主构造函数来满足 required 成员的初始化。
  2. 慎用**[SetsRequiredMembers],该特性违背设计原则,易引发成员缺失问题。**​
  3. 考虑工厂模式:对于复杂的对象创建逻辑,可以考虑使用静态工厂方法来替代多个重载的构造函数。

3.4. 高级主题与注意事项

3.4.1. requiredinit

requiredinit组合表达了“这个属性必须在初始化时赋值,且赋值后不可更改”的清晰意图。

public class ImmutablePerson
{public required string FirstName { get; init; } // 只能在初始化时赋值public required string LastName { get; init; }
}var person = new ImmutablePerson { FirstName = "John", LastName = "Doe" };
// person.FirstName = "Jane"; // 这行会编译错误,因为 init 不允许之后修改

3.4.2. required 与字段

required 也可以用于实例字段,但不能用于 staticconst 字段。

public struct Point
{public required double X;public required double Y;
}

3.4.3. required 与继承

  • 派生类必须满足基类的 required 成员要求。
  • 派生类可以添加自己的 required 成员。
  • 如果派生类的构造函数希望完全负责所有(包括基类)required 成员的初始化,它必须调用一个标记了 [SetsRequiredMembers] 的基类构造函数,或者自己标记 [SetsRequiredMembers] 并为所有成员赋值。
// dotnet run
// 编译器:.NET 7+ / C# 11using System.Diagnostics.CodeAnalysis;namespace RequiredInheritanceDemo;// 1) 基类
public abstract class Animal
{// 基类要求:创建时必须给 Name 赋值public required string Name { get; init; }
}// 2) 派生类
public class Cat : Animal
{// 规则1:不能移除基类 required——这里如果写“override string Name”会 CS9030。//public override string Name { get; init; }// 规则2:派生类可以新增自己的 required 成员public required int Lives { get; init; }// 规则3:如果派生构造函数想让调用端“跳过”基类 required 成员,//       必须同时给基类成员赋值,并标记 [SetsRequiredMembers][SetsRequiredMembers]   // 告诉编译器“这个 ctor 会搞定所有 required” ,如果没有这个标记,编译器会报cs9035错。public Cat(string name = "DefaultCat", int lives = 9){// 先满足基类 requiredName = name; //如果注释掉,cs8618提示,在退出构造函数时,不可为 null 的 属性 "Name" 必须包含非 null 值。请考虑添加 "required" 修饰符或将该 属性 声明为可为 null。// 再满足派生类自己新增的 requiredLives = lives;}
}// 3) 测试
internal static class Program
{private static void Main(){// 3-a 正常对象初始化器写法——必须给所有 required 赋值var a1 = new Cat { Name = "Tom", Lives = 7 };Console.WriteLine($"a1: {a1.Name}, lives={a1.Lives}");// 3-b 使用“跳过”构造函数——不需要再写初始化器var a2 = new Cat();               // 编译通过var a3 = new Cat("Garfield");     // 编译通过Console.WriteLine($"a2: {a2.Name}, lives={a2.Lives}");Console.WriteLine($"a3: {a3.Name}, lives={a3.Lives}");}
}
a1: Tom, lives=7
a2: DefaultCat, lives=9
a3: Garfield, lives=9

3.4.4. 反射与序列化

  • 反射:可以通过 RequiredMemberAttribute 在运行时检查成员是否是必需的。
  • 序列化:现代序列化库(如 System.Text.Json)支持 required 成员,如果在反序列化时缺少必需成员,会抛出异常。
using System;
using System.Linq;
using System.Reflection;
using System.Text.Json;// 定义包含 required 成员的类
public class Person
{public required string Name { get; set; }public required int Age { get; set; }public string? Address { get; set; } // 非必需成员
}class Program
{static void Main(){// 1. 反射示例:检查必需成员Console.WriteLine("=== 反射检查 ===");foreach (var prop in typeof(Person).GetProperties()){// 检查是否包含 RequiredMemberAttributevar isRequired = prop.CustomAttributes.Any(a => a.AttributeType.Name.Contains("RequiredMember"));Console.WriteLine($"{prop.Name}: {(isRequired ? "必需" : "可选")}");}// 2. 序列化示例Console.WriteLine("\n=== 序列化测试 ===");// 有效JSON(包含所有必需成员)string validJson = @"{""Name"": ""张三"",""Age"": 25,""Address"": ""北京""}";// 无效JSON(缺少Age成员)string invalidJson = @"{""Name"": ""张三""}";try{// 成功反序列化var person = JsonSerializer.Deserialize<Person>(validJson);Console.WriteLine($"成功解析:{person?.Name}, {person?.Age}岁");// 会抛出异常var invalidPerson = JsonSerializer.Deserialize<Person>(invalidJson);}catch (JsonException ex){Console.WriteLine($"解析失败:{ex.Message}");}}
}
=== 反射检查 ===
Name: 必需
Age: 必需
Address: 可选=== 序列化测试 ===
成功解析:张三, 25岁
解析失败:JSON deserialization for type 'Person' was missing required properties, including the following: Age

3.5. C# 11 之前的替代方案

在没有 required 关键字时,开发者通常使用以下模式来强制属性初始化:

  1. 构造函数参数:最经典的方式,但在参数过多时会变得臃肿。
  2. Fluent Builder 模式:提供了良好的可读性,但需要编写额外的构建器类。
  3. 可空引用类型(NRT)警告:依赖编译器警告来发现未初始化的非空属性,但并非强制错误。

3.6. 典型应用场景

  1. DTOs 和 API 模型:确保客户端传递了所有必需的字段。
  2. 配置对象:保证应用程序启动时加载了所有必需的配置项(如连接字符串)。
  3. 领域模型:强制实体在创建时就必须包含其核心属性(如 Order 必须有 CustomerId)。
  4. 配合可空引用类型 (NRT)required 是对 NRT 的完美补充,它将“非空”的承诺从编译时警告升级为了强制的初始化要求。

3.7. 限制与常见问题

  • 版本要求:需要 C# 11 / .NET 7 或更高版本。
  • 接口:不能在接口中定义 required 成员。
  • 默认值required 成员不能有默认值,因为这与“必须由调用方显式赋值”的语义相悖。

4. 相关特性回顾

为了更全面地理解 C# 中的不可变性生态,以下几个相关特性值得回顾:

  • 只读属性 (Getter-only Properties)

    在 C# 6.0 中引入,只能在构造函数中赋值。init 可以看作是它的演进版本,增加了对对象初始化器的支持。

    public class OldSchoolImmutable
    {public string Name { get; } // 没有 setterpublic OldSchoolImmutable(string name){Name = name; // 只能在构造函数中赋值}
    }
    
  • readonly** 结构体 (readonly struct)**

    C# 7.2 引入,保证结构体的所有实例成员都不会修改其状态,是实现高性能、不可变值类型的关键。

    public readonly struct Point
    {public Point(double x, double y) {X = x;Y = y;}public double X { get; } // 属性也是只读的public double Y { get; }
    }
    
  • 不可变集合 (Immutable Collections)

    位于 System.Collections.Immutable 命名空间,提供了如 ImmutableList<T>ImmutableDictionary<TKey, TValue> 等线程安全的、真正不可变的集合类型。

    它们是实现深层不可变性的重要工具。

    public class DataContainer
    {public ImmutableArray<int> Scores { get; init; } = ImmutableArray<int>.Empty;
    }
    // 初始化后,无法修改 Scores 数组中的元素
    

5. 最佳实践与设计建议

  1. 优先选择不可变性:在设计 DTO、配置对象和值对象时,默认使用 recordinit 来创建不可变类型。这能从根本上减少 bug,提升线程安全性。
  2. required 明确契约:对于任何在对象生命周期内都必须存在的属性,使用 required 来强制初始化,让编译器成为你数据完整性的守护者。
  3. 警惕“浅”不可变initreadonly 只保证引用本身不变。如果属性是可变引用类型(如 List<T>),考虑使用不可变集合(ImmutableList<T>)或将其封装在只读接口(IReadOnlyList<T>)后暴露,以实现更深层次的不可变性。
  4. 性能考量:不可变对象在修改时会创建新实例(如 with 表达式)。在绝大多数业务场景中,这带来的健壮性收益远超其微小的性能开销。但在极端性能敏感的热路径代码中,仍需进行性能评估。

6. 总结

C# 的 initrecordrequired 特性协同工作,旨在简化数据模型的创建,并强制实现不可变性和必需初始化。

  • init 访问器是实现不可变性的基石,它允许在享受对象初始化器便利性的同时,锁定对象创建后的状态。
  • recordinit 的基础上更进一步,通过自动生成样板代码(如值相等性比较、ToString),简化了数据载体的定义,是 DTO 和领域值对象的最佳选择。
  • required修饰符则为初始化过程提供了编译期的安全保障,确保了对象在创建时就处于一个有效的、完整的状态,从而显著减少了运行时的 NullReferenceException
  • initrequiredrequired 强制必须初始化,init 确保初始化后不可更改。两者结合,提供了最强的初始化契约。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/96781.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96781.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每天五分钟深度学习:神经网络的权重参数如何初始化

本文重点 在逻辑回归的时候,我们可以将神经网络的权重参数初始化为0(或者同样的值),但是如果我们将神经网络的权重参数初始化为0就会出问题,上节课程我们已经进行了简单的解释,那么既然初始化为0不行,神经网络该如何进行参数初始化呢?神经网络的权重参数初始化是模型训…

[论文阅读] 告别“数量为王”:双轨道会议模型+LS,破解AI时代学术交流困局

告别“数量为王”&#xff1a;双轨道会议模型LS&#xff0c;破解AI时代学术交流困局 论文信息信息类别具体内容论文原标题From Passive to Participatory: How Liberating Structures Can Revolutionize Our Conferences主要作者及机构1. Daniel Russo&#xff08;丹麦奥尔堡大…

趣味学solana(介绍)

你就是那个关键的“守门员”&#xff01; 为了方便理解Solana&#xff0c;我们把Solana 想象成一个巨大的、24小时不停歇的足球联赛。成千上万的足球运动员&#xff08;用户&#xff09;在不停地传球、射门&#xff08;发送交易&#xff09;&#xff0c;而整个比赛的结果必须被…

分布式事务性能优化:从故障现场到方案落地的实战手记(三)

第三部分&#xff1a;混合场景攻坚——从“单点优化”到“系统协同” 有些性能问题并非单一原因导致&#xff0c;而是锁竞争与事务耗时共同作用的结果。以下2个案例&#xff0c;展示综合性优化策略。 案例7&#xff1a;基金申购的“TCC性能陷阱”——从全量预留到增量确认 故障…

规则系统架构风格

考题 某公司拟开发一个VIP管理系统,系统需要根据不同商场活动,不定期更新VIP会员的审核标准和VIP折扣系统。针对上述需求,采用(__)架构风格最为合适。 A. 规则系统 B. 管道-过滤器风格 C. 事件驱动 D. 分层 一、什么是规则系统架构风格? 规则系统架构风格是一种将应…

kubeadm搭建生产环境的单master多node的k8s集群

k8s环境规划: podSubnet&#xff08;pod 网段&#xff09; 10.20.0.0/16 serviceSubnet&#xff08;service 网段&#xff09;: 10.10.0.0/16 实验环境规划: 操作系统&#xff1a;centos7.9 配置&#xff1a; 4G 内存/4核CPU/40G 硬盘 网络&#xff1a;NAT K8s集群角色ip主…

React Device Detect 完全指南:构建响应式跨设备应用的最佳实践

前言 在现代 Web 开发中&#xff0c;设备检测是一个至关重要的功能。不同的设备&#xff08;手机、平板、桌面&#xff09;有着不同的屏幕尺寸、交互方式和性能特点&#xff0c;因此需要针对性地提供不同的用户体验。react-device-detect 是一个专门为 React 应用设计的设备检…

Spark专题-第一部分:Spark 核心概述(2)-Spark 应用核心组件剖析

这一篇依然是偏理论向的内容&#xff0c;用两篇理论搭建起Spark的框架&#xff0c;让读者有个基础的认知&#xff0c;下一篇就可以开始sql的内容了 第一部分&#xff1a;Spark 核心概述&#xff08;2&#xff09; Spark 应用核心组件剖析 1. Job, Stage, Task 的三层架构 理解 …

KMP 字符串hash算法

kmp算法 最大相同真前后缀&#xff1a; 如 ababa的最大真前后缀为aba&#xff0c; 而不是ababa&#xff08;真前后缀与真子集类似&#xff0c;不可是本身&#xff0c;不然没意义&#xff09; 所以next[1] 0&#xff1b;//string的下标从1开始 kmp模拟 next初始化&#xff…

HOT100--Day22--74. 搜索二维矩阵,34. 在排序数组中查找元素的第一个和最后一个位置,33. 搜索旋转排序数组

HOT100–Day22–74. 搜索二维矩阵&#xff0c;34. 在排序数组中查找元素的第一个和最后一个位置&#xff0c;33. 搜索旋转排序数组 每日刷题系列。今天的题目是《力扣HOT100》题单。 题目类型&#xff1a;二分查找。 关键&#xff1a; 今天的题目都是“多次二分” 74题&#xf…

Java分布式锁实战指南:从理论到实践

Java分布式锁实战指南&#xff1a;从理论到实践 前言 在分布式系统中&#xff0c;传统的单机锁机制无法满足跨进程、跨机器的同步需求。分布式锁应运而生&#xff0c;成为保证分布式系统数据一致性的关键技术。本文将全面介绍Java中分布式锁的实现方式和最佳实践。 1. 分布式锁…

(二叉树) 本节目标 1. 掌握树的基本概念 2. 掌握二叉树概念及特性 3. 掌握二叉树的基本操作 4. 完成二叉树相关的面试题练习

二叉树1. 树型结构&#xff08;了解&#xff09;1.1 概念1.2 概念&#xff08;重要&#xff09;1.3 树的表示形式&#xff08;了解&#xff09;1.4 树的应用2. 二叉树&#xff08;重点&#xff09;2.1 概念2.2 两种特殊的二叉树2.3 二叉树的性质2.4 二叉树的存储2.5 二叉树的基…

【Zephyr电源与功耗专题】13_PMU电源驱动介绍

文章目录前言一、PMU系统介绍二、Zephyr系统下驱动PMU的组成2.1&#xff1a;PMU系统在Zephyr上包括五大部分&#xff1a;2.2&#xff1a;功能说明2.3&#xff1a;B-core功能说明(Freertos)三、PMU各驱动API详解3.1:Power_domain3.1.1&#xff1a;初始化3.1.2&#xff1a;rpmsg回…

华清远见25072班网络编程学习day5

作业0> 将IO多路复用实现TCP并发服务器实现一遍程序源码&#xff1a;#include <25072head.h> #define SER_IP "192.168.153.128" //服务器ip地址 #define SER_PORT 8888 //服务器端口号 int main(int argc, const char *argv[]) {//1、创建一个…

【数据结构--顺序表】

顺序表和链表 1.线性表&#xff1a; 线性表是n个具有相同特性&#xff08;相同逻辑结构&#xff0c;物理结构&#xff09;的数据元素的有限序列。常见的线性表有&#xff1a;顺序表&#xff0c;链表&#xff0c;栈&#xff0c;队列&#xff0c;字符串…线性表在逻辑上是线性结构…

【PyTorch】图像多分类部署

如果需要在独立于训练脚本的新脚本中部署模型&#xff0c;这种情况模型和权重在内存中不存在&#xff0c;因此需要构造一个模型类的对象&#xff0c;然后将存储的权重加载到模型中。加载模型参数&#xff0c;验证模型的性能&#xff0c;并在测试数据集上部署模型from torch imp…

FS950R08A6P2B 双通道汽车级IGBT模块Infineon英飞凌 电子元器件核心解析

一、核心解析&#xff1a;FS950R08A6P2B 是什么&#xff1f;1. 电子元器件类型FS950R08A6P2B 是英飞凌&#xff08;Infineon&#xff09; 推出的一款 950A/800V 双通道汽车级IGBT模块&#xff0c;属于功率半导体模块。它采用 EasyPACK 2B 封装&#xff0c;集成多个IGBT芯片和二…

【系列文章】Linux中的并发与竞争[05]-互斥量

【系列文章】Linux中的并发与竞争[05]-互斥量 该文章为系列文章&#xff1a;Linux中的并发与竞争中的第5篇 该系列的导航页连接&#xff1a; 【系列文章】Linux中的并发与竞争-导航页 文章目录【系列文章】Linux中的并发与竞争[05]-互斥量一、互斥锁二、实验程序的编写2.1驱动…

TensorRT 10.13.3: Limitations

Limitations Shuffle-op can not be transformed to no-op for perf improvement in some cases. For the NCHW32 format, TensorRT takes the third-to-last dimension as the channel dimension. When a Shuffle-op is added like [N, ‘C’, H, 1] -> [‘N’, C, H], the…

Python与Go结合

Python与Go结合的方法Python和Go可以通过多种方式结合使用&#xff0c;通常采用跨语言通信或集成的方式。以下是几种常见的方法&#xff1a;使用CFFI或CGO进行绑定Python可以通过CFFI&#xff08;C Foreign Function Interface&#xff09;调用Go编写的库&#xff0c;而Go可以通…