If you’re using the latest version of Visual Studio 2019, C#, you may not see the option to change the C# language version of your project. This is a new change in Visual Studio 2019/.NET Core 3.0. The new C# compiler chooses the default version based on your .NET target framework selected for your project in Visual Studio.
The compiler determines a default language based on the following rules:
Target framework version C# language version default
.NET Core             3.x   C# 8.0
.NET Core             2.x   C# 7.3
.NET Framework   all   C# 7.3
Note: If you’re using a previous version of Visual Studio, you’ve an option to select a language version up to C# 7.3.
C# 8.0 is the latest version of C# language. C# 8.0 introduces several new features.

Readonly members

You can apply the readonly modifier to members of a struct. It indicates that the member doesn’t modify state. It’s more granular than applying the readonly modifier to a struct declaration.

Default interface methods

You can now add members to interfaces and provide an implementation for those members. This language feature enables API authors to add methods to an interface in later versions without breaking source or binary compatibility with existing implementations of that interface. Existing implementations inherit the default implementation. This feature also enables C# to interoperate with APIs that target Android or Swift, which support similar features. Default interface methods also enable scenarios similar to a “traits” language feature.

More patterns in more places

Pattern matching gives tools to provide shape-dependent functionality across related but different kinds of data. C# 7.0 introduced syntax for type patterns and constant patterns by using the is expression and the switch statement. These features represented the first tentative steps toward supporting programming paradigms where data and functionality live apart. As the industry moves toward more microservices and other cloud-based architectures, other language tools are needed.

C# 8.0 expands this vocabulary so you can use more pattern expressions in more places in your code. Consider these features when your data and functionality are separate. Consider pattern matching when your algorithms depend on a fact other than the runtime type of an object. These techniques provide another way to express designs.

In addition to new patterns in new places, C# 8.0 adds recursive patterns. The result of any pattern expression is an expression. A recursive pattern is simply a pattern expression applied to the output of another pattern expression.

Using declarations

using declaration is a variable declaration preceded by the using keyword. It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope.

Static local functions

You can now add the static modifier to local functions to ensure that local function doesn’t capture (reference) any variables from the enclosing scope. Doing so generates CS8421, “A static local function can’t contain a reference to <variable>.”

Disposable ref structs

struct declared with the ref modifier may not implement any interfaces and so can’t implement IDisposable. Therefore, to enable a ref struct to be disposed, it must have an accessible void Dispose() method. This feature also applies to readonly ref struct declarations.

Nullable reference types

Inside a nullable annotation context, any variable of a reference type is considered to be a nonnullable reference type. If you want to indicate that a variable may be null, you must append the type name with the ? to declare the variable as a nullable reference type.

For nonnullable reference types, the compiler uses flow analysis to ensure that local variables are initialized to a non-null value when declared. Fields must be initialized during construction. The compiler generates a warning if the variable isn’t set by a call to any of the available constructors or by an initializer. Furthermore, nonnullable reference types can’t be assigned a value that could be null.

Nullable reference types aren’t checked to ensure they aren’t assigned or initialized to null. However, the compiler uses flow analysis to ensure that any variable of a nullable reference type is checked against null before it’s accessed or assigned to a nonnullable reference type.

Asynchronous streams

Starting with C# 8.0, you can create and consume streams asynchronously. A method that returns an asynchronous stream has three properties:

  1. It’s declared with the async modifier.
  2. It returns an IAsyncEnumerable<T>.
  3. The method contains yield return statements to return successive elements in the asynchronous stream.

Consuming an asynchronous stream requires you to add the await keyword before the foreach keyword when you enumerate the elements of the stream. Adding the await keyword requires the method that enumerates the asynchronous stream to be declared with the async modifier and to return a type allowed for an async method. Typically that means returning a Task or Task<TResult>. It can also be a ValueTask or ValueTask<TResult>. A method can both consume and produce an asynchronous stream, which means it would return an IAsyncEnumerable<T>.

Indices and ranges

Indices and ranges provide a succinct syntax for accessing single elements or ranges in a sequence.

This language support relies on two new types, and two new operators:

  • System.Index represents an index into a sequence.
  • The index from end operator ^, which specifies that an index is relative to the end of the sequence.
  • System.Range represents a sub range of a sequence.
  • The range operator .., which specifies the start and end of a range as its operands.

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

A range specifies the start and end of a range. The start of the range is inclusive, but the end of the range is exclusive, meaning the start is included in the range but the end isn’t included in the range. The range [0..^0] represents the entire range, just as [0..sequence.Length] represents the entire range.

Null-coalescing assignment

C# 8.0 introduces the null-coalescing assignment operator ??=. You can use the ??= operator to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to null.

Unmanaged constructed types

In C# 7.3 and earlier, a constructed type (a type that includes at least one type argument) can’t be an unmanaged type. Starting with C# 8.0, a constructed value type is unmanaged if it contains fields of unmanaged types only.

Stackalloc in nested expressions

Starting with C# 8.0, if the result of a stackalloc expression is of the System.Span<T> or System.ReadOnlySpan<T> type, you can use the stackalloc expression in other expressions.

Enhancement of interpolated verbatim strings

Order of the $ and @ tokens in interpolated verbatim strings can be any: both [email protected]"..." and @$"..." are valid interpolated verbatim strings. In earlier C# versions, the $ token must appear before the @ token.

Can I change the default C# version?
Yes, you may.
If you open your .csproj project file in Notepad or any text editor, you can add a new property group and set the LangVersion value to specify the language version your project will use. The default proj file looks like the following in XML.
<Project Sdk="Microsoft.NET.Sdk">  
<Project Sdk="Microsoft.NET.Sdk">  
The LangVersion value can be one of the following:
  • preview
  • latest
  • latestMajor
  • 8.0
  • 7.3
  • 7.2
  • 7.1
  • 7, 6, 5, 4, and 3
error: Content is protected !!