最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#中分部類和分部方法(partial)

 更新時(shí)間:2026年03月05日 10:25:55   作者:武藤一雄  
分部類(partial class)是C#中用于解決機(jī)器生成代碼與人工編寫代碼沖突的一種特性,主要用于自動(dòng)生成的代碼場(chǎng)景,本文給大家介紹C#中分部類和分部方法(partial),感興趣的朋友跟隨小編一起看看吧

分部類(Partial Class).cs

這個(gè)特性不是用來解決業(yè)務(wù)邏輯混亂的,而是為了解決機(jī)器生成代碼人工編寫代碼之間的沖突。

核心應(yīng)用場(chǎng)景

  1. 自動(dòng)生成的代碼(Source Generators / Designer) 這是最主要的使用場(chǎng)景。當(dāng)你使用 Entity Framework (DB First)、或者早期的 WinForms/WPF 時(shí),IDE 會(huì)根據(jù)數(shù)據(jù)庫或 UI 設(shè)計(jì)圖自動(dòng)生成大量的 C# 代碼。
    1. 痛點(diǎn):如果你直接在自動(dòng)生成的類里寫邏輯,下次重新生成時(shí),你的代碼會(huì)被覆蓋。
    2. 方案:機(jī)器寫一個(gè) partial class 放在 A 文件,你寫一個(gè) partial class 放在 B 文件。互不干擾,平安無事。
  2. 多人協(xié)作開發(fā) 當(dāng)一個(gè)類(比如一個(gè)極其復(fù)雜的業(yè)務(wù)控制器)由于歷史原因變得非常龐大時(shí),雖然最好的做法是重構(gòu),但在短期內(nèi)為了避免 Git 合并沖突(Merge Conflict),可以使用分部類讓不同的人在不同的文件里工作。
  3. 代碼組織與關(guān)注點(diǎn)分離 有時(shí)候一個(gè)類需要實(shí)現(xiàn)多個(gè)復(fù)雜的接口,或者內(nèi)部包含大量的常量定義。你可以通過分部類將“接口實(shí)現(xiàn)”、“私有成員”、“公共 API”分別放在不同的文件中,提高可讀性。

代碼示例

假設(shè)你有一個(gè)用戶類,一分部由數(shù)據(jù)庫工具生成,一分部是你自己寫的驗(yàn)證邏輯。

文件 1: User.Generated.cs (機(jī)器生成,不要?jiǎng)?

public partial class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

文件 2: User.Logic.cs (你寫的邏輯)

public partial class User
{
    public bool Validate()
    {
        return !string.IsNullOrEmpty(Name);
    }
}

在 C# 中,您可以使用 partial 關(guān)鍵字將類、結(jié)構(gòu)、方法或接口的實(shí)現(xiàn)拆分到多個(gè) .cs 文件中。編譯器在編譯程序時(shí)會(huì)將來自多個(gè) .cs 文件的所有實(shí)現(xiàn)組合起來。

考慮以下包含 Employee 類的 EmployeeProps.csEmployeeMethods.cs 文件。

// EmployeeProps
 public partial class Employee
 {
     public int EmpId { get; set; }
     public string Name { get; set; }
 }
// EmployeeMethods
 public partial class Employee
 {
     //constructor
     public Employee(int id, string name){
         this.EmpId = id;
         this.Name = name;
     }
     public void DisplayEmpInfo() {
         Console.WriteLine(this.EmpId + " " this.Name);
     }
 }

上面,EmployeeProps.cs 包含 Employee 類的屬性,而 EmployeeMethods.cs 包含 Employee 類的所有方法。這些文件將被編譯成一個(gè) Employee 類。

示例:組合類

 public class Employee
 {
     public int EmpId { get; set; }
     public string Name { get; set; }
     public Employee(int id, string name){
         this.EmpId = id;
         this.Name = name;
     }
     public void DisplayEmpInfo(){
         Console.WriteLine(this.EmpId + " " this.Name );
     }
 }

分部類的規(guī)則

  • 所有分部類定義必須在相同的程序集和命名空間中。
  • 所有分部必須具有相同的可訪問性,例如 public 或 private 等。
  • 如果任何分部被聲明為 abstract、sealed 或基類型,則整個(gè)類都被聲明為相同的類型。
  • 不同的分部可以有不同的基類型,因此最終類將繼承所有基類型。
  • partial 修飾符只能緊接在 class、structinterface 關(guān)鍵字之前。
  • 允許嵌套分部類型。

分部方法

分部類還支持分部方法。這就像是一個(gè)“鉤子(Hook)”。

// 在 A 文件定義鉤子
partial void OnDataChanged();
// 在 B 文件實(shí)現(xiàn)鉤子(如果不實(shí)現(xiàn),編譯器會(huì)直接刪掉這個(gè)調(diào)用,零性能損耗)
partial void OnDataChanged()
{
    Console.WriteLine("數(shù)據(jù)變了!");
}

分部類或結(jié)構(gòu)可以包含一個(gè)方法,該方法被拆分到分部類或結(jié)構(gòu)的兩個(gè)單獨(dú)的 .cs 文件中。其中一個(gè) .cs 文件必須包含該方法的簽名,而另一個(gè)文件可以包含分部方法的可選實(shí)現(xiàn)。方法的聲明和實(shí)現(xiàn)都必須具有 partial 關(guān)鍵字。

 public partial class Employee
 {
     public Employee() {
         GenerateEmpId();
     }
     public int EmpId { get; set; }
     public string Name { get; set; }
     partial void GenerateEmployeeId();
 }
 public partial class Employee
 {
     partial void GenerateEmployeeId()
     {
         this.EmpId = random();
     }
 }

上面,EmployeeProps.cs 包含分部方法 GenerateEmployeeId() 的聲明,該方法在構(gòu)造函數(shù)中使用。EmployeeMethods.cs 包含 GenerateEmployeeId() 方法的實(shí)現(xiàn)。以下代碼演示了如何創(chuàng)建一個(gè)使用分部方法的 Employee 類對(duì)象。

 class Program
 {
     static void Main(string[] args)
     {
         var emp = new Employee();
         Console.WriteLine(emp.EmpId); // prints genereted id
         Console.ReadLine();
     }
 }

分部方法的規(guī)則

  • 分部方法必須使用 partial 關(guān)鍵字,并且必須返回 void。
  • 分部方法可以有 inref 參數(shù),但不能有 out 參數(shù)。
  • 分部方法是隱式私有方法,因此不能是虛方法。
  • 分部方法可以是靜態(tài)方法。
  • 分部方法可以是泛型方法。

簡單粗暴地說,分部方法(Partial Method)的意義在于:給機(jī)器生成的代碼“預(yù)留后悔藥”,同時(shí)不給運(yùn)行環(huán)境增加一丁點(diǎn)負(fù)擔(dān)。

這種設(shè)計(jì)解決了軟件工程中一個(gè)經(jīng)典的矛盾:“自動(dòng)化生成的代碼”與“個(gè)性化業(yè)務(wù)需求”的沖突。

分部方法的意義

核心意義:安全的“掛鉤(Hook)”

在大型項(xiàng)目中(尤其是使用 Entity Framework 或 WPF 時(shí)),工具會(huì)自動(dòng)生成成千上萬行代碼。

  • 如果不提供分部方法:你想在類初始化時(shí)加一行日志,你只能改動(dòng)生成的文件。一旦數(shù)據(jù)庫表結(jié)構(gòu)變了,你重新生成代碼,你寫的日志代碼瞬間被沖掉。你會(huì)陷入“改了丟,丟了再改”的死循環(huán)。
  • 有了分部方法:機(jī)器在生成的代碼里到處埋下 partial void OnSomething()。它告訴你:“我在這里留了個(gè)口子,你想寫邏輯就去另一個(gè)文件寫,不寫也沒關(guān)系。”

性能極致

這是它和 Virtual 方法或 Event(事件)最大的區(qū)別。

  • Virtual/Event:即便沒人重寫方法,即便沒有訂閱者,程序運(yùn)行時(shí)依然要分配內(nèi)存、進(jìn)行跳轉(zhuǎn)判斷。
  • Partial Method:如果你不寫實(shí)現(xiàn),編譯器在生成 .dll 時(shí)會(huì)直接抹除這個(gè)調(diào)用。
    • 意義:對(duì)于需要極高性能的基礎(chǔ)底層庫,既給了開發(fā)者擴(kuò)展的能力,又保證了不擴(kuò)展時(shí)是“零開銷”。

場(chǎng)景對(duì)比:為什么不直接定義一個(gè)普通方法?

方案機(jī)器生成代碼中的動(dòng)作開發(fā)者動(dòng)作后果
普通方法直接寫死邏輯無法干預(yù)必須修改生成的文件,重構(gòu)即地獄。
虛方法 (Virtual)定義 virtual 方法override 重寫存在虛函數(shù)表查詢開銷,必須實(shí)例化對(duì)象。
分部方法聲明 partial 方法并調(diào)用實(shí)現(xiàn) partial 方法不實(shí)現(xiàn)則代碼消失,實(shí)現(xiàn)則無縫嵌入,兩全其美。

只要是虛方法(Virtual Method),哪怕你大括號(hào)里一個(gè)字都不寫,它在運(yùn)行時(shí)依然有“身份支出”。

作為老司機(jī),我給你拆解一下這背后的“隱形賬單”。

虛方法的“買路錢”:vtable 查找

虛方法的本質(zhì)是運(yùn)行時(shí)多態(tài)。編譯器無法在編譯時(shí)確定你到底要執(zhí)行哪個(gè)方法,所以它得留個(gè)心眼。

  • vtable(虛函數(shù)表):每個(gè)包含虛方法的類,在內(nèi)存里都會(huì)多出一張表,記錄著方法的地址。
  • vptr(虛表指針):每一個(gè)該類的實(shí)例(對(duì)象),頭部都會(huì)偷偷多占 4 到 8 個(gè)字節(jié),專門用來指向這張表。
  • 查找開銷:每次調(diào)用虛方法,CPU 都要玩一次“三級(jí)跳”:
    • 找到對(duì)象的指針。
    • 通過指針找到 vtable
    • vtable 里查到方法真正的內(nèi)存地址,最后才跳過去執(zhí)行。

在 C# 中,虛表(VTable) 是一種底層機(jī)制,用于支持面向?qū)ο缶幊讨械亩鄳B(tài)性和虛方法調(diào)用。它是一個(gè)數(shù)據(jù)結(jié)構(gòu),存儲(chǔ)了類的虛方法的地址,以便在運(yùn)行時(shí)動(dòng)態(tài)調(diào)用正確的方法實(shí)現(xiàn)。

哪怕方法體是空的,這套“三級(jí)跳”的動(dòng)作一次都少不了。

無法“內(nèi)聯(lián)”的損失

這是性能差距最大的地方。

  • 內(nèi)聯(lián)(Inlining):JIT 編譯器發(fā)現(xiàn)一個(gè)方法很簡單,會(huì)直接把代碼“平鋪”到調(diào)用點(diǎn),省掉函數(shù)調(diào)用的壓棧和出棧。
  • 虛方法的阻礙:因?yàn)樘摲椒ㄒ竭\(yùn)行時(shí)才知道跑哪段邏輯,JIT 編譯器通常不敢對(duì)其進(jìn)行內(nèi)聯(lián)優(yōu)化。

性能對(duì)比表(Partial & Virtual)

特性分部方法 (Partial Method)虛方法 (Virtual Method)
未實(shí)現(xiàn)/空實(shí)現(xiàn)時(shí)徹底消失。編譯器直接把調(diào)用代碼刪了。依然存在。CPU 照樣執(zhí)行跳轉(zhuǎn)指令。
內(nèi)存占用0 額外占用。每個(gè)對(duì)象多出指針空間(4/8 bytes)。
調(diào)用速度極快(直接調(diào)用或內(nèi)聯(lián))。較慢(需要查表跳轉(zhuǎn))。
靈活性編譯時(shí)決定,不能跨程序集。運(yùn)行時(shí)決定,支持動(dòng)態(tài)加載插件。

什么時(shí)候用哪種?

你可以根據(jù)這個(gè)邏輯來選:

  • partial 的情況:你寫的是底層框架或代碼生成工具,你想給開發(fā)者留個(gè)“可選的開關(guān)”,而且對(duì)性能有極致追求。如果不寫邏輯,你希望這功能像從未存在過一樣。
  • virtual 的情況:你需要真正的多態(tài)。比如你寫了一個(gè) Animal 類,你想讓 DogCat 在運(yùn)行時(shí)表現(xiàn)出不同的 Eat() 行為。

該執(zhí)行哪一個(gè)方法?

答案很簡單:你不需要知道,也不需要選。

因?yàn)樵?C# 中,分部方法(Partial Method)遵循的是**“有且僅有一個(gè)實(shí)現(xiàn)”**的原則。

編譯器眼里的“合并”

