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

C# 異常繼承從設(shè)計(jì)原則到sealed 關(guān)鍵字全解析

 更新時(shí)間:2026年06月02日 10:10:30   作者:周杰倫fans  
本文深入探討C#中自定義異常的設(shè)計(jì)原則,實(shí)戰(zhàn)應(yīng)用三層架構(gòu)異常體系,并解析為何自定義異常應(yīng)標(biāo)記為sealed,通過(guò)電商訂單系統(tǒng)示例,展示如何優(yōu)雅處理異常,提升代碼穩(wěn)定性和維護(hù)性,感興趣的朋友跟隨小編一起看看吧

引言:異常,不僅僅是 try-catch

在 C# 開(kāi)發(fā)中,異常處理是最基礎(chǔ)卻最容易被忽視的高級(jí)話題。很多開(kāi)發(fā)者掌握了 try-catch-finally 的語(yǔ)法,卻對(duì)異常的設(shè)計(jì)哲學(xué)知之甚少。本文將深入探討自定義異常繼承的設(shè)計(jì)原則、實(shí)戰(zhàn)應(yīng)用,以及一個(gè)看似矛盾的規(guī)范——為什么自定義異常通常要標(biāo)記為 sealed?

一、Exception 繼承體系全景圖

1.1 核心繼承層次

System.Object
  └── System.Exception (抽象基類)
       ├── System.SystemException (CLR 拋出的異常)
       │    ├── NullReferenceException
       │    ├── IndexOutOfRangeException
       │    └── ...
       ├── System.ApplicationException (應(yīng)用程序異常 - 已過(guò)時(shí))
       └── 自定義異常 (繼承自 Exception)

1.2 關(guān)鍵成員解析

public class Exception : ISerializable
{
    // 核心屬性
    public string Message { get; }           // 異常說(shuō)明
    public Exception InnerException { get; }  // 內(nèi)部異常(鏈?zhǔn)剑?
    public string StackTrace { get; }         // 調(diào)用堆棧
    public MethodBase TargetSite { get; }     // 拋出異常的方法
    public IDictionary Data { get; }          // 額外鍵值對(duì)數(shù)據(jù)
    // 核心方法
    public virtual void GetObjectData(SerializationContext context);
    public Exception GetBaseException();       // 獲取最內(nèi)部異常
}

二、實(shí)戰(zhàn):設(shè)計(jì)一個(gè)三層架構(gòu)的自定義異常體系

2.1 業(yè)務(wù)場(chǎng)景:電商訂單系統(tǒng)

假設(shè)我們有一個(gè)訂單處理系統(tǒng),需要在不同層級(jí)拋出有意義的異常:

// 1. 基礎(chǔ)自定義異常(抽象基類)
public abstract class OrderProcessingException : Exception
{
    public string OrderId { get; }
    protected OrderProcessingException(string message, string orderId) 
        : base(message)
    {
        OrderId = orderId;
    }
    protected OrderProcessingException(string message, string orderId, Exception inner)
        : base(message, inner)
    {
        OrderId = orderId;
    }
    // 支持序列化(用于跨 AppDomain 傳遞)
    protected OrderProcessingException(
        SerializationInfo info, StreamingContext context) 
        : base(info, context)
    {
        OrderId = info.GetString("OrderId");
    }
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("OrderId", OrderId);
    }
}
// 2. 具體業(yè)務(wù)異常 - 訂單驗(yàn)證失敗
public sealed class OrderValidationException : OrderProcessingException
{
    public List<string> ValidationErrors { get; }
    public OrderValidationException(string orderId, List<string> errors)
        : base($"訂單 {orderId} 驗(yàn)證失敗: {string.Join(", ", errors)}", orderId)
    {
        ValidationErrors = errors;
    }
}
// 3. 庫(kù)存不足異常
public sealed class InsufficientInventoryException : OrderProcessingException
{
    public string ProductId { get; }
    public int RequestedQuantity { get; }
    public int AvailableQuantity { get; }
    public InsufficientInventoryException(string orderId, string productId, 
        int requested, int available)
        : base($"產(chǎn)品 {productId} 庫(kù)存不足 (需要: {requested}, 可用: {available})", 
               orderId)
    {
        ProductId = productId;
        RequestedQuantity = requested;
        AvailableQuantity = available;
    }
}
// 4. 支付失敗異常
public sealed class PaymentFailedException : OrderProcessingException
{
    public string PaymentTransactionId { get; }
    public decimal Amount { get; }
    public string FailureReason { get; }
    public PaymentFailedException(string orderId, string transactionId, 
        decimal amount, string reason)
        : base($"訂單 {orderId} 支付失敗: {reason}", orderId)
    {
        PaymentTransactionId = transactionId;
        Amount = amount;
        FailureReason = reason;
    }
}

