Global using statements
השימוש ב-using השתנה, גם הקוד הקודם עובד.
מה שהיה קודם הוא:
using System.Text.Json
var names = new[] {"A", "B", "C"};
var serialized = JsonSerializer.Serialize(names);
Controle.WriteLine(serialized);
אם יש שימוש בספרייה תדירה, היה צריך להוסיף אותה בכל אחד מהקבצים. מה שאפשר לעשות עכשיו זה ליצור class ולשים בה את ה-using עם global לפניו.
קובץ GlobalUsing.cs
global using System.Text.Json
עכשיו כל קובץ יוכל לגשת ל-using הזה.
אפשר גם לגשת לקובץ proj ולהוסיף ItemGroup כ-using.
<Project>
<ProjectGroup>
<ImplicitUsing>enable</ImplicitUsing>
</ProjectGroup>
<ItemGroup Condition="'$(ImplicitUsing)' == true Or $(ImplicitUsing)' == 'empty'">
<using Include="System.Text.Json"/>
</ItemGroup>
</Project>
File scoped namespaces
לפני גרסה 10 ככה נראה השימוש ב-namespace:
namespace School
{
class Person
{
public string Name { get; set; }
}
}
בתאוריה זה ככה, כי אפשר לשים יותר מ-namespace אחד בקובץ. בפועל זה לא קורה ולכן עברו ל-namespace שרלוונטי לכל הקובץ.
namespace School;
class Person
{
public string Name { get; set; }
}
Constant interpolated strings
לפני גרסה 10, לא היה אפשר להשתמש ב-string interpulation בתוך const (כלומר להשתמש בקריאה למשתנים בתוך {}). עכשיו זה אפשרי.
private const string ApiBase = "/api";
private const string Base = $"{ApiBase}/{{id:guid}}";
Lambda improvements
יכולה להיות לנו פונקציה כזאת:
Func<string> helloWorld = () => "Hello World"; // OK, work fine
var helloWorld = () => "Hello World"; // NOT working in v9, OK on v10
עכשיו הקומפיילר מבין לבד את הסוג של הפונקציה.
לגבי פונקציה שמחזירה null, אם היינו עושים:
var text = () => null; // NOT working, null can be any type
Func<string> text = () => null; // OK, we know the type
זה לא יעבוד, כי null יכול להיות מכל מיני סוגים ואי אפשר לדעת מראש.
מה שאפשר עכשיו לעשות זה להגיד בהגדרת הפונקציה מה הסוג שיוחזר.
var text = string? () => null;
Extended property patterns
נניח שיש לנו רשומה של מלבן שבתוכה יכול להיות עוד מלבן:
record Rectangle(int Height, int Width, Rectangle? rectangle = null);
נניח שאנחנו יוצרים בתוכנית:
var rectangleInside = new Rectangle(100, 100);
var rectangle = new Rectangle(200, 300, rectangleInside);
נניח שאני רוצה לבצע בדיקות על המלבן:
var rectangleInside = new Rectangle(100, 100);
var rectangle = new Rectangle(200, 300, rectangleInside);
if (rectangle is {rectangle: {Height: > 100} })
{
}
עבור כל כניסה למלבן פנימה אני אצטרך להשתמש בעוד {} וזה מייצר בלאגן. עכשיו אפשר לגשת לבדיקה בצורה של גישה לתכונת המלבן:
if (rectangle is {rectangle.Height: > 100})
{
}
Record structs
זאת ההגדרה של struct:
internal struct Rectangle
{
public int Height { get; set; }
public int Width { get; set; }
}
אם היינו מגדירים record, הקומפיילר היה מניח שאנחנו מתכוונים ל-class והמשמעות היא שההשוואה בין 2 records היתה משווה בין התכונות שלהם ולא בין ה-reference שלהם.
internal record Person(string FirstName);
עכשיו אפשר להגדיר ככה גם struct:
internal record struct Person(string FirstName);
Record types can seal ToString
נניח שיש לנו מלבן וריבוע שיורש ממנו.
internal struct Rectangle
{
public override string ToString()
{
return $"Height is: {Height} and width is {Width}";
}
}
internal struct Square : Rectangle
{
protected Square(int sideLength) : base(sideLength, sideLength) {}
public override string ToString()
{
return $"Side length is: {sideLength}";
}
}
אנחנו מאפשרים להגדיר ריבוע רק על ידי צלע אחת. בדוגמא הריבוע דורס את ההגדרה של הפונקציה ToString של המלבן. אם רוצים, אפשר למנוע את זה על ידי sealed:
internal struct Rectangle
{
public sealed override string ToString()
{
return $"Height is: {Height} and width is {Width}";
}
}
Structure type improvements
בגרסה 9, אם יש לנו struct, כדי לאתחל את הערכים שלו בקונסטרקטור, נהיה חייבים להגדיר אותם בשליחה לפונקציה. גם אם יש להם ערכים בתוך הקונסטרקטור.
public struct Rectangle
{
public int Height { get; set; }
public int Width { get; set; }
public Rectangle(int height, int width)
{
Height = height;
Width = width;
}
public Rectangle() // NOT possible on v9
{
Height = 0;
Width = 0;
}
}
עוד דבר שלא היה אפשרי, זה להגדיר משתנה אחד מתוך אחר עם שליחת חלק מהערכים בלבד.
var rectangle = new Rectangle(100, 200);
var newRectangle = rectangle with { Width = 300 }; // NOT possible on v9
בגרסה 10 שני הדברים האלה אפשריים.
Assignment and declaration in the same deconstruction
האפשרות של Deconstruct היא לקחת אובייקט ולפרק אותו לחלקים שלו.
public struct Rectangle
{
...
internal void Deconstruct(out int height, out int width)
{
height = Height;
width = Width;
}
}
// Usage
var rectangle = new Rectangle(100, 200);
// Declaring the h, w and assign values
(int h, int w) = rectangle;
// Declaring
int h1 = 0;
int w1 = 0;
// And then assigning
(h1, w1) = rectangle;
// NOT possible on v9 - Possible on v10
int h3 = 0;
(h3, int w3) = rectangle;
בגרסה 9 לא היה אפשרי באותה השורה להגדיר חלק מהמשתנים ולעשות השמה לחלק אחר. בגרסה 10 אפשר.
סרטון עם הסברים: