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

一文淺析C#如何優(yōu)雅處理引用類型的深拷貝

 更新時間:2026年05月17日 09:51:53   作者:叫我安不理  
本文主要講述了C#中淺拷貝和深拷貝的概念,實現(xiàn)方式以及適用場景,文內(nèi)探討了ICloneable,序列化/AutoMapper,record等方法,并強調(diào)了在不同場景下選擇合適的方法的重要性,感興趣的小伙伴可以了解下

前言

幾年前寫過一個 bug,根因很土:該深拷貝的地方?jīng)]深拷貝,副本一改,原件跟著變。排查的時候老板以為動的是庫里的數(shù)據(jù),其實就是一個本地對象被共享了。

先把詞說清楚:

淺拷貝:值類型復制一份;引用類型復制的是引用,兩邊還指著同一個子對象。你改副本里的引用成員,原件也會變。

只復制對象自身的一層:字段/屬性里如果是值類型,會復制一份值;如果是引用類型,復制的是引用(指針),新舊對象仍指向同一塊堆上的子對象。

深拷貝:引用鏈上也建新對象,改副本不該動到原件的嵌套數(shù)據(jù)。

從根對象開始,遞歸地為引用類型也創(chuàng)建新實例,并把內(nèi)容復制過去,直到整棵「對象圖」在邏輯上獨立。改拷貝不應意外改動原對象里的嵌套數(shù)據(jù)。

ICloneable:能深,但接口不保證

ICloneable 只有一個 object Clone(),文檔不會替你承諾淺還是深,看實現(xiàn)。你想做深拷貝,可以,全寫在 Clone() 里就行。

淺拷貝場景下,改拷貝里的引用類型字段,往往會影響原對象(反之亦然),除非你再給那個字段賦一個新實例。

// 淺拷貝示例(Address 還是同一個引用)
public class DeepAndShallowCopy
{
    public static void ShallowCopy()
    {
        var rawUser = new UserDto { Id = 1, Name = "name1", Address = new UserDto.AddressDto { City = "CS" } };
        var copyUser = rawUser.Clone() as UserDto;
        copyUser.Id = 2;
        copyUser.Name = "name2";
        copyUser.Address.City = "CS2"; // 淺拷貝:動的是同一塊 Address,原數(shù)據(jù)跟著變
        Console.WriteLine($"rawUser={JsonSerializer.Serialize(rawUser)}");
        Console.WriteLine($"copyUser={JsonSerializer.Serialize(copyUser)}");
    }
}
public class UserDto : ICloneable
{
    public int Id { get; set; }
    public string Name { get; set; }
    public AddressDto Address { get; set; }
    public object Clone()
    {
        return new UserDto { Id = Id, Name = Name, Address = Address };
    }
    public class AddressDto
    {
        public string City { get; set; }
    }
}

深拷貝就要讓 AddressClone() 一份。引用類型多就一層層寫,啰嗦但清楚。

public class DeepAndShallowCopy
{
    public static void DeepCopy()
    {
        var rawUser = new UserDto { Id = 1, Name = "name1", Address = new UserDto.AddressDto { City = "CS" } };

        var copyUser = rawUser.Clone() as UserDto;
        copyUser.Id = 2;
        copyUser.Name = "name2";
        copyUser.Address.City = "CS2"; // 深拷貝:Address 已是新實例,原數(shù)據(jù)不變

        Console.WriteLine($"rawUser={JsonSerializer.Serialize(rawUser)}");
        Console.WriteLine($"copyUser={JsonSerializer.Serialize(copyUser)}");
    }
}

public class UserDto : ICloneable
{
    public int Id { get; set; }
    public string Name { get; set; }
    public AddressDto Address { get; set; }

    public object Clone()
    {
        return new UserDto { Id = Id, Name = Name, Address = Address.Clone() as AddressDto };
    }

    public class AddressDto : ICloneable
    {
        public string City { get; set; }

        public object Clone()
        {
            return new AddressDto { City = City };
        }
    }
}

手寫這條路:性能好,行為自己說了算。代價是對象圖一大就容易漏,漏一處就是淺拷貝;另外 Clone() 返回 object,調(diào)用處總要轉一下類型,有點煩。

序列化 / AutoMapper:省事,但要心里有數(shù)

我們 CRUD 程序員經(jīng)常不想維護一整張克隆圖,就會想走捷徑。

System.Text.Json

思路就是序列化再反序列化,得到一棵新對象。代碼少,DTO、配置這類能完整序列化的類型用起來很省事。

public class DeepAndShallowCopy
{
    public static void DeepCopyByJsonSerializer()
    {
        var rawUser = new UserDto { Id = 1, Name = "name1", Address = new UserDto.AddressDto { City = "CS" } };

        var copyUser = rawUser.DeepCopy();
        copyUser.Id = 2;
        copyUser.Name = "name2";
        copyUser.Address.City = "CS2";

        Console.WriteLine($"rawUser={JsonSerializer.Serialize(rawUser)}");
        Console.WriteLine($"copyUser={JsonSerializer.Serialize(copyUser)}");
    }
}

public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public AddressDto Address { get; set; }

    /// <summary>
    /// 序列化 → 反序列化,換一批新實例
    /// </summary>
    public UserDto DeepCopy()
    {
        var rawUserString = JsonSerializer.Serialize(this);
        return JsonSerializer.Deserialize<UserDto>(rawUserString)!;
    }

    public class AddressDto
    {
        public string City { get; set; }
    }
}

好處是加字段一般不用改克隆邏輯(只要還能序列化)。麻煩在循環(huán)引用要單獨配,委托、怪類型、非公開成員也可能過不去。

AutoMapper

public static void DeepCopyByAutoMapper()
{
    var config = new MapperConfiguration(config =>
    {
        config.CreateMap<UserDto, UserDto>();
        // 子類型也要建同型映射,否則 Address 可能還是同一條引用
        config.CreateMap<UserDto.AddressDto, UserDto.AddressDto>();
    });

    IMapper mapper = config.CreateMapper();

    var rawUser = new UserDto { Id = 1, Name = "name1", Address = new UserDto.AddressDto { City = "CS" } };

    var copyUser = mapper.Map<UserDto>(rawUser);
    copyUser.Id = 2;
    copyUser.Name = "name2";
    copyUser.Address.City = "CS2";

    Console.WriteLine($"rawUser={JsonSerializer.Serialize(rawUser)}");
    Console.WriteLine($"copyUser={JsonSerializer.Serialize(copyUser)}");
}

項目里本來就有 Mapper 的話,順手 Map 一下也行。它本職是 DTO 映射,不是克隆庫:子圖沒配齊、策略不對,照樣可能淺拷貝。別指望「默認就是深拷貝」。

JSON 和 Mapper 本質(zhì)上都是在「按數(shù)據(jù)重建對象」,只是經(jīng)常能重建出一棵獨立的樹,和手寫 Clone 的語義不是一回事。

record:我眼里的版本答案

前面 ICloneable 要到處 as,JSON 像繞路,AutoMapper 容易配成玄學。record 從語言層面把「數(shù)據(jù)」這件事說清楚了:默認值語義、相等性、ToString、非破壞性修改,編譯器幫你生成一大坨樣板,你只要在業(yè)務代碼里寫 with。

為什么說它像版本答案(不是銀彈,但在「數(shù)據(jù)拷貝 / 派生」這條線上很對味):

  • 相等按值比:同類型的兩個實例,成員一樣就相等,寫單元測試、去重、緩存 key 都省心。class 默認比引用,想比內(nèi)容要自己重寫 Equals/GetHashCode,一懶就埋雷。
  • with 是語法級的「從舊副本改幾處」:讀代碼的人一眼知道「基于 rawUser 出了一個新對象」,不用跳進 Clone() 里猜深還是淺。
  • 打印友好:自動生成的 ToString 把主要字段打出來,日志里好認,排障少猜幾次。

語法上有兩種常見寫法,知道就行:

  1. 傳統(tǒng)屬性寫法:和 class 差不多,只是類型是 record,白嫖相等性和 with
  2. 位置參數(shù) recordpublic record UserDto(int Id, string Name, AddressDto Address); 編譯器幫你生成主構造函數(shù)、解構、with 里按位置對應,DTO 里很省字。

默認的 record引用類型(相當于 record class)。還有 record struct,那是值類型語義,拷貝整坨 struct 時是按位復制,和「引用圖里拆不拆」又是另一套題,別混在一塊講深拷貝時搞暈自己就行。

with 和深拷貝的關系再強調(diào)一遍,避免面試翻車:with 會復制你沒改到的成員;引用類型的成員如果with 里換掉,新舊兩邊仍指著同一個子對象。所以要深,就顯式寫 Address = rawUser.Address with { ... } 或給一個新的實例。嵌套深就鏈式 with,丑一點但誠實——至少「哪里拆引用」全攤在調(diào)用點,不靠隱式魔法。

想往「真·快照」靠,可以把屬性收成 init 或只在構造函數(shù)里賦值,外面用 with 派生。{ get; set; } 照樣能改字段,別嘴上說 record 不可變、手還在到處 set。

class 分工可以這樣記:class 扛行為、生命周期長、引用身份有時就是業(yè)務含義;record 扛可比較的數(shù)據(jù)快照、適合命令/事件/讀模型里那種「從上一版捏一版」的寫法。

public class DeepAndShallowCopy
{
    public static void DeepCopyByRecord()
    {
        var rawUser = new UserDto { Id = 1, Name = "name1", Address = new UserDto.AddressDto { City = "CS" } };
        var copyUser = rawUser with { Id = 2, Name = "name2", Address = rawUser.Address with { City = "CS2" } };

        Console.WriteLine($"rawUser={JsonSerializer.Serialize(rawUser)}");
        Console.WriteLine($"copyUser={JsonSerializer.Serialize(copyUser)}");
    }
}

//注意,這不是標準用法,只是為了演示
public record UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public AddressDto Address { get; set; }

    public record AddressDto
    {
        public string City { get; set; }
    }
}

標準寫法示意(省樣板,相等/ToString/with 照樣有):

public record AddressDto(string City);

public record UserDto(int Id, string Name, AddressDto Address);

var rawUser = new UserDto(1, "name1", new AddressDto("CS"));
var copyUser = rawUser with { Name = "name2", Address = rawUser.Address with { City = "CS2" } };

怎么選

按常見情況排個序,夠用就行:

  • 核心模型、性能敏感、要一眼能審代碼:手寫 Clone 或工廠從舊對象構造新的。
  • 普通 DTO / 配置、能序列化、沒環(huán):JSON 往返最省事。
  • 項目里 Mapper 已經(jīng)到處都是:可以 CreateMap<T,T>(),把子類型也配全,并記得做配置校驗和用例,別光靠手感。
  • 業(yè)務就是「從舊狀態(tài)派生一個新狀態(tài)」、嵌套也愿意寫清楚:record + with

結論

深拷貝沒有萬能 API,只有你對「哪些引用該共享、哪些該拆開」有沒有想清楚。工具省的是打字時間,省不了腦子。

挖坑待埋:record class與record struct 詳解

到此這篇關于一文淺析C#如何優(yōu)雅處理引用類型的深拷貝的文章就介紹到這了,更多相關C#處理引用類型的深拷貝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 利用Unity制作特寫鏡頭的示例代碼

    利用Unity制作特寫鏡頭的示例代碼

    這篇文章主要介紹了如何利用Unity制作特寫鏡頭效果,文中的示例代碼講解詳細,對我們學習有一定的參考價值,感興趣的可以了解一下
    2022-04-04
  • 詳解WPF中的APP生命周期以及全局異常捕獲

    詳解WPF中的APP生命周期以及全局異常捕獲

    這篇文章主要為大家詳細介紹了WPF中的APP生命周期以及全局異常捕獲的相關資料,文中的示例代碼講解詳細,對我們學習WPF有一點的幫助,需要的可以了解一下
    2023-03-03
  • C#以太網(wǎng)Sockets客戶端設計實現(xiàn)

    C#以太網(wǎng)Sockets客戶端設計實現(xiàn)

    本文主要介紹了C#以太網(wǎng)Sockets客戶端設計實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-02-02
  • 基于C# 生成Zip壓縮包代碼

    基于C# 生成Zip壓縮包代碼

    壓縮包制作也是很多項目中需要用到的功能。接下來通過本文給大家介紹基于C# 生成Zip壓縮包代碼,對c生成zip壓縮包相關知識感興趣的朋友一起學習吧
    2016-04-04
  • 通過C#編寫一個簡易的Windows截屏增強工具

    通過C#編寫一個簡易的Windows截屏增強工具

    在使用?Windows?系統(tǒng)的截屏快捷鍵?PrintScreen?截屏時,如果需要把截屏保存到文件,需要先粘貼到畫圖工具然后另存為文件。所以本文用C#編寫了一個簡易的Windows截屏增強工具,需要的可以參考一下
    2022-05-05
  • 最新評論

    香河县| 白沙| 中方县| 新竹市| 崇明县| 新邵县| 怀柔区| 洪雅县| 探索| 太和县| 措美县| 章丘市| 江阴市| 鹤壁市| 赫章县| 奇台县| 白银市| 芜湖县| 连州市| 江北区| 靖州| 静安区| 政和县| 确山县| 化州市| 昌江| 三原县| 宜君县| 南和县| 长治县| 白河县| 安庆市| 中宁县| 沾益县| 云阳县| 饶平县| 固镇县| 麟游县| 邯郸市| 大英县| 岑巩县|