2.2 使用示例:分層異常處理

public class OrderService
{
    private readonly IOrderRepository _repository;
    private readonly IInventoryService _inventory;
    private readonly IPaymentGateway _payment;
    public async Task ProcessOrderAsync(string orderId)
    {
        try
        {
            // 1. 驗(yàn)證訂單
            var order = await _repository.GetOrderAsync(orderId);
            ValidateOrder(order);
            // 2. 檢查庫(kù)存
            await ReserveInventoryAsync(order);
            // 3. 處理支付
            await ProcessPaymentAsync(order);
            // 4. 更新訂單狀態(tài)
            await _repository.UpdateOrderStatusAsync(orderId, "Completed");
        }
        catch (OrderValidationException ex)
        {
            // 業(yè)務(wù)層處理:記錄驗(yàn)證失敗,通知用戶修改訂單
            _logger.LogWarning(ex, "訂單驗(yàn)證失敗");
            throw; // 重新拋出,讓上層 UI 處理
        }
        catch (InsufficientInventoryException ex)
        {
            // 嘗試自動(dòng)替換供應(yīng)商或部分發(fā)貨
            await HandleInventoryShortage(ex);
            throw; // 仍需要通知調(diào)用方
        }
        catch (PaymentFailedException ex)
        {
            // 記錄支付失敗,嘗試其他支付方式
            await TryAlternativePayment(ex);
            throw;
        }
        catch (Exception ex)
        {
            // 捕獲未知異常,包裝為業(yè)務(wù)異常
            throw new OrderProcessingException(
                $"處理訂單 {orderId} 時(shí)發(fā)生未知錯(cuò)誤", orderId, ex);
        }
    }
    private void ValidateOrder(Order order)
    {
        var errors = new List<string>();
        if (string.IsNullOrEmpty(order.CustomerId))
            errors.Add("客戶ID不能為空");
        if (order.TotalAmount <= 0)
            errors.Add("訂單金額必須大于0");
        if (errors.Any())
            throw new OrderValidationException(order.Id, errors);
    }
}

2.3 UI 層優(yōu)雅處理

[ApiController]
public class OrderController : ControllerBase
{
    [HttpPost("{orderId}/process")]
    public IActionResult ProcessOrder(string orderId)
    {
        try
        {
            await _orderService.ProcessOrderAsync(orderId);
            return Ok(new { message = "訂單處理成功" });
        }
        catch (OrderValidationException ex)
        {
            // 返回 400 并附帶驗(yàn)證詳情
            return BadRequest(new 
            { 
                error = ex.Message,
                validationErrors = ex.ValidationErrors,
                orderId = ex.OrderId 
            });
        }
        catch (InsufficientInventoryException ex)
        {
            // 返回 409 Conflict,提示用戶調(diào)整數(shù)量
            return Conflict(new
            {
                error = ex.Message,
                productId = ex.ProductId,
                available = ex.AvailableQuantity
            });
        }
        catch (PaymentFailedException ex)
        {
            // 返回 402 Payment Required
            return StatusCode(402, new { error = ex.Message });
        }
        catch (OrderProcessingException ex)
        {
            // 通用業(yè)務(wù)異常
            return StatusCode(500, new { error = ex.Message });
        }
    }
}

三、核心爭(zhēng)議:為什么自定義異常要 sealed?

3.1 微軟官方設(shè)計(jì)準(zhǔn)則的明確規(guī)定

CA1064: Exceptions should be public
CA1032: Implement standard exception constructors
Do seal exception classes - 雖然沒(méi)有獨(dú)立的 CA 代碼,但 .NET Core 源碼分析和 Framework Design Guidelines 明確建議異常類應(yīng)為 sealed。

3.2 Sealed 的四大核心理由

理由 1:防止異常多態(tài)的濫用