在你提供的代碼中,編譯器會(huì)報(bào)錯(cuò),原因是你寫了兩個(gè)實(shí)現(xiàn)(方法體 {}。你可以把分部方法的機(jī)制理解為:

  • 定義端(聲明):像是一個(gè)“槽位”,告訴編譯器:“我這里可能會(huì)有一段邏輯”。
  • 實(shí)現(xiàn)端(邏輯):像是往“槽位”里填東西。

如果你在兩個(gè)地方都寫了 { ... }(哪怕一個(gè)是空的 {}),編譯器就會(huì)像見到兩個(gè)同名同姓的人一樣陷入混亂,直接報(bào)錯(cuò)。

正確的執(zhí)行邏輯

如果你希望在某些情況下輸出 “BBB”,在另一些情況下“什么都不做”,你不能靠定義兩個(gè)方法實(shí)現(xiàn),而是要靠邏輯判斷

正確的寫法應(yīng)該是這樣的:

// 【生成代碼部分】
public partial class MyClass
{
    // 只有聲明,沒有大括號(hào)!這代表“此處預(yù)留鉤子”
    partial void OnOpen();
    public void DoWork()
    {
        OnOpen(); // 編譯器在這里調(diào)用鉤子
    }
}
// 【開發(fā)者代碼部分】
public partial class MyClass
{
    // 如果你想執(zhí)行邏輯,就實(shí)現(xiàn)它
    partial void OnOpen()
    {
        if (DateTime.Now.Hour > 12)
        {
            Console.WriteLine("BBB");
        }
        // 否則什么都不做(這就是你想要的“空方法”效果)
    }
}

為什么分部方法不能像普通方法那樣“重載”?

分部方法的設(shè)計(jì)初衷不是為了讓你做選擇題,而是為了**“無縫注入”**。

  • 如果你沒寫實(shí)現(xiàn):編譯器在生成的 .dll 里會(huì)把 OnOpen() 徹底抹除。調(diào)用它的地方(如 DoWork 內(nèi)部)也會(huì)被刪掉。就像這段代碼從未存在過。
  • 如果你寫了實(shí)現(xiàn):編譯器會(huì)把實(shí)現(xiàn)里的代碼“縫”到原本聲明調(diào)用的地方。

它不存在“調(diào)用哪一個(gè)”的問題,因?yàn)樽罱K編譯出來的類里,只有一個(gè) OnOpen

易混淆

那這樣執(zhí)行的時(shí)候,不會(huì)沖突么,或者有兩個(gè)分部類,他會(huì)執(zhí)行哪一個(gè)

不會(huì)沖突,因?yàn)樵诰幾g器眼里,它們壓根就不是兩個(gè)類,而是一個(gè)類。

你可以把“分部類”想象成一塊拼圖:源代碼階段你手里拿著幾塊碎片,但編譯完之后,這些碎片已經(jīng)嚴(yán)絲合縫地粘在一起,變成了一塊完整的板子。

為什么不會(huì)沖突?

  1. 物理上的“合體”:當(dāng)你點(diǎn)擊“生成”或“運(yùn)行”時(shí),C# 編譯器(Roslyn)會(huì)先掃描所有帶 partial 關(guān)鍵字的類。如果發(fā)現(xiàn)命名空間相同、類名相同的 partial 類,它會(huì)把它們所有的成員(字段、屬性、方法、構(gòu)造函數(shù))全部收集起來,塞進(jìn)同一個(gè) IL(中間語言)定義里。
  2. 符號(hào)的唯一性:由于它們最終是一個(gè)類,所以普通的 C# 規(guī)則依然適用。比如,你在 A 文件里定義了 public int Id,就不能在 B 文件里再定義一個(gè) public int Id,否則編譯器會(huì)報(bào)錯(cuò):“類已經(jīng)包含該成員的定義”。

執(zhí)行順序是怎么算的?

既然物理上合二為一了,就不存在“執(zhí)行哪一個(gè)”的問題。程序的入口(比如 Main 方法)調(diào)用該類的方法時(shí),該在哪就在哪。

唯一需要注意的順序:構(gòu)造函數(shù)

如果兩個(gè)分部類里都寫了構(gòu)造函數(shù),情況如下:

  • 普通構(gòu)造函數(shù):如果你在兩個(gè)文件里都定義了 public User()(無參構(gòu)造),編譯會(huì)報(bào)錯(cuò)。因?yàn)橥粋€(gè)類里不能有兩個(gè)簽名完全一樣的構(gòu)造函數(shù)。
  • 執(zhí)行邏輯:如果你想在不同文件中初始化數(shù)據(jù),通常會(huì)在 A 文件寫構(gòu)造函數(shù),在構(gòu)造函數(shù)里調(diào)用一個(gè)在 B 文件實(shí)現(xiàn)的分部方法(partial method)。

避坑指南

雖然編譯器幫你合體了,但在開發(fā)中要遵循以下守則:

  • 同一程序集:所有的分部類必須在同一個(gè)程序集(Project)內(nèi),不能跨 DLL。
  • 修飾符一致:如果 A 文件寫了 public partial,B 文件寫了 internal partial,編譯器會(huì)直接罷工。訪問級(jí)別必須完全統(tǒng)一。
  • 基類一致:如果 A 繼承自 BaseClassA,B 繼承自 BaseClassB,編譯報(bào)錯(cuò)。一個(gè)類只能有一個(gè)親爹。

同一個(gè)成員(方法)在同一個(gè)類中不能有多個(gè)實(shí)現(xiàn)

// 生成代碼分部
public partial class MyClass
{
	public string Name{get;set;}
    partial void OnOpen()
	{
		Console.WriteLine("AAA");
	};
}
// 開發(fā)者代碼分部
public partial class MyClass
{
	public string Age{get;set;}
    partial void OnOpen()
	{
		Console.WriteLine("BBB");
	};
}

雖然 partial 類可以將代碼拆分,但它依然遵循 C# 最基本的語法規(guī)則:同一個(gè)成員(方法)在同一個(gè)類中不能有多個(gè)實(shí)現(xiàn)。

為什么會(huì)報(bào)錯(cuò)?

分部方法(Partial Method)的設(shè)計(jì)初衷是:一個(gè)地方定義聲明(類似占位符),另一個(gè)地方編寫實(shí)現(xiàn)(可選)。

在你的例子中,兩個(gè)分部類都寫了具體的 { ... } 邏輯塊,編譯器會(huì)認(rèn)為你對(duì)同一個(gè)方法定義了兩次,直接拋出編譯錯(cuò)誤:

CS0757: 分部方法聲明具有多個(gè)實(shí)現(xiàn)方法。

正確的用法應(yīng)該是這樣

// ------------------------------------
// 文件 A:生成代碼分部(通常由工具生成)
// ------------------------------------
public partial class MyClass
{
    public string Name { get; set; }
    // 這里只定義“鉤子”的簽名,不寫大括號(hào)邏輯
    partial void OnOpen();
    public void Trigger()
    {
        OnOpen(); // 編譯器在這里埋下調(diào)用點(diǎn)
    }
}
// ------------------------------------
// 文件 B:開發(fā)者代碼分部(由你編寫)
// ------------------------------------
public partial class MyClass
{
    public string Age { get; set; }
    // 這里編寫具體的業(yè)務(wù)邏輯
    partial void OnOpen()
    {
        Console.WriteLine("BBB");
    }
}

執(zhí)行結(jié)果: 調(diào)用 Trigger() 方法時(shí),屏幕會(huì)輸出:BBB。

如果開發(fā)者沒寫代碼會(huì)怎樣?

這是分部方法最聰明的地方。如果你在文件 B 中沒有編寫 partial void OnOpen() 的實(shí)現(xiàn):

  1. 編譯器會(huì)發(fā)現(xiàn)這個(gè)方法沒被實(shí)現(xiàn)。
  2. 為了絕對(duì)的性能,編譯器會(huì)直接刪掉文件 A 中的方法聲明,并刪掉所有對(duì) OnOpen() 的調(diào)用。
  3. 程序運(yùn)行時(shí),就像這個(gè)方法從未存在過一樣,沒有任何性能開銷。

專業(yè)詞匯詳解

  • 編譯時(shí)合并 (Compile-time Merging): 意味著 partial 只是編譯器層面的語法糖。運(yùn)行時(shí)的 CLR(公共語言運(yùn)行時(shí))根本不知道分部類的存在,它看到的永遠(yuǎn)是一個(gè)完整的類。
  • Source Generators (源代碼生成器): .NET 5+ 引入的新技術(shù),在代碼編譯期間實(shí)時(shí)生成額外的 C# 代碼。它極度依賴分部類,因?yàn)樗荒苄薷哪悻F(xiàn)有的代碼,只能通過 partial 注入新功能。
  • 零性能損耗 (Zero Performance Overhead): 如果你定義了一個(gè)分部方法但沒有去實(shí)現(xiàn)它,編譯器在生成 IL 代碼時(shí)會(huì)徹底移除該方法的聲明和所有調(diào)用點(diǎn)。

到此這篇關(guān)于C#中分部類和分部方法(partial)的文章就介紹到這了,更多相關(guān)C#分部類和分部方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

衡水市| 山丹县| 仙桃市| 彰化县| 海门市| 胶南市| 土默特左旗| 大名县| 慈溪市| 汕尾市| 马尔康县| 承德市| 新昌县| 山东省| 香格里拉县| 石阡县| 陆川县| 洛宁县| 波密县| 广西| 铁力市| 卢氏县| 建昌县| 韩城市| 威远县| 博罗县| 蛟河市| 垫江县| 涡阳县| 壶关县| 南漳县| 拉孜县| 鸡东县| 剑川县| 邵武市| 巨鹿县| 泾川县| 扎囊县| 财经| 潢川县| 洛扎县|