מה חדש ב-C#10?

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 אפשר.

סרטון עם הסברים: