02 กรกฎาคม 2568

คุณสมบัติที่ดีที่สุดของ C# 11 และ 12: เขียนน้อยลง ทำได้มากขึ้น

ในบทความนี้ เราจะมาดูคุณสมบัติใหม่บางส่วนที่ถูกเพิ่มเข้ามาใน C# 11 และ 12 ซึ่งช่วยให้โค้ดของคุณง่ายขึ้นและทำให้การพัฒนาเป็นไปอย่างราบรื่น การอัปเดตเหล่านี้อาจไม่ใช่การเปลี่ยนแปลงที่ปฏิวัติวงการ แต่เป็นการออกแบบที่ใช้งานได้จริงและช่วยประหยัดเวลาโดยลดความซับซ้อนที่ไม่จำเป็น เราจะเห็นว่าการเปลี่ยนแปลงเล็กๆ สามารถนำไปสู่โซลูชันที่สะอาดและมีประสิทธิภาพมากขึ้นในงานเขียนโค้ดประจำวัน

สตริงลิเทอรัลแบบดิบ

การสร้างสตริงที่มีเนื้อหาซับซ้อนเป็นความท้าทายมาโดยตลอดใน C# นักพัฒนามักต้องจัดการกับการหลีกเลี่ยงอักขระพิเศษ บรรทัดใหม่ และเครื่องหมายคำพูด ซึ่งนำไปสู่โค้ดที่ยาวและมักอ่านยาก กระบวนการนี้จะยุ่งยากเป็นพิเศษเมื่อต้องจัดการกับรูปแบบเช่น JSON, XML หรือนิพจน์ปกติที่ฝังอยู่ในไฟล์ซอร์สโดยตรง

C# 11 ได้แนะนำสตริงลิเทอรัลแบบดิบเพื่อแก้ไขปัญหานี้โดยตรง คุณสมบัตินี้ช่วยให้สตริงสามารถครอบคลุมหลายบรรทัดและมีอักขระใดๆ ก็ได้ รวมถึงเครื่องหมายคำพูดและแบ็กสแลชที่ฝังอยู่ โดยไม่ต้องใช้ลำดับการหลีกเลี่ยง สตริงลิเทอรัลแบบดิบเริ่มต้นและสิ้นสุดด้วยเครื่องหมายคำพูดคู่สามตัวขึ้นไป (""")

ก่อน C# 11:

string oldJson = "{\r\n  \"name\": \"Alice\",\r\n  \"age\": 30\r\n}";
Console.WriteLine(oldJson);

ด้วย C# 11:

string newJson = """
  {
    "name": "Alice",
    "age": 30
  }
  """;
Console.WriteLine(newJson);

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

รูปแบบรายการ

การจับคู่รูปแบบใน C# ได้พัฒนาไปอย่างมาก โดย C# 11 ได้แนะนำรูปแบบรายการเพื่อให้สามารถจับคู่ลำดับภายในอาร์เรย์หรือรายการได้ การปรับปรุงนี้ช่วยให้นักพัฒนาสามารถตรวจสอบโครงสร้างและเนื้อหาของคอลเลกชันได้อย่างกระชับและชัดเจน

ก่อนหน้านี้ การตรวจสอบโครงสร้างคอลเลกชันต้องมีการตรวจสอบความยาวและดัชนีแต่ละรายการด้วยตนเอง ซึ่งนำไปสู่โค้ดที่ยาวและยากต่อการบำรุงรักษา รูปแบบรายการแก้ไขปัญหานี้โดยรองรับรูปแบบย่อย เช่น รูปแบบคงที่ รูปแบบประเภท รูปแบบคุณสมบัติ และรูปแบบสัมพันธ์ คุณสมบัติหลักรวมถึงรูปแบบการละทิ้ง (_) สำหรับการจับคู่元素เดียวใดๆ และรูปแบบช่วง (..) สำหรับการจับคู่ลำดับของศูนย์หรือมากกว่าศูนย์元素

ก่อน C# 11:

int[] numbers = { 1, 2, 3 };

if (numbers != null && numbers.Length == 3 &&
    numbers[0] == 1 && numbers[1] == 2 && numbers[2] == 3)
{
    Console.WriteLine("Array contains exactly 1, 2, 3.");
}

if (numbers != null && numbers.Length >= 2 && numbers[1] == 2)
{
    Console.WriteLine("Array has 2 as its second element.");
}

ด้วย C# 11:

int[] numbers = { 1, 2, 3 };

if (numbers is [1, 2, 3])
{
    Console.WriteLine("Array contains exactly 1, 2, 3.");
}

if (numbers is [_, 2, ..])
{
    Console.WriteLine("Array has 2 as its second element.");
}

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

สมาชิกที่ต้องระบุ

การเริ่มต้นอ็อบเจ็กต์บางครั้งอาจนำไปสู่สถานะที่ไม่พึงประสงค์ ซึ่งคุณสมบัติหรือฟิลด์ที่สำคัญยังคงไม่ได้กำหนดค่า โดยปกติแล้ว นักพัฒนาจะบังคับให้มีการเริ่มต้นที่จำเป็นผ่านคอนสตรัคเตอร์ที่รับพารามิเตอร์ทั้งหมดที่ต้องการ หรือโดยการเพิ่มการตรวจสอบป้องกันภายในเมธอด

C# 11 แนะนำตัวแก้ไข required สำหรับคุณสมบัติและฟิลด์ ซึ่งเป็นกลไกการบังคับใช้ในเวลาคอมไพล์ เมื่อสมาชิกถูกทำเครื่องหมายว่า required คอมไพเลอร์จะตรวจสอบให้แน่ใจว่าสมาชิกนั้นได้รับค่าในระหว่างการสร้างอ็อบเจ็กต์ ไม่ว่าจะผ่านคอนสตรัคเตอร์หรือตัวเริ่มต้นอ็อบเจ็กต์ ซึ่งรับประกันว่าอินสแตนซ์ของประเภทจะอยู่ในสถานะที่ถูกต้องและสมบูรณ์เสมอ ป้องกันข้อบกพร่องทั่วไปที่เกี่ยวข้องกับข้อมูลที่ขาดหายไป

ก่อน C# 11:

public class OldPerson
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public void DisplayName() => Console.WriteLine($"Name: {FirstName} {LastName}");
}

// การใช้งาน:
var person = new OldPerson(); // ไม่มีข้อผิดพลาดในเวลาคอมไพล์ แต่สร้างอ็อบเจ็กต์ที่อาจไม่ถูกต้อง
person.DisplayName();

ด้วย C# 11:

public class NewPerson
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    public void DisplayName() => Console.WriteLine($"Name: {FirstName} {LastName}");
}

// การใช้งาน:
// var person = new NewPerson(); // ข้อผิดพลาดในเวลาคอมไพล์ - ขาดคุณสมบัติที่ต้องระบุ
// var person = new NewPerson { FirstName = "John" }; // ข้อผิดพลาดในเวลาคอมไพล์ - ขาด LastName
var person = new NewPerson { FirstName = "Jane", LastName = "Doe" }; // ถูกต้อง
person.DisplayName();

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

คอนสตรัคเตอร์หลัก

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

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

ก่อน C# 12:

public class OldProduct
{
    private readonly int _productId;
    private readonly string _productName;

    public OldProduct(int productId, string productName)
    {
        _productId = productId;
        _productName = productName;
    }

    public string PrintDetails() => $"Product ID: {_productId}, Name: {_productName}";
}

// การใช้งาน:
OldProduct oldProd = new OldProduct(101, "Laptop");
oldProd.PrintDetails();

ด้วย C# 12:

public class NewProduct(int productId, string productName)
{
    public string PrintDetails() => $"Product ID: {productId}, Name: {productName}";
}

// การใช้งาน:
NewProduct newProd = new NewProduct(102, "Keyboard");
newProd.PrintDetails();

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

นิพจน์คอลเลกชัน

การเริ่มต้นคอลเลกชันใน C# โดยทั่วไปเกี่ยวข้องกับไวยากรณ์ที่หลากหลายขึ้นอยู่กับประเภทของคอลเลกชัน เช่น new List<T> { ... } สำหรับรายการ หรือ new T[] { ... } สำหรับอาร์เรย์ การรวมหรือผสานคอลเลกชันที่มีอยู่เข้าในคอลเลกชันใหม่มักต้องใช้ลูปซ้ำหรือเมธอด LINQ เช่น Concat() ซึ่งเพิ่มภาระและความยาวของโค้ด

C# 12 แนะนำนิพจน์คอลเลกชัน ซึ่งเป็นไวยากรณ์ที่รวมเป็นหนึ่งและกระชับสำหรับการสร้างและเริ่มต้นคอลเลกชันประเภทต่างๆ โดยใช้ไวยากรณ์ [...] ที่เรียบง่าย นักพัฒนาสามารถสร้างอาร์เรย์ รายการ Span<T> และประเภทคอลเลกชันอื่นๆ ได้ องค์ประกอบการกระจายใหม่ (..) ช่วยให้สามารถแทรกองค์ประกอบจากคอลเลกชันที่มีอยู่เข้าไปในนิพจน์คอลเลกชันใหม่ได้โดยตรง ขจัดความจำเป็นในการเชื่อมต่อด้วยตนเอง

ก่อน C# 12:

// การเริ่มต้นคอลเลกชันที่แตกต่างกัน
int[] initialNumbers = new int[] { 1, 2, 3 };
List<int> moreNumbers = new List<int> { 4, 5 };

// การรวมคอลเลกชัน
List<int> allNumbers = new List<int>();
allNumbers.AddRange(initialNumbers);
allNumbers.AddRange(moreNumbers);
allNumbers.Add(6);
allNumbers.Add(7);

Console.WriteLine(string.Join(", ", allNumbers));

ด้วย C# 12:

// การเริ่มต้นคอลเลกชันที่แตกต่างกัน
int[] initialNumbers = [1, 2, 3];
List<int> moreNumbers = [4, 5];

// การรวมคอลเลกชันโดยใช้ตัวดำเนินการกระจาย
List<int> allNumbers = [..initialNumbers, ..moreNumbers, 6, 7];

Console.WriteLine(string.Join(", ", allNumbers));

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

พารามิเตอร์ค่าเริ่มต้นของ Lambda

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

C# 12 ปิดช่องว่างนี้โดยอนุญาตให้กำหนดค่าเริ่มต้นสำหรับพารามิเตอร์ในนิพจน์ Lambda ไวยากรณ์และพฤติกรรมสะท้อนถึงพารามิเตอร์ของเมธอดหรือฟังก์ชันท้องถิ่น ส่งมอบวิธีที่ลื่นไหลและกระชับมากขึ้นในการนิยามฟังก์ชัน Lambda ที่ยืดหยุ่น

ก่อน C# 12:

// Lambda โดยไม่มีพารามิเตอร์ค่าเริ่มต้น
// หากต้องการค่าเริ่มต้นสำหรับ 'y' มักต้องใช้ตัวห่อหรือตรรกะเงื่อนไข:
Func<int, int, int> addOld = (x, y) => x + y;

Func<int, int> addWithDefaultOld = x => addOld(x, 10); // วิธีแก้ปัญหาทั่วไป

Console.WriteLine(addOld(5, 3));
Console.WriteLine(addWithDefaultOld(5));

ด้วย C# 12:

// Lambda ที่มีพารามิเตอร์ค่าเริ่มต้น
Func<int, int, int> addNew = (x, y = 10) => x + y;

Console.WriteLine(addNew(5, 3)); // y เป็น 3
Console.WriteLine(addNew(5));    // y เป็นค่าเริ่มต้น 10

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

สรุป

C# 11 และ 12 นำเสนอชุดคุณสมบัติที่น่าสนใจซึ่งสอดคล้องกับคำมั่นสัญญา “เขียนน้อยลง ทำได้มากขึ้น” ตั้งแต่สตริงลิเทอรัลแบบดิบและรูปแบบรายการของ C# 11 ไปจนถึงคอนสตรัคเตอร์หลักและนิพจน์คอลเลกชันของ C# 12 ความก้าวหน้าเหล่านี้แก้ไขความยุ่งยากที่แท้จริงในการเขียนโค้ดประจำวัน พวกเขาลบไวยากรณ์ที่ไม่จำเป็นออกไป ยกระดับความสามารถในการอ่าน และบังคับใช้รูปแบบที่ปลอดภัยยิ่งขึ้น ซึ่งช่วยเพิ่มประสิทธิภาพในกระบวนการพัฒนาซอฟต์แวร์และโครงการแปลงโค้ดโดยตรง นวัตกรรมแต่ละอย่าง—ไม่ว่าจะเป็นการบังคับใช้สมาชิกที่ต้องระบุหรือการทำให้การตั้งค่าคอลเลกชันง่ายขึ้น—ช่วยลดการกดแป้นพิมพ์ในขณะที่เพิ่มความชัดเจนและลดความเสี่ยงของข้อผิดพลาดให้เหลือน้อยที่สุด

บทความที่เกี่ยวข้อง