// 反模式 - 不應(yīng)該這樣做
public class DatabaseException : Exception { }
// 有人繼承了它,改變了語(yǔ)義
public class SqlConnectionException : DatabaseException { }
public class SqlQueryException : DatabaseException { }
// 問(wèn)題:catch(DatabaseException ex) 會(huì)捕獲所有子類
// 導(dǎo)致無(wú)法精確處理特定錯(cuò)誤

如果確實(shí)需要層次結(jié)構(gòu),應(yīng)該使用不同的異常類型,而不是繼承:

// 正確做法:獨(dú)立的不相關(guān)異常
public sealed class SqlConnectionException : Exception { }
public sealed class SqlQueryException : Exception { }

理由 2:保持異常語(yǔ)義的原子性

// 未密封的異常
public class FileOperationException : Exception 
{
    public string FilePath { get; set; }
}
// 派生類可能修改重要屬性
public class SpecialFileException : FileOperationException
{
    // 可能覆蓋 FilePath 的語(yǔ)義,導(dǎo)致基類邏輯錯(cuò)誤
    public new string FilePath { get; set; }
}
// 問(wèn)題:基類的異常處理代碼可能被破壞

理由 3:序列化與跨域邊界傳遞

// 未密封的異常在跨 AppDomain 或跨進(jìn)程序列化時(shí)
// 需要完整的類型信息,派生類可能破壞序列化契約
[Serializable]
public class MyException : Exception 
{
    // 如果沒(méi)有正確實(shí)現(xiàn)序列化構(gòu)造函數(shù),派生類會(huì)失敗
    protected MyException(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
}
// 派生類可能忘記實(shí)現(xiàn)序列化構(gòu)造函數(shù)
public class DerivedException : MyException { } // 危險(xiǎn)!

理由 4:性能與代碼穩(wěn)定性

// JIT 編譯器能對(duì) sealed 類進(jìn)行更好的優(yōu)化
// 調(diào)用虛方法時(shí)無(wú)需檢查派生類
public sealed class FastException : Exception 
{
    public override string Message => "Optimized";
}
// vs 未密封版本
public class VirtualException : Exception 
{
    public override string Message => "Needs vtable lookup";
}

3.3 什么時(shí)候可以不用 sealed?

極少數(shù)例外場(chǎng)景

// 1. 抽象基類模式(本身不直接拋出)
public abstract class PluginException : Exception 
{
    protected PluginException(string message) : base(message) { }
}
// 2. 框架級(jí)別的公共異常基類(如 Prism 的 CompositePresentationException)
// 但這通常被認(rèn)為是反模式
// 3. 測(cè)試 Mock 時(shí)需要(但測(cè)試應(yīng)避免 Mock 異常)

3.4 實(shí)戰(zhàn)對(duì)比:密封 vs 非密封

// ? 推薦:密封的完整異常
public sealed class ApiException : Exception
{
    public int StatusCode { get; }
    public string ApiPath { get; }
    public ApiException(string message, int statusCode, string apiPath)
        : base(message) => (StatusCode, ApiPath) = (statusCode, apiPath);
    private ApiException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        StatusCode = info.GetInt32(nameof(StatusCode));
        ApiPath = info.GetString(nameof(ApiPath));
    }
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue(nameof(StatusCode), StatusCode);
        info.AddValue(nameof(ApiPath), ApiPath);
    }
}
// 使用:清晰、安全、高性能
try { /* ... */ }
catch (ApiException ex) when (ex.StatusCode == 404)
{
    // 精確匹配,無(wú)需擔(dān)心子類干擾
}

四、最佳實(shí)踐總結(jié)

4.1 設(shè)計(jì)檢查清單

  • 異常類命名為 [Name]Exception
  • 標(biāo)記為 sealed(除非有充分理由)
  • 實(shí)現(xiàn)三個(gè)標(biāo)準(zhǔn)構(gòu)造函數(shù)(參數(shù)為 message、message+inner、序列化)
  • 添加自定義屬性時(shí)實(shí)現(xiàn)序列化支持
  • 避免在異常屬性中使用復(fù)雜的引用類型
  • 異常類應(yīng)該是 public(跨程序集使用)

4.2 構(gòu)造函數(shù)模板

public sealed class MyException : Exception
{
    // 1. 無(wú)參構(gòu)造函數(shù)(可選)
    public MyException() { }
    // 2. 帶消息的構(gòu)造函數(shù)
    public MyException(string message) : base(message) { }
    // 3. 帶內(nèi)部異常的構(gòu)造函數(shù)
    public MyException(string message, Exception inner) : base(message, inner) { }
    // 4. 序列化構(gòu)造函數(shù)(必須)
    private MyException(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
}

4.3 拋異常 vs 返回值

// ? 避免:用返回碼表示錯(cuò)誤
public enum Result { Success, NotFound, ValidationError }
public Result ProcessOrder(string id) { /* ... */ }
// ? 推薦:使用異常
public void ProcessOrder(string id)
{
    if (string.IsNullOrEmpty(id))
        throw new ArgumentNullException(nameof(id));
    // ...
}
// ? 邊界情況:預(yù)期內(nèi)的失敗用 Result 模式
public (bool Success, string ErrorMessage) TryParseOrder(string input) { /* ... */ }

五、性能考量與替代方案

5.1 異常的性能開(kāi)銷

// 異常很昂貴:堆棧跟蹤收集 + 序列化 + CLR 內(nèi)部處理
// 100,000 次異常拋出 ≈ 2-3 秒
// 100,000 次條件判斷 ≈ 0.01 秒
// ? 高頻路徑避免異常
public bool TryGetValue(string key, out string value)
{
    if (_cache.ContainsKey(key))
    {
        value = _cache[key];
        return true;
    }
    value = null;
    return false;
}
// 而不是
public string GetValue(string key)
{
    if (!_cache.ContainsKey(key))
        throw new KeyNotFoundException(); // 如果頻繁發(fā)生,性能災(zāi)難
    return _cache[key];
}

5.2 何時(shí)真正需要自定義異常

  • ? 需要攜帶額外的業(yè)務(wù)數(shù)據(jù)(如訂單ID、產(chǎn)品ID)
  • ? 需要在日志系統(tǒng)中區(qū)分不同業(yè)務(wù)場(chǎng)景
  • ? 需要特定于領(lǐng)域的中文錯(cuò)誤信息
  • ? 需要與第三方系統(tǒng)集成時(shí)的錯(cuò)誤映射
  • ? 僅僅為了給異常起個(gè)新名字
  • ? 可以使用現(xiàn)有異常(如 InvalidOperationException)時(shí)
  • ? 異常永遠(yuǎn)不會(huì)被 catch 區(qū)分處理時(shí)

結(jié)語(yǔ):優(yōu)雅異常的藝術(shù)

異常繼承設(shè)計(jì)看似簡(jiǎn)單,實(shí)則體現(xiàn)了對(duì)系統(tǒng)邊界、錯(cuò)誤傳播和代碼可維護(hù)性的深刻理解。sealed 關(guān)鍵字在這里不是限制,而是保護(hù)——它防止了異常體系的無(wú)序膨脹,確保了每個(gè)異常類型的語(yǔ)義完整性和運(yùn)行時(shí)穩(wěn)定性。

記?。?strong>異常不是業(yè)務(wù)流程,而是業(yè)務(wù)規(guī)則的例外。當(dāng)你的代碼拋出異常時(shí),應(yīng)該讓調(diào)用者無(wú)法忽視,同時(shí)提供足夠的上下文信息。而 sealed 異常,就是這種清晰語(yǔ)義的最佳載體。

討論:你是否有過(guò)因異常繼承層次過(guò)深而導(dǎo)致的調(diào)試噩夢(mèng)?歡迎在評(píng)論區(qū)分享你的經(jīng)歷和見(jiàn)解。

到此這篇關(guān)于C# 異常繼承從設(shè)計(jì)原則到 sealed 關(guān)鍵字的奧秘的文章就介紹到這了,更多相關(guān)C# 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C#實(shí)現(xiàn)把圖片轉(zhuǎn)換成二進(jìn)制以及把二進(jìn)制轉(zhuǎn)換成圖片的方法示例

    C#實(shí)現(xiàn)把圖片轉(zhuǎn)換成二進(jìn)制以及把二進(jìn)制轉(zhuǎn)換成圖片的方法示例

    這篇文章主要介紹了C#實(shí)現(xiàn)把圖片轉(zhuǎn)換成二進(jìn)制以及把二進(jìn)制轉(zhuǎn)換成圖片的方法,結(jié)合具體實(shí)例形式分析了基于C#的圖片與二進(jìn)制相互轉(zhuǎn)換以及圖片保存到數(shù)據(jù)庫(kù)的相關(guān)操作技巧,需要的朋友可以參考下
    2017-06-06
  • C#使用System.Threading.Timer實(shí)現(xiàn)計(jì)時(shí)器的示例詳解

    C#使用System.Threading.Timer實(shí)現(xiàn)計(jì)時(shí)器的示例詳解

    以往一般都是用 System.Timers.Timer 來(lái)做計(jì)時(shí)器,其實(shí) System.Threading.Timer 也可以實(shí)現(xiàn)計(jì)時(shí)器功能,下面就跟隨小編一起來(lái)學(xué)習(xí)一下如何使用System.Threading.Timer實(shí)現(xiàn)計(jì)時(shí)器功能吧
    2024-01-01
  • C# CancellationToken的使用小結(jié)

    C# CancellationToken的使用小結(jié)

    在C#中CancellationToken是用于實(shí)現(xiàn)協(xié)作式取消的核心類型,本文就來(lái)詳細(xì)的介紹一下C# CancellationToken的使用小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-11-11
  • C#實(shí)現(xiàn)十五子游戲

    C#實(shí)現(xiàn)十五子游戲

    這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)十五子游戲的相關(guān)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • C#?wpf使用DockPanel實(shí)現(xiàn)制作截屏框

    C#?wpf使用DockPanel實(shí)現(xiàn)制作截屏框

    做桌面客戶端的時(shí)候有時(shí)需要實(shí)現(xiàn)截屏功能,能夠在界面上框選截屏,本文就來(lái)為大家介紹一下wpf如何使用DockPanel制作截屏框吧,感興趣的可以了解下
    2023-09-09
  • C#連接SQL?Sever數(shù)據(jù)庫(kù)詳細(xì)圖文教程

    C#連接SQL?Sever數(shù)據(jù)庫(kù)詳細(xì)圖文教程

    C#是Microsoft公司為.NET Framework推出的重量級(jí)語(yǔ)言,和它搭配最完美的數(shù)據(jù)庫(kù)無(wú)疑就是Microsoft SQL Server了,下面這篇文章主要給大家介紹了關(guān)于C#連接SQL?Sever數(shù)據(jù)庫(kù)的詳細(xì)圖文教程,需要的朋友可以參考下
    2023-06-06
  • C#編程自學(xué)之類和對(duì)象

    C#編程自學(xué)之類和對(duì)象

    C#一種面向?qū)ο蟮木幊陶Z(yǔ)言,是專門為.NET應(yīng)用而開(kāi)發(fā)出的語(yǔ)言。在.NET運(yùn)行庫(kù)的支持下,.NET框架的各種優(yōu)點(diǎn)在C#中表現(xiàn)得淋漓盡致。所以學(xué)習(xí)類和對(duì)象以及如何運(yùn)用它們是非常重要!
    2015-10-10
  • c#使用dynamic類型優(yōu)化反射的方法

    c#使用dynamic類型優(yōu)化反射的方法

    dynamic是FrameWork4.0的新特性,下面這篇文章主要給大家介紹了關(guān)于c#使用dynamic類型優(yōu)化反射的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-12-12
  • C#代碼實(shí)現(xiàn)根據(jù)模板快速生成Word文檔

    C#代碼實(shí)現(xiàn)根據(jù)模板快速生成Word文檔

    在日常開(kāi)發(fā)中,我們經(jīng)常遇到需要批量生成合同、通知書(shū)、報(bào)告等Word文檔的場(chǎng)景,今天小編就來(lái)分享如何使用?Free Spire.Doc for .NET?輕松實(shí)現(xiàn)根據(jù)模板快速生成Word文檔吧
    2026-04-04
  • c#中string的特性介紹及注意事項(xiàng)小結(jié)

    c#中string的特性介紹及注意事項(xiàng)小結(jié)

    這篇文章主要給大家介紹了關(guān)于c#中string的特性介紹及注意事項(xiàng)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用c#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11

最新評(píng)論

屏东县| 洛隆县| 黎城县| 囊谦县| 望城县| 舟山市| 凭祥市| 桐城市| 合山市| 南丹县| 哈尔滨市| 肃北| 洞口县| 炎陵县| 同心县| 中方县| 屏东县| 万年县| 辽阳县| 三台县| 中卫市| 黔西| 宁国市| 阳高县| 武冈市| 剑河县| 龙井市| 都昌县| 即墨市| 历史| 平定县| 泰来县| 双城市| 无极县| 凤台县| 惠水县| 南江县| 丹棱县| 镇康县| 安西县| 循化|