02 กรกฎาคม 2568
ในบทความนี้ เราจะมาดูคุณสมบัติใหม่บางส่วนที่ถูกเพิ่มเข้ามาใน 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 ซึ่งเป็นรากฐานของการเขียนโปรแกรมแบบฟังก์ชันใน 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 ความก้าวหน้าเหล่านี้แก้ไขความยุ่งยากที่แท้จริงในการเขียนโค้ดประจำวัน พวกเขาลบไวยากรณ์ที่ไม่จำเป็นออกไป ยกระดับความสามารถในการอ่าน และบังคับใช้รูปแบบที่ปลอดภัยยิ่งขึ้น ซึ่งช่วยเพิ่มประสิทธิภาพในกระบวนการพัฒนาซอฟต์แวร์และโครงการแปลงโค้ดโดยตรง นวัตกรรมแต่ละอย่าง—ไม่ว่าจะเป็นการบังคับใช้สมาชิกที่ต้องระบุหรือการทำให้การตั้งค่าคอลเลกชันง่ายขึ้น—ช่วยลดการกดแป้นพิมพ์ในขณะที่เพิ่มความชัดเจนและลดความเสี่ยงของข้อผิดพลาดให้เหลือน้อยที่สุด