Raw string literals
כדי להוסיף סימנים מיוחדים למחרוזת כמו " או \ צריך להוסיף תו \ לפני (פעולת escape) על מנת שהקומפיילר יבין שיש פה תו שצריך להתייחס אליו כמו שהוא כתוב.
דרך פשוטה יותר ב-C# 11 היא להתחיל מחרוזת עם """ (שלוש מרכאות) ולסיים עם """. כל מה שבפנים יקבל התייחסות של תו רגיל. בצורה הזאת אין צורך לעבור על המחרוזת ולהוסיף קידומת לכל תו יוחד והמחרוזת תהיה קריאה יותר.
זה עובד גם עבור שורה חדשה באמצע המחרוזת, אין צורך להשתמש ב-\n אפשר לשים """ וכל מעבר שורה יעבוד.
אפשר לגם להכניס שימוש במשתנים על ידי {} אם שמים $ בתחילת המחרוזת לפני ה-". ואם יש במחרוזת סימן של { שאנחנו רוצים שיחשב כתו (למשל במחרוזת json) וגם להשתמש ב-{} על מנת להוסיף משתנה, נשים $$ בתחילת המחרוזת ונשתמש ב-{{}} עבור המשתנה.
var someText = $"""<?xml version="{ version }" ?>""";
var jsonText = """
{
"name": "Jack"
}
""";
var jsonTextWithVar = $$"""
{
"name": {{ name }}
}
""";
List patterns
אפשר לבדוק האם מערך או רשימה (כלמה שיש לו תכונת count) מתאים לתבנית מסויימת. התבנית יכולה להיות מפורשת או להתאים לתנאים מסויימים. אפשר להשתמש בזה גם במחרוזות ולא רק במספרים.
int[] numbers = {1, 2, 3};
Console.WriteLine(numbers is [1, 2, 3]); // True
Console.WriteLine(numbers is [1, 2, 4]); // False
Console.WriteLine(numbers is [1, 2, 3, 4]); // False
Console.WriteLine(numbers is [0 or 1, <=2, >=3]); // True
// Will print 1 and ignore the second and third values.
if(numbers is [var first, _, _])
{
Console.WriteLine(first);
}
// Will print 1 and put the rest, the 2, 3 in an array
if(numbers is [var first, .. var rest])
{
Console.WriteLine(first);
}
Generics on attributes
אפשר להעביר ל-Attribute סוג T גנרי ולהשתמש בו ביותר גמישות.
Extended nameof scope
[Name(nameof(text))]
public void Test(string text)
{
...
}
UTF-8 string literals
אם רוצים להשתמש במחרוזות כ-Utf-8 לשליחה ב-response אפשר לעשות את זה.
ReadOnlySpan<byte> text = "Some Text"u8;
String interpolated new line
אפשר להעביר שורות בתוך קריאה למשתנים במחרוזת (לקריאות). קודם לא היה אפשרי לעשות את מעבר השורה הזה.
var world = "World";
Console.WriteLine($"Hello, {world
.ToLower()}!");
Generic maths
נניח שיש לנו מערך של מספרים ופונקציה שמחשבת את הסכום שלהם כמו בקוד שלפנינו:
int[] numbers = new[] { 1, 2, 3, 4 };
var sum = AddAll(numbers);
int AddAll(int[] values )
{
int result = 0;
foreach(var value in values) {
result += value;
}
return result;
}
אם נוסיף למערך מספר שאינו int, למשל double, נקבל שגיאה.
int[] numbers = new[] { 1, 2, 3, 4, 8.6 }; // Error
אם נרצה שהפונקציה תעבוד עם double נצטרך לבנות פונקציה חדשה בשם אחר, ולהפך בה כל מקום שיש בו int ל-double.
מה שאפשר לעשות במקום זה ב-C#11 זה לשלוח סוג גנרי T לפונקציה ואף להגיד ש-T הוא מסוג מספר ולאפס אותו.
var numbers = new[] { 1, 2, 3, 4, 8.6 };
var sum = AddAll(numbers);
T AddAll<T>(T[] values ) where T : INumber<T>
{
T result = T.Zero;
foreach(var value in values) {
result += value;
}
return result;
}
Required members
כשיש לנו property בתוך class שאנחנו מאתחלים, אפשר לסמן את השדה כנדרש עם המילה required. בדרך הזאת אין צורך ליצור constructor.
class User
{
public required string FullName { get; init; }
}
// Have to create with value
var user = new User {
FullName = "Jack"
};
File scoped types
אם יש לי שני קבצים שונים ובהם מוגדר class עם אותו השם, תהיה שגיאה, מכיוון שה-class מוכר ברמת הפרויקט ויש התנגשות. מה שאפשר לעשות זה להגדיר את ה-class ברמת הקובץ שהוא מוגדר בו בדרך הזאת:
file class User
{
....
}
Pattern matching on Spans for constant strings
ReadOnlySpan<char> text = "some text";
if(text is "some text") {
Console.WriteLine("This is some text");
}
if(text is ['s', ..]) {
Console.WriteLine("s is the first letter");
}
Auto-default structs
נניח שיש לי struct עם constructor. עד עכשיו, אם שלחתי ל-constructor ערכים, הייתי חייבת להשתמש בהם בתוך ה-constructor אחרת היתה מתקבלת שגיאה. עכשיו אפשר לא להשתמש בערכים ו-C# ישים עבורם ערכי ברירת מחדל.
public struct Point
{
public int X;
public int Y;
public Point(int x, int y) {
X = x;
// Y will get 0
}
}
Improved method group conversion to delegate
נראה את ה-class הבא: יש לנו רשימה של מספרים מ-0 עד 100 ושתי פונקציות. שתיהן עושות את אותו הדבר, משתמשות בפונקציה filter ומקבלות את אותה התוצאה. ההבדל הוא שבגרסאות הקודמות של C# הגרסה השנייה היתה איטית יותר למרות שהפעולה היתה זהה. עכשיו שתיהן עובדות אותו הדבר.
public class FilterStuff
{
private static readonly List<int> Ages = Enumerable.Range(0, 100).ToList();
public int sum() {
return Ages.Where(x => Filter(x)).Sum();
}
public int SumMethodGroup() {
return Ages.Where(Filter).Sum();
}
static bool Filter(int age) {
return age > 50;
}
}
לצפייה בסרטון הסבר: