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
英文地址,請注明出處:http://en.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可以通…