0Pricing
C# Academy · บทเรียน

ความไม่เปลี่ยนแปลงด้วย init และ with

ใช้ตัวกำหนดค่าแบบ init เท่านั้นเพื่อสร้างออบเจ็กต์ที่ไม่เปลี่ยนแปลง และใช้นิพจน์ with เพื่อสร้างสำเนาที่แก้ไขโดยไม่ทำลายออบเจ็กต์เดิม

ความไม่เปลี่ยนแปลงด้วย init และ with เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

ความไม่เปลี่ยนแปลงคืออะไร

ออบเจ็กต์ที่ ไม่เปลี่ยนแปลง จะไม่สามารถเปลี่ยนแปลงได้หลังจากสร้างแล้ว ความไม่เปลี่ยนแปลงช่วยป้องกันการเปลี่ยนค่าโดยไม่ตั้งใจ ทำให้ออบเจ็กต์ปลอดภัยต่อเธรดโดยค่าเริ่มต้น และทำให้การทำความเข้าใจโค้ดง่ายขึ้น C# มีตัวตั้งค่า init และนิพจน์ with เพื่อให้การสร้างออบเจ็กต์ที่ไม่เปลี่ยนแปลงทำได้สะดวก

ตัวตั้งค่าที่ใช้ได้เฉพาะกับ init

ตัวเข้าถึง init อนุญาตให้กำหนดคุณสมบัติระหว่างการเริ่มต้นออบเจ็กต์ เช่น ในตัวสร้างหรือตัวเริ่มต้นออบเจ็กต์ แต่จะไม่สามารถกำหนดได้อีกหลังจากนั้น นี่คือรูปแบบที่ไม่เปลี่ยนแปลงของ set

public class Point
{
    public double X { get; init; }
    public double Y { get; init; }
}

var p = new Point { X = 3.0, Y = 4.0 }; // OK — init phase
p.X = 5.0; // COMPILE ERROR — cannot set after init

// All of these are valid init-phase assignments:
var p2 = new Point(X: 1.0, Y: 2.0);
var p3 = new Point { X = 0, Y = 0 };

init ในคุณสมบัติแบบระบุตำแหน่งของเรคคอร์ด

คุณสมบัติแบบระบุตำแหน่งของเรคคอร์ดจะใช้ init เท่านั้นโดยค่าเริ่มต้น ซึ่งเป็นสิ่งที่ทำให้เรคคอร์ดไม่เปลี่ยนแปลง คอมไพเลอร์จะสร้าง get; init; ให้กับพารามิเตอร์แบบระบุตำแหน่งแต่ละรายการ

// This record:
public record Person(string Name, int Age);

// Is equivalent to:
public record Person
{
    public string Name { get; init; }
    public int Age    { get; init; }
    public Person(string Name, int Age) { this.Name = Name; this.Age = Age; }
    // + Deconstruct, Equals, GetHashCode, ToString
}

var alice = new Person("Alice", 30);
alice.Name = "Bob"; // COMPILE ERROR

นิพจน์ with: การเปลี่ยนแปลงแบบไม่ทำลายข้อมูล

นิพจน์ with จะสร้างสำเนาของเรคคอร์ดพร้อมเปลี่ยนคุณสมบัติบางรายการ เรคคอร์ดเดิมจะไม่เปลี่ยนแปลง จึงยังคงไม่เปลี่ยนแปลงอย่างแท้จริง

public record Person(string Name, int Age, string Email);

var alice = new Person("Alice", 30, "alice@example.com");

// Create a modified copy — alice is unchanged
var olderAlice = alice with { Age = 31 };
var renamed    = alice with { Name = "Alicia", Email = "alicia@example.com" };

Console.WriteLine(alice.Name);      // Alice (unchanged)
Console.WriteLine(olderAlice.Age);  // 31
Console.WriteLine(renamed.Name);    // Alicia

with กับชนิดที่ไม่ใช่เรคคอร์ด

C# 10 อนุญาตให้ใช้ with กับสตรักต์หรือคลาสใด ๆ ที่มีความหมายของตัวสร้างสำเนา แต่การใช้งานกับเรคคอร์ดเป็นธรรมชาติมากที่สุด สำหรับคลาส คุณต้องนำความสามารถนี้ไปสร้างเอง

// struct with with-expression:
public struct Temperature
{
    public double Celsius { get; init; }
    public double Fahrenheit => Celsius * 9 / 5 + 32;
}

var t1 = new Temperature { Celsius = 20 };
var t2 = t1 with { Celsius = 25 }; // copy with change
Console.WriteLine(t1.Celsius); // 20 — unchanged
Console.WriteLine(t2.Celsius); // 25

การเชื่อมโยงนิพจน์ with

เชื่อมโยงนิพจน์ with หลายรายการเพื่อสร้างการแปลงที่ซับซ้อนขึ้น โดยแต่ละขั้นตอนจะสร้างค่าที่ไม่เปลี่ยนแปลงใหม่จากค่าก่อนหน้า

public record Order(int Id, string Status, decimal Total, DateTime UpdatedAt);

var order = new Order(42, "Pending", 99.99m, DateTime.UtcNow);

// Apply a promotion discount then mark as confirmed
var finalOrder = order
    with { Total    = order.Total * 0.9m }    // 10% off
    with { Status   = "Confirmed" }
    with { UpdatedAt = DateTime.UtcNow };

Console.WriteLine(order.Status);     // Pending (original unchanged)
Console.WriteLine(finalOrder.Status); // Confirmed

คุณสมบัติที่คำนวณได้บนชนิดที่ไม่เปลี่ยนแปลง

คุณสมบัติที่ได้มาจากชนิดที่ไม่เปลี่ยนแปลงจะบริสุทธิ์ตามธรรมชาติ โดยคำนวณจากค่าคุณสมบัติที่ไม่เปลี่ยนแปลง และจะส่งคืนผลลัพธ์เดิมเสมอเมื่อได้รับข้อมูลเข้าเดียวกัน

public record Money(decimal Amount, string Currency)
{
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException("Currency mismatch");
        return this with { Amount = Amount + other.Amount };
    }

    public Money Multiply(decimal factor) =>
        this with { Amount = Amount * factor };

    public override string ToString() =>
        $"{Amount:F2} {Currency}";
}

var price = new Money(10.00m, "USD");
var tax   = price.Multiply(0.08m);
var total = price.Add(tax);
Console.WriteLine(total); // 10.80 USD

คอลเลกชันที่ไม่เปลี่ยนแปลง

ผสานเรคคอร์ดเข้ากับ ImmutableList<T> และชนิดอื่น ๆ จาก System.Collections.Immutable เพื่อสร้างโครงสร้างข้อมูลที่ไม่เปลี่ยนแปลงอย่างสมบูรณ์

using System.Collections.Immutable;

public record ShoppingCart(
    string UserId,
    ImmutableList<CartItem> Items)
{
    public ShoppingCart AddItem(CartItem item) =>
        this with { Items = Items.Add(item) };

    public ShoppingCart RemoveItem(int productId) =>
        this with { Items = Items.RemoveAll(i => i.ProductId == productId) };

    public decimal Total => Items.Sum(i => i.Price * i.Quantity);
}

ความปลอดภัยต่อเธรดจากความไม่เปลี่ยนแปลง

ออบเจ็กต์ที่ไม่เปลี่ยนแปลงจะปลอดภัยต่อเธรดโดยธรรมชาติ ไม่จำเป็นต้องซิงโครไนซ์เมื่อแชร์ออบเจ็กต์ข้ามเธรด เพราะสถานะของออบเจ็กต์ไม่สามารถเปลี่ยนแปลงได้หลังการสร้าง

// Immutable config record shared across all threads safely
public record AppConfig(
    string ConnectionString,
    int MaxRetries,
    TimeSpan Timeout);

// Register as singleton — safe because record is immutable
builder.Services.AddSingleton(
    new AppConfig(
        ConnectionString: config["DB"]!,
        MaxRetries: 3,
        Timeout: TimeSpan.FromSeconds(30)));

// Any thread can read this simultaneously without locks

การใช้งานจริง: การสร้างสถานะจากเหตุการณ์เชิงฟังก์ชัน

เรคคอร์ดที่ไม่เปลี่ยนแปลงเข้ากันได้อย่างเป็นธรรมชาติกับการสร้างสถานะจากเหตุการณ์ เหตุการณ์ของโดเมนแต่ละรายการจะไม่เปลี่ยนแปลง และสถานะจะได้มาจากการพับรวมเหตุการณ์ จึงไม่มีการเปลี่ยนค่าและไม่มีเรื่องไม่คาดคิด

public record OrderState(
    int Id,
    string Status = "Draft",
    decimal Total = 0m);

public static OrderState Apply(OrderState state, object evt) => evt switch
{
    OrderPlaced   e => state with { Status = "Pending", Total = e.Total },
    OrderShipped  _ => state with { Status = "Shipped" },
    OrderCancelled _ => state with { Status = "Cancelled" },
    _ => state
};

// Fold events to get current state:
var state = events.Aggregate(
    new OrderState(id),
    Apply);

การตรวจสอบอย่างรวดเร็ว

นิพจน์ 'with' ทำอะไรกับเรคคอร์ดเดิม

สรุป: ความไม่เปลี่ยนแปลงด้วย init และ with

ประเด็นสำคัญ:

  • ตัวเข้าถึง init: กำหนดค่าได้เฉพาะระหว่างการเริ่มต้น และไม่สามารถกำหนดได้อีกหลังจากนั้น
  • คุณสมบัติแบบระบุตำแหน่งของเรคคอร์ดใช้ init เท่านั้นโดยค่าเริ่มต้น
  • นิพจน์ with: สร้างสำเนาที่ปรับเปลี่ยนแล้ว โดยเรคคอร์ดเดิมไม่เปลี่ยนแปลง
  • เชื่อมโยงนิพจน์ with สำหรับการแปลงหลายขั้นตอน
  • ชนิดที่ไม่เปลี่ยนแปลงปลอดภัยต่อเธรดโดยไม่ต้องซิงโครไนซ์
  • ใช้ร่วมกับ ImmutableList<T> เพื่อสร้างกราฟออบเจ็กต์ที่ไม่เปลี่ยนแปลงอย่างสมบูรณ์

คำถามที่พบบ่อย

บทเรียน “ความไม่เปลี่ยนแปลงด้วย init และ with” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ความไม่เปลี่ยนแปลงด้วย init และ with” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ความไม่เปลี่ยนแปลงด้วย init และ with”

ใช้ตัวกำหนดค่าแบบ init เท่านั้นเพื่อสร้างออบเจ็กต์ที่ไม่เปลี่ยนแปลง และใช้นิพจน์ with เพื่อสร้างสำเนาที่แก้ไขโดยไม่ทำลายออบเจ็กต์เดิม คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “ความไม่เปลี่ยนแปลงด้วย init และ with” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม

ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ชนิด Record: พื้นฐานและไวยากรณ์
  2. ความไม่เปลี่ยนแปลงด้วย init และ with
  3. ความเท่ากันของค่าและการแยกโครงสร้าง
  4. Records ในการออกแบบที่ขับเคลื่อนด้วยโดเมน
← กลับไปที่ C# Academy