Shallow Copy vs Deep Copy ใน C#
การ Copy Object ใน C# มี 2 แบบหลัก ที่แตกต่างกันในการจัดการ Reference Type ภายใน Object
Shallow Copy (คัดลอกตื้น)
- คัดลอก Value Type Fields (int, bool, struct, etc.) แบบ Value
- คัดลอก Reference Type Fields (class, array, string) แบบ Reference (Address)
- Object เดิมและ Copy แชร์ Reference Type Fields เดียวกัน
- แก้ไข Reference Field ใน Copy → ส่งผลต่อ Original ด้วย
Deep Copy (คัดลอกลึก)
- คัดลอก ทุกอย่าง รวมถึง Reference Type Fields (สร้าง Instance ใหม่)
- Object เดิมและ Copy อิสระจากกันสมบูรณ์
- แก้ไข Copy ไม่กระทบ Original
ตัวอย่างเปรียบเทียบ
using System;
class Program
{
static void Main()
{
Console.WriteLine("=== Shallow Copy ===");
ShallowCopyDemo();
Console.WriteLine("\n=== Deep Copy ===");
DeepCopyDemo();
}
static void ShallowCopyDemo()
{
// Class ที่มี Reference Type Field (List)
var original = new Person
{
Name = "John",
Age = 30,
Addresses = new List { "123 Main St", "456 Oak Ave" }
};
// Shallow Copy: MemberwiseClone (Protected) หรือ Manual Copy
var shallow = original.ShallowCopy();
Console.WriteLine($"Original Name: {original.Name}"); // John
Console.WriteLine($"Shallow Name: {shallow.Name}"); // John
// เปลี่ยน Value Type (ไม่กระทบ Original)
shallow.Age = 25;
Console.WriteLine($"\nAfter shallow.Age = 25:");
Console.WriteLine($"Original Age: {original.Age}"); // 30
Console.WriteLine($"Shallow Age: {shallow.Age}"); // 25
// เปลี่ยน Reference Type (กระทบ Original!)
shallow.Addresses.Add("789 Pine Rd");
Console.WriteLine($"\nAfter shallow.Addresses.Add():");
Console.WriteLine($"Original Addresses: {string.Join(", ", original.Addresses)}"); // 3 items!
Console.WriteLine($"Shallow Addresses: {string.Join(", ", shallow.Addresses)}"); // 3 items!
Console.WriteLine($"Same List Reference? {object.ReferenceEquals(original.Addresses, shallow.Addresses)}"); // True
}
static void DeepCopyDemo()
{
var original = new Person
{
Name = "Jane",
Age = 28,
Addresses = new List { "100 First St" }
};
// Deep Copy: Manual / Serialization / Library
var deep = original.DeepCopy();
Console.WriteLine($"Original Name: {original.Name}"); // Jane
Console.WriteLine($"Deep Name: {deep.Name}"); // Jane
// เปลี่ยน Value Type
deep.Age = 35;
Console.WriteLine($"\nAfter deep.Age = 35:");
Console.WriteLine($"Original Age: {original.Age}"); // 28
Console.WriteLine($"Deep Age: {deep.Age}"); // 35
// เปลี่ยน Reference Type (ไม่กระทบ Original!)
deep.Addresses.Add("200 Second Ave");
Console.WriteLine($"\nAfter deep.Addresses.Add():");
Console.WriteLine($"Original Addresses: {string.Join(", ", original.Addresses)}"); // 1 item
Console.WriteLine($"Deep Addresses: {string.Join(", ", deep.Addresses)}"); // 2 items
Console.WriteLine($"Same List Reference? {object.ReferenceEquals(original.Addresses, deep.Addresses)}"); // False
}
}
// Class สำหรับ Demo
class Person
{
public string Name { get; set; } // Reference Type (immutable แต่เป็น reference)
public int Age { get; set; } // Value Type
public List Addresses { get; set; } // Reference Type (Mutable)
// Shallow Copy: ใช้ MemberwiseClone (Protected) ต้อง expose ผ่าน Method
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
// Deep Copy: Manual Implementation
public Person DeepCopy()
{
return new Person
{
Name = this.Name, // string immutable - copy reference OK
Age = this.Age,
Addresses = new List(this.Addresses) // สร้าง List ใหม่ Copy ค่า
};
}
}
ผลลัพธ์:
=== Shallow Copy === Original Name: John Shallow Name: John After shallow.Age = 25: Original Age: 30 Shallow Age: 25 After shallow.Addresses.Add(): Original Addresses: 123 Main St, 456 Oak Ave, 789 Pine Rd Shallow Addresses: 123 Main St, 456 Oak Ave, 789 Pine Rd Same List Reference? True === Deep Copy === Original Name: Jane Deep Name: Jane After deep.Age = 35: Original Age: 28 Deep Age: 35 After deep.Addresses.Add(): Original Addresses: 100 First St Deep Addresses: 100 First St, 200 Second Ave Same List Reference? False
วิธีทำ Deep Copy ทางเลือกต่างๆ
1. Manual Copy (Constructor/Method) - เร็วที่สุด ควบคุมได้
public class Person
{
public Person DeepCopy() => new Person
{
Name = this.Name,
Age = this.Age,
Addresses = new List(this.Addresses),
// Nested Object ต้อง Deep Copy ต่อ
Profile = this.Profile?.DeepCopy()
};
}
2. JSON Serialization (ง่าย ช้า)
using System.Text.Json;
public T DeepCopyJson(T obj)
{
var json = JsonSerializer.Serialize(obj);
return JsonSerializer.Deserialize(json);
}
3. Binary Serialization (Legacy - ต้อง [Serializable])
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public T DeepCopyBinary(T obj) where T : class
{
using var ms = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
4. AutoMapper / Mapster (Library)
// AutoMapper
var config = new MapperConfiguration(cfg => cfg.CreateMap());
var mapper = config.CreateMapper();
var deepCopy = mapper.Map(original);
// Mapster (เร็วกว่า)
var deepCopy = original.Adapt();
5. ICloneable Interface (Standard Pattern)
public class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public List Addresses { get; set; }
public object Clone()
{
return new Person
{
Name = this.Name,
Age = this.Age,
Addresses = new List(this.Addresses)
};
}
}
// Usage
var copy = (Person)original.Clone();
ตารางเปรียบเทียบวิธี Deep Copy
| วิธี | Speed | Complexity | Remarks |
|---|---|---|---|
| Manual | ⚡ Fastest | High | Control ทุกอย่าง เหมาะ Performance Critical |
| JSON Serialize | 🐢 Slow | Low | ง่าย รองรับ Complex Graph ต้อง [JsonInclude] private fields |
| Binary Formatter | 🐢 Slow | Low | Legacy, Security Risk, Obsolete |
| Mapster | ⚡ Fast | Low | Library แนะนำ เร็ว ใช้ Expression Tree |
| AutoMapper | 🐢 Medium | Low | Feature ริช แต่ช้ากว่า Mapster |
| MemberwiseClone + Manual Fix | ⚡ Fast | Medium | Shallow แล้ว Fix Reference Fields เอง |
Struct = Value Type = Auto Deep Copy
struct Point
{
public int X, Y;
public List Data; // Reference Type ใน Struct!
}
var p1 = new Point { X = 1, Y = 2, Data = new List { 1, 2 } };
var p2 = p1; // Value Type Copy - ทุก Field ถูก Copy
p2.X = 10;
p2.Data.Add(3);
Console.WriteLine(p1.X); // 1 (ไม่เปลี่ยน)
Console.WriteLine(p1.Data.Count); // 3! ⚠️ Reference Type ใน Struct ยัง Share อยู่
⚠️ Struct ที่มี Reference Type Field ยังคงเป็น Shallow Copy สำหรับ Field นั้น!
Struct Copy = Bitwise Copy ของ Fields ทั้งหมด Reference Field ยังชี้ Object เดียวกัน
Struct Copy = Bitwise Copy ของ Fields ทั้งหมด Reference Field ยังชี้ Object เดียวกัน
สรุป
- Shallow Copy: Copy Value Types, Share Reference Types (MemberwiseClone)
- Deep Copy: Copy ทุกอย่าง สร้าง Instance ใหม่ทั้งหมด
- Default Assignment (=) สำหรับ Class = Reference Copy (ชี้ Object เดียวกัน)
- Default Assignment สำหรับ Struct = Value Copy (แต่ Ref Fields ยัง Share)
- เลือกวิธี Deep Copy ตาม: Performance, Complexity, Library Dependency
- Immutable Types (string, record, readonly struct) - Copy Reference ปลอดภัย