Announcing .NET 6 Release Candidate 2

Richard

We are excited to release .NET 6 Release Candidate 2. It is the second of two “go live” release candidate releases that are supported in production. For the last couple months, the team has been focused exclusively on quality improvements. There are a lot of new features in the release, which only fully come together near the end. The team is currently validating end-to-end workflows to find the places where design intentions and technical reality don’t yet fully match. That’s led to teams tightening leaky pipes and paving paths all the way to their destination.

We’ve heard from users that upgrading production sites to .NET 6 RC1 has been both “boring” (non-event) and “exciting” (significant performance improvements). We’re confident that RC2 will continue that trend of having no surprises and exciting results.

You can download .NET 6 Release Candidate 2 for Linux, macOS, and Windows.

See the .NET MAUI and ASP.NET Core posts for more detail on what’s new for client and web application scenarios.

.NET Conf is a free, three-day, virtual developer event that celebrates the major releases of .NET. This year, .NET 6 will launch at .NET Conf November 9-11. We encourage you to save the date and tune in. Visual Studio 2022 Preview 5 is also releasing today and they have also announced that launch event happening on November 8. You can read all about that on the Visual Studio blog.

We’re at that fun part of the cycle where we support the new release in production. We genuinely encourage it. You can reach out to us at dotnet@microsoft.com if you need guidance on how to approach that. A bunch of businesses have reached out and some are now in production. We also help Microsoft teams run on RC releases. Some Microsoft teams have gone into production on RC1 and we expect many more on RC2. This includes the .NET Website.

.NET 6 RC2 has been tested and is supported with Visual Studio 2022 Preview 5, also releasing today. .NET 6 will be supported with Visual Studio 2022 and not Visual Studio 2019. Similarly, it will be supported with MSBuild 17.x and not 16.x. If you want to use .NET 6, you will need to upgrade to Visual Studio 2022.

Visual Studio 2022 for Mac is currently NOT compatible with .NET 6 RC2. We are in the process of resolving that.

Check out the new conversations posts for in-depth engineer-to-engineer discussions on the latest .NET features.

The .NET 6 RC1 post was focused on foundational features, many of which will not see their full fruition until .NET 7. This post is focused on C# 10 and the related improvements to templates. It also includes an update on macOS and Windows Arm64 (which includes breaking changes). Let’s take a look.

C# 10

C# 10 is an important part of the .NET 6 release. For the most part, C# 10 is a further evolution of existing concepts and capabilities, like records and patterns. It also includes features — global using and file-scoped namespaces — that help you simplify your code and write less boilerplate. Those specific improvements are the basis of the template changes that are discussed later in this post. You can see the examples used in this section at .NET 6 examples. I’ll add more examples there for the final .NET 6 post.

Record structs

C# 10 adds support for record structs. This new feature is similar to C# 9 (class-based) records, with some key differences. For the most part, record structs have been added for completeness so that structs can enjoy the same record benefits as classes. However, the team did not simply structify records, but decided to align struct records with ValueTuple as much as class records. As a result of that design approach, record struct properties are mutable by default, while record class properties are immutable. However, you can declare a readonly record struct, which is immutable and matches the record class semantics.

At a high level, record structs do not replace record classes, nor do we encourage migration of record classes to record structs. The guidance for using classes vs structs applies equally to record classes and record structs. Put another way, the choice between classes and structs should be made before choosing to use records.

Let’s take a look at how record structs differ from record classes

This example of a record struct produces the following output.

Battery { Model = CR2032, TotalCapacityAmpHours = 0.235, RemainingCapacityPercentage = 100 }
Battery { Model = CR2032, TotalCapacityAmpHours = 0.235, RemainingCapacityPercentage = 0 }

As already stated, the most obvious difference is that record struct properties are mutable by default. That’s the key difference beyond being structs and the record struct syntax. I’ve also re-written the example with a readonly record struct, as follows.

You’ll see that it is almost identical to the (class) record example I published for C# 9.

Let’s review C# 9 records. They provide a terse syntax for defining struct-like data-oriented classes. They bias to immutability while offering a terse syntax — with expressions — for immutable-friendly copying. It might have surprised folks that we started with classes for a feature that is intended to be struct-like. Most of the time, developers use classes over structs, due to pass by reference as opposed to value semantics. Classes are the best choice in most cases and easier to use.

Struct records are very similar to class records:

  • They use the same syntax (with the exception of struct or class in the definition).
  • They enable customizing member definitions (new in C# 10) to use fields over (by default) property members.
  • They enable customizing the member behavior, using init or mutable properties.
  • They support with expressions. In fact, starting in C# 10, all struct types support with expressions.

Struct records differ from class records:

    • Record structs are defined with record struct or readonly record struct.
    • Record classes are defined with record or record class.
    • Record struct properties are mutable (get/set) by default.
    • Record class properties are immutable (get/init) by default.

The asymmetric (im)mutability behavior between record structs and record classes will likely be met with surprise and even distaste with some readers. I’ll try to explain the thinking behind the design. By virtue of pass by value semantics, structs don’t benefit from immutability nearly as much as classes. For example, if a struct is a key in a dictionary, that (copy of the struct) key is immutable and lookups will be performed via equality. At the same time, the design team saw that ValueTuple can be thought of as an anonymous record struct and that record structs can be seen as a grow-up from ValueTuple. That only works if record structs embrace mutability and support fields, which is exactly what the team decided to do. Also, embracing mutability is a reflection that structs and classes are different.

If you prefer the immutable behavior with record structs, you can have it by adding the readonly keyword.

Looking at structs generally, key functionality is common:

  • Equality checks are the same for all structs, provided by the runtime.
  • All structs can use with expressions for creating non-mutating copies, which is new in C# 10.

Global usings

global using enables you to specify a namespace that you would like available in all of your source files, as if it was declared in each one. It can also be used with using static and aliasing. This feature enables a common set of using declarations to be available, and by extension, for many using lines to no longer be needed. This is most relevant for the platform namespaces, but can be used for any namespace.

The following syntax can be used for the various using forms:

  • global using System;
  • global using static System.Console;
  • global using E = System.Environment;

These declarations just need to be declared once in your compilation to be effective throughout it. There are four patterns for declaring global using.

  • In Program.cs, make your root using statements global to the whole program by upgrading them to global using.
  • In a GlobalUsings.cs file (or some similar name), centrally manage all of your global using statements.
  • In your project file, with syntax that follows.
  • In your project file, enable the default platform using statements (for the MSBuild SDK your app relies on), with syntax that follows.

The following MSBuild syntax can be used in place of .cs files, in an <ItemGroup> (using analogs of the earlier examples).

  • <Using Include="System"/>
  • <Using Include="System.Console" Static="True"/>
  • <Using Include="System.Environment" Alias="E"/>

You can enable implicit using statements defined by the platform in a <PropertyGroup>.

  • <ImplicitUsings>enable</ImplicitUsings>

Implicit using differs by MSBuild SDK. Microsoft.NET.Sdk defines a basic set, and other SDKs define additional ones.

If you use the MSBuild capabilities, you can see the effective result in a generated file in the obj directory, as demonstrated by the following.

PS C:\Users\rich\app> type .\app.csproj | findstr Using
    <ImplicitUsings>enable</ImplicitUsings>
    <Using Include="System.Console" Static="True"/>
    <Using Include="System.Environment" Alias="E"/>
PS C:\Users\rich\app> type .\obj\Debug\net6.0\app.GlobalUsings.g.cs
// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
global using E = global::System.Environment;
global using static global::System.Console;

You can see that the Microsoft.NET.Sdk adds several global using statements, in the .GlobalUsings.g.cs file. Note that the global:: operator isn’t necessary and can be safely ignored in terms of developing an understanding of the generated syntax.

File-scoped namespace declaration

File-scoped namespace declarations is another C# 10 feature that is aimed at reducing indentation and line count.

The syntax for this feature follows:

namespace Foo;

It is an alternative to the traditional three-line syntax:

namespace Foo
{
}

The three-line syntax can be nested. The one-line syntax does not support nesting. There can only be one file-scoped declaration per file. It must precede all types defined in the file, much like the three-line syntax.

Namespaces are not compatible with top-level statements. Top-level statements exist within the top-level namespace. This also means that if you add additional methods to the Program class, using the partial class syntax that the partial Program class needs to also be in the top-level namespace.

This feature is very similar to the one-line using declaration that was added to C# 8.

const and interpolated strings

Interpolated strings can now be assigned to const variables. Interpolated strings are intuitive to use and read, and should be usable everywhere. They can now be used with const provided that the placeholder values are also constant.

The following example demonstrates a possible use case:

Many other improvements have been made to interpolated strings, which are described in String Interpolation in C# 10 and .NET 6.

Extended property patterns

You can now reference nested properties or fields within a property pattern. For example, the following pattern is now legal:

{ Prop1.Prop2: pattern }

Previously, you’d need to use a more verbose form:

{ Prop1: { Prop2: pattern } }

You can see this more compact form used in the following example, for example with Reading.PM25.

.NET SDK: C# project templates modernized

We modernized .NET SDK templates with Preview 7, using the latest C# features and patterns. There was significant feedback on those changes, in part because builds started to fail. As part of the initial templates update, we enabled implicit usings by default (AKA opt-out) for .NET 6 (net6.0) projects (including if you updated an app from .NET 5 to .NET 6). That has been changed. We’ve updated the SDK so that all the new features are opt-in. The response to that change (which was made in RC1) has been positive.

There was also feedback that some folks didn’t like the new simplified Program.cs file, with top level statements. We’ve made improvements to top-level statements in response and continued to use it for templates. We expect that most developers who prefer the traditional approach can straightforwardly add the extra ceremony themselves.

The following language features are used in the new templates:

  • async Main
  • Top-level statements
  • Target-typed new expressions
  • Global using directives
  • File-scoped namespaces
  • Nullable reference types

We built all of these features because we think that they are better than the previous alternative. Templates are the easiest and best way to steer new developers and new apps to use the best patterns. The C# design team is a big believer in enabling the use of fewer lines, fewer characters to specify a given concept or operation, and the reducing needless repetition. That’s what most of these new features enable. Nullable differs in that it results in more reliable code. Every app or library that uses nullable is less likely to crash in production. Software is too complicated for human minds to see the mistakes that a compiler can.

A common theme across these features is that they reduce noise and increase signal when you are looking at your code in a code editor. More important aspects should now pop. For example, if you see a using statement in a given file, it’s a special one needed beyond the implicit set. You should be able to copy/paste code from one file to another without needing to CTRL-. types to add the required namespaces (at least not as much). If you see nullable warnings or errors, you know that your code is likely incorrect in some way. There is also the benefit of removing indentation. More of your code will be visible within the first dozen columns of your editor, as opposed to those columns being filled with just space characters. One can think of these features as delivering higher density of specificity and meaning to your eyes, on the way to your brain.

Console template

Let’s start with the console template. It’s very small.

Program.cs:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Even though the console template is much more minimal that its .NET 5 counterpart, there is no reduction in functionality. You can also see that ImplicitUsings is now an opt-in feature, and is enabled in the templates.

Web templates

The web template is similarly minimal.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

The webapi template maps more closely to typical ASP.NET Core apps. You will quickly notice that the split between Program.cs and Startup.cs has been removed. Startup.cs is no longer required.

Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

The project file for the webapi template is similar to the console one.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
  </ItemGroup>

</Project>

You can see that file-scope namespaces are used throughput this sample, including in WeatherForecast.cs:

namespace webapi;

public class WeatherForecast
{
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string? Summary { get; set; }
}

Target-type new is used with WeatherForecastController.cs:

private static readonly string[] Summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

In the mvc template, you can see the use of nullable annotations and an expression-bodied method.

namespace webmvc.Models;

public class ErrorViewModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}

Windows Forms templates

The Windows Forms template has also been updated. It includes most of the other improvements covered with the other templates, although notably not top-level statements.

namespace winforms;

static class Program
{
    /// <summary>
    ///  The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.Run(new Form1());
    }    
}

You can see the use of the one-line namespace statement and the lack of any platform-level using statements due to enabling implicit usings.

WPF templates have not been updated as part of the release.

Implicit usings

I’ll now show you these features in action. Let’s start with implicit usings. When enabled, each Sdk adds its own set of implicit using statements.

As demonstrated earlier, the Microsoft.NET.Sdk adds several global using statements.

If this feature is disabled, you’ll see that the app no longer compiles since the System namespace (in this example) is no longer declared.

PS C:Usersrichapp> type .app.csproj | findstr Implicit
    <ImplicitUsings>disable</ImplicitUsings>
PS C:Usersrichapp> dotnet build
Microsoft (R) Build Engine version 17.0.0-preview-21501-01+bbcce1dff for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  You are using a preview version of .NET. See: https://aka.ms/dotnet-core-preview
C:UsersrichappProgram.cs(2,1): error CS0103: The name 'Console' does not exist in the current context [C:Usersrichappapp.csproj]

Build FAILED.

Another option is to use the global using feature, which enables you to only use the namespaces you want, as you can see in the following example.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <Using Include="System" />
  </ItemGroup>

</Project>

Now, building the code.

PS C:Usersrichapp> type .app.csproj | findstr Using
    <ImplicitUsings>disable</ImplicitUsings>
  <Using Include="System" />
PS C:Usersrichapp> dotnet build
Microsoft (R) Build Engine version 17.0.0-preview-21501-01+bbcce1dff for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  You are using a preview version of .NET. See: https://aka.ms/dotnet-core-preview
  app -> C:UsersrichappbinDebugnet6.0app.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.80

That’s not the recommended general pattern, but is a good option for folks that want maximum control. In most cases, we expect that developers will rely on the implicit usings provided by the SDK and take advantage of explicit global usings for namespaces from their own code or NuGet packages that they use pervasively.

Nullable

I’ve updated Program.cs to demonstrate nullable reference types. The app calls a List<T> method that returns a T?, in this case a nullable string (string?).

List<string> greetings = new()
{
    "Nice day, eh?"
};

string? hello = greetings.Find(x => x.EndsWith("!"));
string greeting = hello ?? "Hello world!";

Console.WriteLine($"There are {greeting.Length} characters in "{greeting}"");

The line that defines the hello variable won’t compile without being defined as var, string? or addressing the case where List<T>.Find returns null. Without the nullable feature enabled, I might miss this problem, which would result in my code crashing with a NullReferenceException exception. That’s no good. I’m handling that on the next line with ??, the null coalescing operator. In most cases, these two lines would be merged into one, as you can see in the following code. I’ve kept them separate (in this contrived example) so that you can see my use of string? to accommodate an API that returns a nullable reference type.

string greeting = greetings.Find(x => x.EndsWith("!")) ?? "Hello world!";

In this example, I’ve merged everything into one line. I can now declare the variable as string since the null-ness has been accommodated with the string that follows ??. The string? in this case is something that only the compiler sees.

string[] args

I’m now going to quickly address the questions that most people ask about top-level statements, including the args parameter. The args parameter remains available with top-level statements. It’s just less obvious. Here’s another program that demonstrates using it.

string greeting = args.Length > 0 ? string.Join(" ", args) : "Hello World!";
WriteLine($"There are {greeting.Length} characters in "{greeting}" in this {nameof(Program)}.");

It produces the following output.

PS C:\Users\rich\app> dotnet run
There are 12 characters in "Hello World!" in this Program.
PS C:\Users\rich\app> dotnet run Nice day, eh?
There are 13 characters in "Nice day, eh?" in this Program.

This app also demonstrates that the Program type is still defined. Program.Main is not.

I removed the Console from Console.WriteLine in Program.cs by adding a global static using to my project file, as follows.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <Using Include="System.Console" Static="True"/>
  </ItemGroup>

</Project>

Defining regular methods

We’ve heard the feedback from some folks that real methods are preferred over local functions, specifically for the Program class. You can use either model with top-level statements.

I’ve kept the program the same, but switched all the functionality to static method in the Program class, defined in a partial class.

Program.cs.

WriteLine(GetTheGreeting(args));

Program.Foo.cs.

public partial class Program
{
    public static string GetTheGreeting(string[] args)
    {
        string greeting = args.Length > 0 ? string.Join(" ", args) : "Hello World!";
        return $"There are {greeting.Length} characters in "{greeting}" in this {nameof(Program)}.";
    }
}

It produces the same result as the previous example. The Program.Foo.cs filename is arbitrary. In fact, so is Program.cs. You can name these files whatever you’d like. The compiler will figure it out.

As mentioned earlier, the Program type must be in the top-level namespace when using top-level statements.

macOS and Windows Arm64 Update

We have good news, but also some breaking change to share. The project to support macOS and Windows arm64 is almost done. We’ve been working on it for over a year, with help when we needed it from the fine folks on the macOS and Windows teams. At first, we thought that the project was solely about supporting Arm64 on macOS and that Rosetta 2 would cover x64. Easy and just about ISAs, right? Not quite. As we dug deeper, it became clear that x64 + Arm64 co-existence was the bigger task to take on, focused on the CLI and installers, including a few places where they need to agree. With RC2, we’re delivering builds of .NET that we deliver the best experience we could imagine.

I’ll provide a quick summary of where we are at for macOS and Windows Arm64 machines:

  • .NET 6 RC2 enables Arm64 + x64 co-existence by installing Arm64 and x64 builds to different locations. Until now, Arm64 and x64 builds overwrite each other, which led to general sadness.
  • You need to uninstall all .NET builds and start from scratch (on macOS and Windows Arm64 machines) to adopt .NET 6 RC2+. This includes Arm64 and x64, .NET 6 and pre-.NET 6.
  • Pre .NET 6 builds are not yet ready to install.
  • The CLI enables you to use the Arm64 SDK to do Arm64 AND x64 development (assuming you have the required Arm64 and x64 runtimes installed). The same is true in reverse.
  • We want people to just use the Arm64 SDK since it will be a better experience (native architecture performance; one SDK to maintain). We will continue to improve the product to make this model the easy choice for most developers.
  • For the SDK, we will only support .NET 6+ on Arm64. Earlier SDK builds will be blocked on Arm64.
  • For runtimes, we will support all in-support versions, Arm64 and x64.
  • .NET 6 RC2 delivers the bulk of the final .NET 6 experience for Arm64 (including x64 emulation).
  • We hope to update .NET Core 3.1 and .NET 5 runtimes to align with .NET 6 RTM (both in time and with the technical requirements). That’s still TBD.
  • RC2 nightly builds are currently broken, so you will need to wait a couple more weeks until we actually ship RC2 to try this all out.
  • The .NET 5 SDK for Windows Arm64 will go out of support early, with .NET 6 RTM.

We’re also considering making breaking changes to dotnet test to unify the arguments we use for architecture targeting:

A big part of the project is enabling the use of x64 runtimes with the Arm64 SDK. You can see that demonstrated in the following image.

x64 on Arm64, Announcing .NET 6 Release Candidate 2

If you want more details, check out dotnet/sdk #21686.

Contributor showcase

In developing .NET 6, we appreciate the many contributions of our GitHub community. As we did in our Preview 7 and RC1 posts, we’d like to recognize just a few of those.

This text is written in the contributors’ own words:

Alexandr Golikov (@kirsan31)

My name is Alexandr Golikov and I am from Saint Petersburg, Russia.

I have been writing stock trading software for over 15 years, mostly with .Net Framework and WinForms. As soon as .Net Core WinForms became open source and a preview version was released, I started trying to migrate our projects into it and contribute to solving both old problems that were in .Net Framework and new ones. At this moment all of our projects are working on .Net 5.0. But .Net 6 is already almost here, and .Net 7 is on the horizon, and… I hope that I will continue to contribute to this great framework, and will have more free time for this 🙂

In the end, I want to thank the whole .Net team and especially the WinForms project developers and maintainers!

Tobias Käs (@weltkante)

I’m Tobias Käs and mostly visible under the alias weltkante on github. I’m from Germany, studied computer science in Frankfurt and am currently living in Mönchengladbach. I’m coming from a low level C++ programming background but have switched to C# programming as my main language ever since it distinguished itself from Java by adding generics in .NET 2.0.

I’ve been working over 12 years programming desktop applications in .NET Framework using WinForms and later WPF. I have spent a lot of time digging into the internals of .NET and its UI frameworks to learn how they work, debugging issues, finding workarounds, and in general getting the most out of these frameworks. Ever since they went open source I’m trying to give back from what I learned, following issue discussions, commenting pull requests, or doing the occasional contribution.

Ivan Zlatanov (@zlatanov)

My name is Ivan Zlatanov, and I am from Botevgrad, Bulgaria. I dipped my toes in the world of software first in high school where we did competitive programming in Turbo Pascal. I’ve spent the next summer doing some PHP and learning about the web dev. Not long after an opportunity arose where I could learn C# and ASP.NET 2.0 and once I picked that, I never stopped. I’ve been programming with C# and .NET for almost 14 years now.

I love contributing to open-source projects because I can help others. I love the immediate feedback you get from reviews and the open discussion of problems and solutions with other people around the globe. As Scott Hanselman once said that we only have limited keystrokes left in each of us, so we better spend them well.

Contributing to Open Source and in particular .NET is keystrokes spent well.

Closing

C# 10 offers significant improvements in simplicity and expressiveness, building on similar features in C# 9. In the past, one could reasonably poke fun at C# for requiring so much ceremony and knowledge of object oriented concepts just to write a single line of code. Those days are behind us, and the templates reflect that. Part of the motivation for these changes has been to make C# more attractive to new programmers and schools. These simplifying changes fundamentally change what you need to learn to get started with and become skilled using C#. In its new default form, it is directly comparable to other languages that similarly start with a single file source file.

We look forward to seeing other projects simplify their new user experiences with top-level statements, global using, records and many of the other modern features. There is a lot of opportunity to create simpler learning journeys for both open source projects and commercial offerings.

We encourage you to use .NET 6 RC2. We’re confident that you’ll find it a solid release. And save the date for .NET Conf on November 9 for the .NET 6 launch.

Thanks for being a .NET developer.

99 comments

Leave a comment

  • Łukasz Kondracki

    The args parameter remains available with top-level statements. It’s just less obvious.

    Now this I’m not a fan of. It essentially makes args a magic keyword. I would be fine with some System.GetArgs() being introduced instead, but a magic args feels very smelly to me.

    • Richard LanderMicrosoft employee

      Understood. However, it’s the most natural design choice given the C-oriented heritage of C#. It enables code to be moved between the two models w/o change. That’s a very nice characteristic.

      The Swift model is effectively the same: https://developer.apple.com/documentation/swift/commandline.

      System is a namespace. I could imagine System.CommandLine.GetArgs() as alternative, however, it would require migration for no technical benefit.

      • Someone

        @Richard Lander: The problem is not string[] args. The problem is that person could not know about hidden Program class, Main method and so on. Guys, why did you developed and then forced people to use “features” which break clean of C# language? Why did you introduce non-clean default templates and do OPPOSITE what programmers and beginners want? Can’t you see what is good or what is not good?

        1) This is far easier to understand what is going on. Why? Because you know explicitly what is namespace, what is Program class, what is ENTRY POINT (Main method) and you also can see that Main method accepts args.

        using System;
        
        namespace Tutorial
        {
            class Program
            {
                static void Main(string[] args)
                {
                    Console.WriteLine("Hello World!");
                }
            }
        }
        
        • Richard LanderMicrosoft employee

          I can only say that the design team disagrees. We don’t think beginners need to learn these things. They don’t for a lot of the other popular languages. The model you’ve shown is the C language model. There are other models. Rust, Go, and Swift all do it differently than C#.

          I remember trying to teach my kids C# when they were young. It was way too much information to get started. I almost switched to teaching them JavaScript instead so that I could start with just a single line of code.

          • Jennings, Jay

            Thank you! Every time I have to write a line of code that has nothing specifically to do with the app I’m writing, I wonder why programming is still so much like it was back in the 80s. If 99% of the time I don’t need to tweak some kind of housekeeping thing, then I don’t even want to see it — it’s a waste of my time to have to write it. (And that 1% of the time, as long as I can get to it somewhere under the hood, then we’re cool.)

            So thanks for continuing to advance C# — it may never become my favorite language, but the fewer times I have to swear while writing code, the more my wife likes it.

          • Someone

            @Richard Lander: Alright, I understand what you wrote about simple Program.cs file. However, what should I do now? I create new project and then what about Main method? I think the problem is that I don’t know if it’s good to omit “boilerplate” (namespace, Program class, Main method) in Program.cs when other files contain namespaces, classes and so on. Don’t get me wrong but I think it looks strange when you have one file (entry point) which doesn’t have any namespace, class etc. Mr Richard, what do you think about it?

            About other languages: I like Rust or Kotlin idea that simple hello world application contains main function. C# is mainly OOP so it is/was like Java and we have/had to define class which contains Main method.

          • Timothy Boyden

            Richard,

            Are you a noob to the .NET platform?

            “I remember trying to teach my kids C# when they were young. It was way too much information to get started. I almost switched to teaching them JavaScript instead so that I could start with just a single line of code.”

            Every professional coder just rolled their eyes at that statement because they all know you are comparing apples to oranges. C# is a compiled language whereas JavaScript is a scripting language.

            You are also laughingly trying to defend a feature over a simple “Hello World” construct that anyone who has ever coded an actual application knows that “Hello World” is utterly useless as an example because you almost immediately need to start tweaking the dials and switches to configure the application for third-party libraries or custom data types/structures and need the “ceremony” to do all that configuring. That is the case with any language, even the ones you mentioned.

            There seems to be this game being played out by Silicon Valley types of who can make “Hello World” the most simple? Nobody who does coding to make money gives an F about that. We want features that make it quick to build a full-featured application, not toys.

            There is also a lot to be said here about making it easier for people to be lazy programmers. By abstracting away “ceremony” you also handicap new developers by hiding what makes the application work. While experienced programmers gloss past those basic structures, educationally they are important for new programmers to understand how a program is constructed, illustrates proper object-oriented structure, and provides the inquisitive mind an opportunity to dig deeper into the concept of why that code is needed in the first place. That leads to better coding style and etiquette, more knowledgeable programmers, and promotes root-cause analysis that is sorely lacking in newer generations of information technology workers.

            We don’t want C# to be those other languages or else we would just go use those languages. Make C# a better C#, not a Swift, not a Scala, not a Go, and certainly not JavaScript.

      • Jorge Morales Vidal

        Thank you! It’s good to know there are ways people can obtain what they need. I embrace the new patterns and styles; it makes new applications better and with less boilerplate to maintain.

  • Tanvir Ahmad Arjel

    Now the most hated features of C# are:

    1) Top-level statements
    2) Target-typed new expressions

    These two features totally go against the C# nature. These two did not add any value to this great programing language. Unfortunately, these two have been added by default in the .NET project templates too which means we have to make a lot of changes to the default template just after creating it.

    We think the .NET team now days doing so much over-engineering while there are a lot of crucial issues which need to be fixed. Frustrating! very frustrating!

    • Juan Manuel Barahona

      I love those feature, as many others do, just see the comments on the article you just mentioned, progress always hit our habits very hard but is still good and I personally like the way the .NET teams is leading this.

    • Someone

      @Tanvir Ahmad Arjel: 100% right. I can’t understand why Microsoft and people who contribute to C# and .NET create “features” which are not needed. Beginners and professionals don’t need top-level statements and these new default templates with hidden Program class.

      • Alexander Batishchev

        Beginners and professionals don’t need top-level statements and these new default templates with hidden Program class.

        Totally agree.

    • Денис Перевозчиков

      BTW the most upvoted comment under this article:
      This is such a childish piece that serves no purpose other than padding your stats with a clickbait title.
      I didn’t even find any positive comment.
      Hating new features is unwillingness to learn.

        • Wade

          Don’t drag me into this 🙁

          I like top-level statements in most cases! The actual only thing I’m not that keen on is implicit using statements for .NET 6 being hidden in the obj folder and not just being an additional file as part of the template.

          Although, I do feel for web projects in particular, templates being the top level statements is oof (I’m not sure if that’s the actual case). But the template for console apps being top level statements seems right IMO.

    • Michael Peter

      First of all want to say I am a big fan of the top level statements, finally we can shell out scripts and quick PoCs in C# like previously only in node+js, pyton or powershell. Just create a textfile and call dotnet run. As a language I prefer C# over Java since it didn’t stop evolving.

      Second one C# 10 feature which was committed in csharplang and still missing: What about the parameter null checks? MyClass param! which throws argument null exceptions. Would improve my workflow so much to not have all these Guards anymore

      • Carlos Villegas

        After the introduction of System.ArgumentNullException.ThrowIfNull(…) I don’t believe we need the parameter null check at the compiler level anymore. If introduced, it may cause a mix of different approaches when guarding our arguments. We not only guard for nulls.

        Now, one self explanatory line of code is all you need:

        ArgumentNullException.ThrowIfNull(myParam);
      • Emmanuel Hansen

        You are forced to use top level statements on every new console program. If you want the classic Program.cs file, you have to use an extra parameter if you use the dotnet new, or use net5 and then switch to net6 if you are using Visual Studio. Why is something that is auto-generated by the template for you, now needs extra effort to do. In most cases, you aren’t working on a single class. So only your Program.cs looks weird while all your other classes have full namespaces and class names. It takes less effort to delete blocks of code, than to copy and paste from an external source, so why not make those who want to use top level statements just use CTRL+A and Delete to get started.

    • MgSam

      I initially wasn’t a fan of target-typed new but it actually is really useful in some scenarios. Microsoft just did not sell the feature well in the blog posts. It prevents you having to repeat the type names for field initialization and even more importantly, for initializing an array or list.

      class Foo
      {
          private Dictionary<string, List> _myDict = new();
          private List<Bar> _bars = new List<Bar>  
          {
              new() { A = 1, B = 2 },
              new() { A = 3, B = 4 },
              new() { A = 5, B = 6 }
          }
      }
    • Thorsten Sommer

      Not true @Tanvir Ahmad Arjel. I love all the new features and use them daily wherever possible. I want to say something positive: Thank you Microsoft and thank you to the community for putting so much work into .NET and C#. It’s really great that nowadays .NET works on Linux, macOS (even with M1 chip), Windows, iOS and Android. You folks are doing valuable work that is appreciated by many developers. Thanks for everything.

  • Someone

    I’d like to write more detailed opinion because I think everyone should write their own, especially when it comes to new “features” of C# and .NET releases. However, I’ll do it a bit later because right now I’d like to ask about file-level namespaces. It seems to be nice features and it reminds how Java package looks like.

    You showed us very simple example and it’s cool but you didn’t show WHERE usings should be put. In every C# program I see usings are used outside namespace:

    using System;
    // more usings
    
    namespace Tutorial
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Hello World!");
            }
        }
    }
    

    but how about file-level namespace? Where usings should be put? Java’s style be something like this:

    namespace Tutorial;
    
    using System;
    // more usings
    
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
    
  • Someone

    The args parameter remains available with top-level statements. It’s just less obvious.

    @Richard Lander: Hey, do you see what you wrote? Guys, I really love what have you done about C# and .NET Core, but c’mon why you introduced less obvious features? What community should do you to stop developing features which break C#? People need performance improvements, clean and consistent syntax and we don’t need new default templates which breaks OOP style and introduces more confusion. Please…

    • Richard LanderMicrosoft employee

      The new templates do not break OOP. They are exactly just as object oriented as the .NET 5 ones. Let’s be clear that static void main is NOT an object oriented pattern. It’s a model borrowed from C. I suspect very few developers create a Program object in their programs.

      Yes, the magic args variable is unfortunate. It isn’t clear what a better idea would have been. It’s the best approach available, IMO.

      • Kathleen DollardMicrosoft employee

        As Carlos Villegas pointed out above…

        Part of the decision behind args is that we already have a way to get args from the system: “System.Environment.GetCommandLineArgs()” It didn’t make sense to further expand the BCL with a nearly identical value.

        This was one of the harder decisions around top-level namespaces, and we welcome feedback.

        • Alexander Batishchev

          This was one of the harder decisions around top-level namespaces

          Reads: yeah, we’ve messed up but decided to go ahead anyway.

          and we welcome feedback.

          So far all the feedback is brushed off and the feature made it to the new version of C#.

          So what exactly you ​welcome? And what’s the point to provide the feedback if only positive is accepted and negative is ignored?

      • Someone

        I will install .NET 6 and check on my own how it works. I wonder how omnisharp will work when it comes to suggestions that some part of code is not needed to write (good example is not used args variable).

        Guys, don’t get me wrong because I really like C# up to .NET 5 because it seems more elegant, modern, open source and somehow better than other solutions I tried, however I don’t want to see fall of the language C#. Magic variables, too complicated and weird syntax or not needed features – these are examples which everyone doesn’t want to have in language. I think there’s more urgent features such as stability, performance, great libraries, frameworks, friendly and open source ecosystem, quality courses/tutorials/books/docs for everyone (beginners, medium learners, junior/senior/expert programmers).

  • MgSam

    This blog post left out most of the interpolated string improvements in C# 10. I think you should at least mention the fact that they are no longer a perf no-no and should now be preferred in almost all cases.

    You also left out the lambda improvements in C# 10; probably my favorite new feature.

  • Joseph Musser

    Good stuff! “Struct class” and “struct classes” were probably typos for “record struct” and “class records.”

  • eugene gordeev

    is it possible to have optional move semantics for value types(struct, etc.) in future C# versions?(to have custom destructor/finalizer in movable structs)
    With something like “movable” keyword:

    movable struct Test {
      int fileDescriptor;
      Test(){
         fileDescriptor = openFile("file_name");
       }
       ~Test(){
          closeFile(fileDescriptor);
       }
    }
  • Stilgar Naib

    “We’ve made improvements to top-level statements in response and continued to use it for templates.”

    The link leads to the C# 10 article which doesn’t contain anything about top-level statements

    • Richard LanderMicrosoft employee

      You are right. I’m referring to all the other features that make top-level statements feel like a real experience. When I think of top-level statements, I think of the allowance for one-line programs. I’m pretty sure I’m not the only one that feels that way. The other features enable that.

      There is some early .NET 7 discussion about project-less programs. That would further push down this path. I’m sure a lot of people would like that.

      • Stilgar Naib

        I agree but the wording in the article makes it sound like there are new things regarding this feature in particular. I tend to follow the development of the language and thought I missed something new about top level statements themselves.

  • schbaem

    “There was also feedback that some folks didn’t like the new simplified Program.cs file, with top level statements. We’ve made improvements to top-level statements in response and continued to use it for templates. We expect that most developers who prefer the traditional approach can straightforwardly add the extra ceremony themselves.”

    This is just a lie, no improvements were made upon the backlash, you just continued to defend what was not liked. The second sentence is a big f… y.. to all who voiced valid concerns over it.

    Hidden magic variables, that was the best your engineers could come up with?

  • Max Mustermueller

    Can you please fix the missing runtime redirection bug which exists since .NET (Core) 3.1? Its still broken in .NET 6 and causes us a lot of customer support tickets. You have said you would fix that, its been more than 1 year since then: https://github.com/dotnet/runtime/issues/36765

    WPF templates have not been updated as part of the release.

    WPF team is behind everything. They cannot even comment issues or review PRs. I wasn’t surprised that the last poll about repo satisfaction hasn’t been published. If you develop on desktop it’s getting hard to develop with .NET. Winforms looks too old, WPF is unofficial dead, WinUI too bugy (you cannot even set window title in XAML, it simply crashes. Reported since months and won’t be fixed for 1.0 stable release), UWP too restricted. Xamarin too much focus on mobile and too resource heavy on desktop

    • Hughes, Danial

      Completely agree!!!! We were all hoping that the open sourcing of WPF would at least allow the community to improve WPF as Microsoft seems to have abandoned LOB developers for the past 10 years (chasing shiny crap), but they delivered the final kick in the face by not resourcing the repo adequately.

      Now we get – “WPF templates have not been updated as part of the release.” – but Winforms has!?!? WTF!!!!???!