What's new in C × 8.0

01. Readonly member

The readonly modifier can be applied to any member of a structure, indicating that the member does not modify the state. This is finer than applying the readonly modifier to a struct declaration.

public struct Point
{
    public double X { get; set; }

    public double Y { get; set; }

    public double Distance => Math.Sqrt(X * X + Y * Y);

    public override string ToString() => $"({X}, {Y}) is {Distance} from the origin";
}

Like most structures, the ToString () method does not modify the state. This can be indicated by the readonly modifier added to the declaration of ToString():

public readonly override string ToString() => $"({X}, {Y}) is {Distance} from the origin";

The above changes will cause compiler warnings because ToString accesses the Distance property, which is not marked readonly, as shown in the following figure:

 

When you need to create a defensive copy, the compiler warns that the Distance property does not change the state, so you can fix this warning by adding the readonly modifier to the Declaration:

public readonly double Distance => Math.Sqrt(X * X + Y * Y);

Note that the readonly modifier is required for read-only properties. The compiler does not assume that the get accessor does not modify the state; readonly must be explicitly declared and the compiler enforces the following rules:

The readonly member does not modify the state, unless the readonly modifier, the following methods are not compiled:

public readonly void Translate(int xOffset,int yOffset)
{
    X += xOffset;
    Y += yOffset;
}

This feature allows you to specify a design intent, which the compiler can enforce, and optimize based on.

2. Default interface member

Members can now be added to the interface, without their implementation. With the sublanguage function, API sitting can add methods to the interface of later versions without damaging the compatibility with the currently implemented source or headset files in South China Sea. Some implementations now inherit the default interface. This function enables C to interoperate with APIs for Android or Swift, which support similar functions. Default interface members also support schemes similar to the feature language feature.

Default interface members affect many schema and language elements. https://www.cnblogs.com/SavionZhang/p/11203119.html

3. Use more modes in more locations

Pattern matching provides tools to provide shape related functions in relevant but different types of data. Cා7.0 introduces the syntax of type pattern and constant pattern by using is expression and switch statement. These functions represent an initial attempt to support programming paradigms when data and functionality are separated. As the industry moves to more microservices and other cloud based architectures, additional language capabilities are needed.

C × 8.0 extends this vocabulary. This allows you to use more schema expressions in more places in your code. Consider using these functions when data and functions are separated. Consider using pattern matching when the algorithm depends on facts other than the object runtime type. These technologies provide another way to express design.

In addition to the new mode used by grilled fish in the new location, recursion mode has been added to C × 8.0. The result of any pattern expression is an expression. A recursive pattern is just a pattern expression applied to the output of another pattern expression.

switch expression

Typically, a switch statement generates a value in each of its case blocks. With the switch expression, you can use more concise expression statements, just a little repeated case and break keyword amount braces. Take the following enumeration of rainbow colors for example:

public enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}

If the application defines the RGBColor type constructed by R, G, and B components, you can use the following method containing switch expression to convert Rainbow to RGB value:

public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
    Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
    Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
    Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
    Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
    Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
    Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
    Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
    _ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
};

Here are several grammar improvements:

1. The variable is before the switch keyword. Different order makes it easy to distinguish switch expression and switch statement visually.

2. Replace case and: element with = > which is more concise and intuitive.

3. Replace the default case with "discard".

4. The body is an expression, not a statement.

Compare it with the equivalent code using the classic switch statement:

public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
    switch (colorBand)
    {
        case Rainbow.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case Rainbow.Orange:
            return new RGBColor(0xFF, 0x7F, 0x00);
        case Rainbow.Yellow:
            return new RGBColor(0xFF, 0xFF, 0x00);
        case Rainbow.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case Rainbow.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        case Rainbow.Indigo:
            return new RGBColor(0x4B, 0x00, 0x82);
        case Rainbow.Violet:
            return new RGBColor(0x94, 0x00, 0xD3);
        default:
            throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
    };
}

Property mode:

With the property pattern, you can match the properties of all checked objects. Please see the case amount of an e-commerce website, which must calculate the sales tax according to the buyer's Address, which is the core responsibility of the Address class from time to time. It changes over time, perhaps more frequently than the Address format, and the amount of sales tax depends on the State attribute of the Address. The following method uses the attribute pattern to calculate sales tax from Address and price:

public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
    location switch
    {
        { State: "WA" } => salePrice * 0.06M,
        { State: "MN" } => salePrice * 0.75M,
        { State: "MI" } => salePrice * 0.05M,
        // other cases removed for brevity...
        _ => 0M
    };

Pattern matching creates a brief syntax for expressing this algorithm

Element mode:

Some algorithms rely on multiple inputs, using the tuple pattern, and can be based on multiple values expressed as tuples. The following code shows the switch expression of the game "rock, pack, scissors"

 public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };

The message shows the winner, the discard yuan represents the three combinations of draw (stone scissors game) or other text input.

Location mode

Some types contain the Deconstruct method, which can be used to Deconstruct their properties into discrete variables. If you have access to the Deconstruct method, you can use the location pattern to examine the properties of the object and use those properties for the pattern. Consider the following Point classes. It contains the Deconstruct method for creating discrete variables for X and Y:

public class Point
{
    public int X { get; set; }

    public int Y { get; set; }

    public Point(int x, int y) => (X, Y) = (x, y);

    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

In addition, consider the following enumeration of locations that represent quadrants:

 public enum Quadrant
 {
     Unknown,
     Origin,
     One,
     Two,
     Three,
     Four,
     OnBorder
 }

The following method uses the location pattern to extract the values of x and y, and then uses when to determine the quality of the change point:

static Quadrant GetQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};

When x or y is 0 (but not both are 0), the discard pattern in the previous switch matches, and the switch expression must either generate a value or throw an exception. If none of these conditions match, the switch expression throws an exception. The compiler generates a warning if all possible situations are not covered in the switch expression.

You can explore pattern matching methods in the advanced course of pattern matching. https://docs.microsoft.com/zh-cn/dotnet/csharp/tutorials/pattern-matching

4. using statement

The using declaration is a variable declaration preceded by the using keyword. It indicates that the variables declared by the compiler should be processed at the end of the enclosing scope. Take the following code for writing a text file as an example:

static void WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    foreach (var line in lines)
    {
        //If the line does not contain a word“ Second",Write the line to the file
        if (!line.Contains("Second"))
        {
            file.WriteLine(line);
        }
    }
    //File released here
}

In the previous example, when the closing bracket of the method is reached, the file will be processed. This is the end of the range of the declared file. The preceding code is equivalent to the following code using the classic using statement:

static void WriteLinesToFile(IEnumerable<string> lines)
{
    using (var file=new System.IO.StreamWriter("WriteLines2.txt"))
    {
        foreach (var line in lines)
        {
            //If the line does not contain a word“ Second",Write the line to the file
            if (!line.Contains("Second"))
            {
                file.WriteLine(line);
            }
        }
    }//File released here
}

In the previous example, the file is processed when the closing bracket associated with the using statement is reached. In both cases, the compiler generates a call to Dispose(). If an expression in a using statement is not resolvable, the compiler generates an error.

5. Static local function

You can now add a static modifier to the local function to ensure that the local function does not capture (Reference) any variables from the enclosing scope. Doing so generates CS8421, "static local functions cannot contain references to < variable >."

Considering the following code, the local function accesses the variable y declared in the enclosing scope (method M), so the local function cannot be declared with the static modifier:

int M()
{
    int y;
    LocalFunction();
    return y;

    void LocalFunction() => y = 0;
}

If you add static before the LocalFunction method:

 

 

 

The following code contains a static local function, which can be static, because it does not access the hot variables in the closed range:

 int M()
 {
     int y = 5;
     int x = 7;
     return Add(x, y);

     static int Add(int left, int right) => left + right;
 }

6. Resolvable ref structure

Structs declared with ref modifiers may not implement any excuses and therefore IDisposable. Therefore, to be able to handle ref struct, it must have an accessible void Dispose () method. This also applies to the readonly ref struct declaration.

7. Empty reference type

In an empty annotation context, any variable of a reference type is considered to be an empty reference type. To indicate that a variable may be null, you must append "? To the type name to declare the variable as an empty reference type.

In traditional code, if you write this way, an error will be reported.

static void Main(string[] args)
{
    List<string> list = null;
    var newList = list.Where(s => s.Length > 2) ?? new List<string>();
    foreach (var item in newList)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine("ok");
    Console.ReadLine();
}

 

 

 

After improvement, there is no problem:

static void Main(string[] args)
{
    List<string> list = null;
    var newList = list?.Where(s => s.Length > 2) ?? new List<string>();
    foreach (var item in newList)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine("ok");
    Console.ReadLine();
}

 

 

 

For non nullable reference types, the compiler uses flow analysis to ensure that local variables are initialized to non Null values on declaration, and fields must be initialized during construction. If variables are not set by calling any available constructors or by initializing expressions, the compiler generates warnings. In addition, you cannot assign a nullable value to a non nullable reference type.

Nullable reference types are not checked to ensure that they are not given a Null value or initialized to Null. However, the compiler uses flow analysis to ensure that any variable of nullable reference type is checked for nullness before it is accessed or assigned to a non nullable reference type.

8. Asynchronous flow

Starting with C × 8.0, you can create and use streams asynchronously. The method that returns the asynchronous flow has three properties:

1. It is declared with async modifier

2. It will return iasyncenumerable < T >

3. The method contains a yield return statement for returning continuous elements in an asynchronous flow.

Using asynchronous flows requires adding the await keyword before the foreach keyword when enumerating flow elements. The negotiation await keyword needs to enumerate the methods of the asynchronous flow to use the async modifier to declare and return the types allowed by the async method. It usually means to return Task or Task < tresult >. It can also be ValueTask or ValueTask < tresult >. Method can use or generate an asynchronous flow, which means it will return iasyncenumerable < T >. The following code generates a sequence from 0 to 10, waiting for 100 milliseconds between numbers generated:

public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence()
{
    for (int i = 0; i < 20; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

You can use the await foreach statement to enumerate sequences:

 public static async void TestGenerateSequence()
 {
     await foreach (var item in GenerateSequence())
     {
         Console.WriteLine(item);
     }
 }

9. Index and scope

Scope and index provide a concise syntax for specifying resource ranges (span < T > or readonlyspan < T >) in arrays.

This language support relies on two new types and two new operators.

1. System.Index indicates a sequence index

2. The ^ operator, which specifies that an index is related to the end of the sequence.

3. System.Range indicates the sub range of the sequence

4. The range operator (..) is used to specify the start and end of the range, just like the operand.

Let's start with index rules. Consider array sequence. 0 index is the same as sequence[0]. ^0 index is the same as sequence[sequence.Length]. Note that sequence[^0] does not throw an exception, just like sequence[sequence.Length]. For any number n, index ^ n is the same as sequence.Length-n.

Scope specifies the start and end of the scope. Include the beginning of the range, but not the end of the range, which means that the range contains the beginning but not the end. The range [0.. ^ 0] represents the entire range, just as [0..sequence.Length] represents the entire range.

For an example, consider the following array, annotated with its ordinal and reciprocal indexes:

 var words = new string[]
 {
                 // index from start    index from end
     "The",      // 0                   ^9
     "quick",    // 1                   ^8
     "brown",    // 2                   ^7
     "fox",      // 3                   ^6
     "jumped",   // 4                   ^5
     "over",     // 5                   ^4
     "the",      // 6                   ^3
     "lazy",     // 7                   ^2
     "dog"       // 8                   ^1
 };              // 9 (or words.Length) ^0

You can use the ^ 1 index to retrieve the last word:

 Console.WriteLine($"The last word is {words[^1]}");

 

The following code creates a child scope that contains the words "quick," "brown," and "fox." It includes words[1] to words[3], and the elements words[4] are not in this range.

var quickBrownFox = words[1..4];
foreach (var item in quickBrownFox)
{
    Console.WriteLine(item);
}

 

The following code uses "lazy" and "dog" to create a sub scope, which includes words[^2] and words[^1]. Exclude end index words[^0]:

var lazyDog = words[^2..^0];

The following example creates an open scope for start and / or end:

var allWords = words[..];      // contains "The" through "dog".
var firstPhrase = words[..4];  // contains "The" through "fox"
var lastPhrase = words[6..];   // contains "the", "lazy" and "dog"

You can also declare a scope as a variable:

Range phrase = 1..4;

You can then use the range in the [and] characters:

var text = words[phrase];

 

Refer to a blog by Zhang CHUANNING:

Source: http://www.cnblogs.com/SavionZhang

Author: Zhang CHUANNING, Microsoft MCP, Innovation Engineer of science and Technology Department

Keywords: C# Attribute Android Swift Programming

Added by Stryks on Mon, 21 Oct 2019 08:44:19 +0300