laitimes

Microsoft officially released C#10

IT House reported on February 12 that Microsoft China MSDN announced that C# 10 has been released as part of .NET 6 and Visual Studio 2022. In this article, Microsoft will introduce many of the new features of C# 10 that make your code more beautiful, expressive, and faster.

Microsoft officially released C#10

Read visual Studio 2022 bulletins and . .NET 6 bulletin for more information, including how to install.

Visual Studio 2022 bulletin

https://aka.ms/vs2022gablog

.NET 6

https://aka.ms/dotnet6-GA

Global and implicit usings

The using directive simplifies how you use namespaces. C# 10 includes a new global using directive and implicit usings to reduce the number of usings you need to specify at the top of each file.

Global using directive

If the keyword global appears before the using directive, using applies to the entire project:

global using System;

You can use any of the functions of using in the global using directive. For example, add a static import type and make members and nested types of that type available throughout your project. If you use an alias in the using directive, the alias also affects your entire project:

global using static System.Console;global using Env = System.Environment;

You can put global usage in any .cs file, including Program.cs or specially named files, such as globalusings.cs. The scope of the global usings is the current compilation, which generally corresponds to the current project.

For more information, see Global using directives.

https://docs.microsoft.com/dotnet/csharp/languagereference/keywords/using-directive#global-modifier

Implicit usings

The implicit usings feature automatically adds a common global using directive to the type of project you're building. To enable implicit usings, set the ImplicitUsings property in the .csproj file:

enable

Implicit usings is enabled in the new .NET 6 template. Read more about the .NET 6 template changes in this blog post.

Some specific global using instruction sets depend on the type of application you are building. For example, the implicit usings of a console application or class library are different from the implicit usings of ASP.NET applications.

For more information, see this implicit usings article.

Blog posts

https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-7/#net-sdk-c-project-templates-modernized

https://docs.microsoft.com/en-us/dotnet/core/project-sdk/overview#implicit-using-directives

Combining using features

The traditional using directive, the global using directive, and the implicit using at the top of the file work well together. Implicit using allows you to include .NET namespaces in your project files that are appropriate for the type of project you are building. The global using directive allows you to include additional namespaces to make them available throughout the project. The using directive at the top of the code file allows you to include namespaces used by only a few files in your project.

Regardless of how they are defined, additional using directives increase the likelihood of ambiguity in name resolution. If this is the case, consider adding aliases or reducing the number of namespaces to import. For example, you can replace the global using directive with an explicit using directive at the top of a subset of files.

If you need to remove namespaces that are included through implicit usings, you can specify them in the project file:

You can also add namespaces as if they were global using directives, and you can add using items to the project file, for example:

The namespace of the file scope

Many files contain code for a single namespace. Starting in C# 10, you can include a namespace as a statement, followed by a semicolon and without curly braces:

namespace MyCompany.MyNamespace;class MyClass // Note: no indentation{ ... }

He simplified the code and removed the nesting levels. Only one file-wide namespace declaration is allowed, and it must appear before any type is declared.

For more information about file-wide namespaces, see the Namespace Keywords article.

Namespace keyword articles https://docs.microsoft.com/ dotnet/csharp/languagereference/keywords/namespace

Improvements to lambda expressions and method groups

Microsoft has made several improvements to the syntax and types of lambdas. Microsoft expects these to be widely useful, and one of the driver scenarios is to make ASP.NET minimal APIs simpler.

The syntax of lambda

https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-10#lambda-expression-improvements

ASP.NET Minimum API

https://devblogs.microsoft.com/dotnet/announcing-asp-net-core-in-net-6/

The natural type of lambda

Lambda expressions now sometimes have a "natural" type. This means that the compiler can often infer the type of the lambda expression.

Until now, you must convert lambda expressions to delegate or expression types. In most cases, you will use one of the overloaded Func or Action delegate types in the BCL:

Funcparse = (string s) => int. Parse(s);

However, starting with C# 10, if lambda doesn't have such a "target type," Microsoft will try to calculate one for you:

var parse = (string s) => int. Parse(s);

You can hover over the var parse in your favorite editor and see if the type is still Func. In general, the compiler will use the available Func or Action delegates if a suitable delegate exists. Otherwise, it will synthesize a delegate type (for example, when you have ref parameters or have a large number of parameters).

Not all lambda expressions have natural types —some just don't have enough type information. For example, discarding a parameter type makes it impossible for the compiler to decide which delegate type to use:

var parse = s => int. Parse(s); // ERROR: Not enough type info in the lambda

The natural type of lambda means that they can be assigned to weaker types, such as object or Delegate:

object parse = (string s) => int. Parse(s); // FuncDelegate parse = (string s) => int. Parse(s); // Func

When it comes to expression trees, Microsoft combines "target" and "natural" types. If the target type is LambdaExpression or a non-generic Expression (the base type for all expression trees) and lambda has a natural delegate type D, Microsoft will generate expressions instead:

LambdaExpression parseExpr = (string s) => int. Parse(s); // Expression>Expression parseExpr = (string s) => int. Parse(s); // Expression>

The natural type of the method group

Method groups (that is, method names without parameter lists) now sometimes have natural types as well. You will always be able to convert a method group to a compatible delegate type:

Funcread = Console.Read; Actionwrite = Console.Write;

Now, if the method group has only one overload, it will have a natural type:

var read = Console.Read; // Just one overload; Funcinferredvar write = Console.Write; // ERROR: Multiple overloads, can't choose

The return type of lambda

In the preceding example, the return type of the lambda expression is obvious and inferred. This is not always the case:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type

In C# 10, you can specify an explicit return type on a lambda expression, just like on a method or local function. The return type precedes the parameter. When you specify an explicit return type, the arguments must be enclosed in parentheses so that compilers or other developers are not too confused:

var choose = object (bool b) => b ? 1 : "two"; // Func

Attributes on lambda

Starting with C# 10, you can put properties on lambda expressions just like you would for methods and local functions. When there are attributes, the parameter list of lambdas must be enclosed in parentheses:

Funcparse = [Example(1)] (s) => int. Parse(s);var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";

Just like local functions, if a property is valid on AttributeTargets.Method, you can apply the property to lambda.

Lambdas are invoked differently than methods and local functions, so attributes have no effect when lambda is invoked. However, properties on lambdas are still useful for code analysis, and they can be discovered through reflection.

Improvements to structs

C# 10 introduces functionality for structs that provide better parity between structs and classes. These new features include parameterless constructors, field initializers, record structures, and with expressions.

01 Parameterless structure constructor and field initializer

Prior to C# 10, every struct had an implicit public parameterless constructor that set the struct's fields to default values. It is a mistake to create a parameterless constructor on a structure.

Starting with C# 10, you can include your own parameterless structure constructors. If you don't, an implicit parameterless constructor is provided to set all fields to default values. The parameterless constructor you create in the struct must be public and cannot be partial:

public struct Address{ public Address() { City = ""; } public string City { get; init; }}

You can initialize fields in the parameterless constructor as described above, or you can initialize them through a field or property initializer:

public struct Address{ public string City { get; init; } = "";}

Structures created by default or as part of an array allocation ignore the explicit parameterless constructor and always set the structure members to their default values. For more information about parameterless constructors in structs, see Struct Types.

02 Record structs

Starting with C# 10, you can now define a record using record struct. These are similar to the record classes introduced in C# 9:

public record struct Person{ public string FirstName { get; init; } public string LastName { get; init; }}

You can continue to define the record class with record, or you can use the record class to make it clear.

Structures already have equal values - when you compare them, it's by value. The record structure adds IEquatable support and the == operator. The recording structure provides a custom implementation of IEquatable to avoid reflected performance issues, and they include logging features such as ToString() overrides.

The record structure can be positional, and the main constructor implicitly declares a public member:

public record struct Person(string FirstName, string LastName);

The parameters of the main constructor become public auto-implementation properties of the record structure. Unlike the record class, implicitly created properties are read/written. This makes it much easier to convert tuples to named types. Changing the return type from a tuple like (string FirstName, string LastName) to a named type of Person cleans up your code and ensures that the member names are consistent. Declaring a location record structure is easy and maintains mutable semantics.

If you declare a property or field with the same name as the main constructor parameter, no automatic properties will be synthesized and yours will be used.

To create an immutable record structure, add readonly to the structure (just as you can add to any structure) or apply readonly to a single property. The object initializer is part of the construction phase where you can set read-only properties. This is just one way to use an immutable record structure:

var person = new Person { FirstName = "Mads", LastName = "Torgersen"};public readonly record struct Person{ public string FirstName { get; init; } public string LastName { get; init; }}

Learn more about the record structure in this article.

Record structure

https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record

03 Seal modifier on ToString() in the Record class

The record class has also been improved. Starting with C# 10, ToString() methods can contain the seal modifier, which prevents the compiler from synthesizing ToString implementations for any derived record.

Learn more about ToString() in the records in this article.

More information about ToString ().

https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record#built-in-formatting-for-display

04 Expressions of structures and anonymous types

C# 10 supports with expressions for all structures, including record structures, as well as anonymous types:

var person2 = person with { LastName = "Kristensen" };

This returns a new instance with the new value. You can update any number of values. Values that you did not set will retain the same values as the initial instance.

Learn more about with in this article

Learn more about with

Interpolated string improvements

When Microsoft added interpolated strings to C#, Microsoft always felt that using the syntax could do more in terms of performance and expressiveness.

01 Interpolate string handlers

Today, the compiler converts an interpolated string to a string. The call to Format. This results in a lot of allocations — boxing of parameters, allocation of arrays of parameters, and of course the result string itself. In addition, it does not have any room for manoeuvre in the meaning of the actual interpolation.

In C# 10, Microsoft added a library pattern that allows the API to "take over" the processing of interpolated string parameter expressions. For example, consider StringBuilder.Append:

var sb = new StringBuilder();sb. Append($"Hello {args[0]}, how are you?");

So far, this will use the newly allocated and computed string to call the Append (string?value) overload, appending it to a block in the StringBuilder. However, Append now has a new overload Append (refStringBuilder.AppendInterpolatedStringHandler handler), which takes precedence over string overloading when interpolated strings are used as arguments.

Usually, when you look at parameter types in the form of SomethingInterpolatedStringHandler, the API authors do some work behind the scenes to more appropriately handle interpolated strings for their purposes. In Microsoft's Append example, the strings "Hello", args [0], and ", how are you?" "Attaching to StringBuilder separately is more efficient and the results are the same.

Sometimes you just want to do the work of building strings under certain conditions. An example is Debug.Assert:

Debug.Assert(condition, $"{SomethingExpensiveHensHere()}");

In most cases, the condition is true and the second parameter is not used. However, each call evaluates all parameters, unnecessarily slowing down execution. Debug.Assert now has an overload with a custom interpolated string builder that ensures that the second argument is not even evaluated unless the condition is false.

Finally, here's an example of actually changing the string interpolation behavior in a given call: String.Create() allows you to specify an expression in the hole that IFormatProvider uses to format the interpolated string parameter itself:

String.Create(CultureInfo.InvariantCulture, $"The result is {result}");

You can learn more about interpolated string handlers in this article and in this tutorial on creating custom handlers.

Create a custom handler

https://docs.microsoft.com/dotnet/csharp/languagereference/tokens/interpolated#compilation-of-interpolated-strings

More information about interpolated string handlers

https://docs.microsoft.com/dotnet/csharp/whats-new/tutorials/interpolated-string-handler

02 Constant interpolated string

If all holes in the interpolated string are constant strings, the resulting string is now also constant. This allows you to use string interpolation syntax in more places, such as properties:

[Obsolete($"Call {nameof(Discard)} instead")]

Note that the hole must be filled with a constant string. Other types, such as numeric or date values, cannot be used because they are culturally sensitive and cannot be calculated at compile time.

Other improvements

C# 10 makes many minor improvements to the language as a whole. Some of them just make C# work the way you expect it to.

Mix declarations and variables in deconstruction

Prior to C# 10, deconstruction required that all variables be new, or that all variables had to be declared in advance. In C# 10, you can mix:

int x2;int y2;(x2, y2) = (0, 1); // Works in C# 9(var x, var y) = (0, 1); // Works in C# 9(x2, var y3) = (0, 1); // Works in C# 10 onwards

Learn more in the article on deconstruction.

Clear distribution of improvements

If you use a value that has not been explicitly assigned, C# produces an error. C# 10 can better understand your code and produce fewer spurious errors. These same improvements also mean that you'll see fewer fake errors and warnings for null references.

Learn more about C# deterministic assignments in the What's new in C# 10 article.

What's new in C# 10 article

https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-10#improved-definite-assignment

Extended property patterns

C# 10 added extended property patterns to make it easier to access nested property values in patterns. For example, if Microsoft adds an address to the Person record above, Microsoft can do pattern matching in two ways:

object obj = new Person{ FirstName = "Kathleen", LastName = "Dollard", Address = new Address { City = "Seattle" }};if (obj is Person { Address: { City: "Seattle" } }) Console.WriteLine("Seattle");if (obj is Person { Address.City: "Seattle" }) // Extended property pattern Console.WriteLine("Seattle");

Extended property patterns simplify code and make it easier to read, especially when matching multiple properties.

Learn more about extended property patterns in the pattern matching article.

Pattern matching articles

https://docs.microsoft.com/dotnet/csharp/languagereference/operators/patterns#property-pattern

Caller expression property

CallerArgumentExpressionAttribute provides information about the context of the method call. Like other CompilerServices properties, this property applies to optional parameters. In this case, a string:

void CheckExpression(bool condition, [CallerArgumentExpression("condition")] string? message = null ){ Console.WriteLine($"Condition: {message}");}

The parameter names passed to callerArgumentExpression are the names of different parameters. The expression passed as a parameter to the parameter is included in the string. For example

var a = 6;var b = true; CheckExpression(true); CheckExpression(b); CheckExpression(a > 5);// Output:// Condition: true// Condition: b// Condition: a > 5

ArgumentNullException.ThrowIfNull() is a good example of how this property is used. It avoids having to pass in parameter names by providing values by default:

void MyMethod(object value){ ArgumentNullException.ThrowIfNull(value);}

Learn more about CallerArgumentExpressionAttribute

https://docs.microsoft.com/dotnet/csharp/languagereference/attributes/caller-information#argument-expressions

Read on