Announcing .NET 6 Preview 7

Richard

We are delighted to release .NET 6 Preview 7. It is the last preview before we enter the (two) Release Candidate (RC) period. The team has been burning the midnight oil and the candle at both ends getting the last set of features in before we slow the speed down on the release. It’s also the release where you will see the last bit of polish on various features and large it-took-the-whole-release features come in all at once. From this point, the team will be focused on bringing all the features to uniform (high) quality so that .NET 6 is ready for your production workloads.

On the topic of production workloads, it’s worth reminding everyone that both the .NET website and Bing.com have been running on .NET 6 since Preview 1. We’re in talks with various teams (Microsoft and otherwise) about going into production with the .NET 6 RCs. If you are interested in that and want guidance on how to approach that, please reach out at dotnet@microsoft.com. We’re always happy to talk to early adopters.

You can download .NET 6 Preview 7 for Linux, macOS, and Windows.

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

.NET 6 Preview 7 has been tested and is supported with Visual Studio 2022 Preview 3. Visual Studio 2022 enables you to leverage the Visual Studio tools developed for .NET 6 such as development in .NET MAUI, Hot Reload for C# apps, new Web Live Preview for WebForms, and other performance improvements in your IDE experience. .NET 6 is also supported with Visual Studio Code. The latest version of the C# extension for Visual Studio Code has been updated for .NET 6 Preview 7 and includes support for C# 10.

Check out the new conversations posts for in-depth engineer-to-engineer discussions on the latest .NET features. We also published posts on String Interpolation in C# 10 and .NET 6  Preview Features in .NET 6 – Generic Math.

.NET SDK: C# project templates modernized

We updated .NET SDK templates to use the latest C# language features and patterns. We hadn’t revisited the templates in terms of new language features in a while. It was time to do that and we’ll ensure that the templates use new and modern features going forward.

The following language features are used in the new templates:

  • Top-level statements
  • async Main
  • Global using directives (via SDK driven defaults)
  • File-scoped namespaces
  • Target-typed new expressions
  • Nullable reference types

You might wonder why we enable certain features via templates instead of enabling them by default when a project targets .NET 6. We’re OK with requiring some amount of work on your part to upgrade applications to a new version of .NET as a tradeoff for improving the default behavior of the platform. This allows us improve the product without complicating project files over time. However, some features can be quite disruptive with that model, such as nullable reference types. We don’t want to tie those features to the upgrade experience, but want to leave that choice to you, both when and if ever. The templates are a much lower risk pivot point, where we’re able to set what the new “good default model” is for new code without nearly as much downstream consequence. By enabling these features via project templates, we’re getting the best of both worlds: new code starts with these features enabled but existing code isn’t impacted when you upgrade.

Console template

The console template demonstrates the biggest change. It’s now (effectively) a one-liner by virtue of top-level statements and global using directives.

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

The .NET 5 version of the same template includes several lines of familiar ceremony that provide the structure previously necessary for even a single line of actual code.

using System;

namespace Company.ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

The project file for the console template has also changed, to enable the nullable reference types feature, as you can see in the following example.

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

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

</Project>

Other templates also enable nullability, implicit global usings, and file scoped namespaces, including ASP.NET Core and Class Library.

ASP.NET web template

The web template is also similarly reduced in lines of code, using the same features.

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

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

app.Run();

ASP.NET MVC template

The mvc template is similar in structure. In this case, we’ve merged Program.cs and Startup.cs into a single file (Program.cs), creating a further simplification.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Template compatibility

See the following documents for compatibility concerns with using the new templates.

Libraries: Reflection APIs for nullability information

Nullable reference types is an important feature for writing reliable code. It works great for writing code but not (until now) for inspecting it. New Reflection APIs enable you to determine the nullability nature of parameters and return values for a given method. These new APIs will be critical for Reflection-based tools and serializers, for example.

For context, we added nullable annotations to the .NET Libraries in .NET 5 (and finished up in .NET 6) and are in the process of doing same with ASP.NET Core this release. We also see developers adopting nullability for their projects.

Nullability information is persisted in metadata using custom attributes. In principle, anyone can already read the custom attributes, however, this is not ideal because the encoding is non-trivial to consume.

The following examples demonstrate using the new APIs for a couple different scenarios.

Getting top-level nullability information

Imagine you’re implementing a serializer. Using these new APIs the serializer can check whether a given property can be set to null:

private NullabilityInfoContext _nullabilityContext = new NullabilityInfoContext();

private void DeserializePropertyValue(PropertyInfo p, object instance, object? value)
{
    if (value is null)
    {
        var nullabilityInfo = _nullabilityContext.Create(p);
        if (nullabilityInfo.WriteState is not NullabilityState.Nullable)
        {
            throw new MySerializerException($"Property '{p.GetType().Name}.{p.Name}'' cannot be set to null.");
        }
    }

    p.SetValue(instance, value);
}

Getting nested nullability information

Nullability has special treatment for objects that can (formally) hold other objects, like arrays and tuples. For example, you can specify that an array object (as a variable, or as part of a type member signature) must be non-null but that the elements can be null, or vice versa. This extra level of specificity is inspectable with the new Reflection APIs, as you see demonstrated in the following example.

class Data
{
    public string?[] ArrayField;
    public (string?, object) TupleField;
}
private void Print()
{
    Type type = typeof(Data);
    FieldInfo arrayField = type.GetField("ArrayField");
    FieldInfo tupleField = type.GetField("TupleField");

    NullabilityInfoContext context = new ();

    NullabilityInfo arrayInfo = context.Create(arrayField);
    Console.WriteLine(arrayInfo.ReadState);        // NotNull
    Console.WriteLine(arrayInfo.Element.State);    // Nullable

    NullabilityInfo tupleInfo = context.Create(tupleField);
    Console.WriteLine(tupleInfo.ReadState);                      // NotNull
    Console.WriteLine(tupleInfo.GenericTypeArguments [0].State); // Nullable
    Console.WriteLine(tupleInfo.GenericTypeArguments [1].State); // NotNull
}

Libraries: ZipFile Respects Unix File Permissions

The System.IO.Compression.ZipFile class now captures Unix file permissions during create and set file permissions when extracting zip archives on Unix-like operating systems. This change allows for executable files to be round-tripped through a zip archive, which means you no longer have to modify file permissions to make files executable after extracting a zip archive. It also respects the read/write permissions for user, group, and other as well.

If a zip archive doesn’t contain file permissions (because it was created on Windows, or using a tool which didn’t capture the permissions, like an earlier version of .NET) extracted files get the default file permissions, just like any other newly created file.

The Unix file permissions work with other zip archive tools as well, including:

Early .NET 7 Feature Preview: Generic Math

For .NET 6, we’ve built the capability to mark APIs as “in preview”. This new approach will allow us to offer and evolve preview features across multiple major releases. In order to use preview APIs, projects need to explicitly opt-into using preview features. If you use preview features without explicitly opting-in, you will see build errors with actionable messages, starting in .NET 6 RC1. Preview features are expected to change, likely in breaking ways, in later releases. That’s why they are opt-in.

One of those features we’re previewing in .NET 6 is static abstract interface members. Those allow you to define static abstract methods (including operators) in interfaces. For example, it is now possible to implement algebraic generic methods. For some folks, this feature will be the absolute standout improvement we’re delivering this year. It is perhaps the most important new type system capability since Span<T>.

The following example takes an IEnumerable<T> and is able to sum all the values due to the T being constrained to INumber<T>, possibly an INumber<int>.

public static T Sum<T>(IEnumerable<T> values)
    where T : INumber<T>
{
    T result = T.Zero;

    foreach (var value in values)
    {
        result += value;
    }

    return result;
}

This works because INumber<T> defines various (static) operator overloads that must be satisfied by interface implementors. The IAdditionOperators is perhaps the easiest new interface to understand, which INumber<T> itself is derived from.

This is all powered by a new feature which allows static abstract members to be declared in interfaces. This enables interfaces to expose operators and other static methods, such as Parse or Create, and for those to be implemented by a derived type. Please see our associated blog post for more details!

All of the features mentioned are in preview for .NET 6 and not supported for use in production. We would appreciate your feedback using them. We intend to continue evolving and improving the generic math features and the runtime and C# features that support them in .NET 7. We expect to make breaking changes to the current experience, and that’s part of the reason why the new APIs are marked as “in preview”.

Libraries: NativeMemory APIs

We added new native memory allocation APIs exposed via System.Runtime.InteropServices.NativeMemory. These APIs represent equivalents to the malloc, free, realloc, and calloc C APIs and also includes APIs for doing aligned allocations.

You may be wondering about how to think about these APIs. First, they are low-level APIs that are intended for low-level code and algorithms. Application developers would rarely if ever use these. Another way to think about these APIs is similarly to the platform intrinsic APIs, which are low-level .NET APIs for chip instructions. These APIs are similar but expose low-level APIs for memory-related operations.

Libraries: System.Text.Json serialization notifications

The System.Text.Json serializer now exposes notifications as part of (de)serialization operations. They are useful for defaulting values and validation. To use them, implement one or more of the interfaces IJsonOnDeserialized, IJsonOnDeserializing, IJsonOnSerialized or IJsonOnSerializing within the System.Text.Json.Serialization namespace.

Here’s an example that validates during both JsonSerializer.Serialize() and JsonSerializer.Deserialize() to ensure a FirstName property is not null.

  public class Person : IJsonOnDeserialized, IJsonOnSerializing
  {
      public string FirstName{ get; set; }

      void IJsonOnDeserialized.OnDeserialized() => Validate(); // Call after deserialization
      void IJsonOnSerializing.OnSerializing() => Validate(); // Call before serialization

      private void Validate()
      {
          if (FirstName is null)
          {
              throw new InvalidOperationException("The 'FirstName' property cannot be 'null'.");
          }
      }
  }

Previously, you would need to implement a custom converter to achieve this functionality.

Libraries: System.Text.Json serialization property ordering

We’ve also added the ability to control the serialization order of properties, with System.Text.Json.Serialization.JsonPropertyOrderAttribute. An integer specifies the order. Smaller integers are serialized first; properties that have no attribute have a default ordering value of 0.

Here’s an example that specifies JSON should be serialized in the order Id, City, FirstName, LastName:

public class Person
{
    public string City { get; set; } // No order defined (has the default ordering value of 0)

    [JsonPropertyOrder(1)] // Serialize after other properties that have default ordering
    public string FirstName { get; set; }

    [JsonPropertyOrder(2)] // Serialize after FirstName
    public string LastName { get; set; }

    [JsonPropertyOrder(-1)] // Serialize before other properties that have default ordering
    public int Id { get; set; }
}

Previously, the serialization order was determined by reflection order which was neither deterministic nor resulting in a specific desired order.

Libraries: “write raw” JSON with System.Text.Json.Utf8JsonWriter

There are times when you need to integrate “raw” JSON when writing JSON payloads with Utf8JsonWriter.

For example:

  • I have a deliberate sequence of bytes I want to write out on the wire, and I know what I’m doing (as demonstrated in the following example).
  • I have a blob which I think represents JSON content and which I want to envelope, and I need to make sure the envelope & its inner contents remain well-formed
JsonWriterOptions writerOptions = new() { WriteIndented = true, };

using MemoryStream ms = new();
using UtfJsonWriter writer = new(ms, writerOptions);

writer.WriteStartObject();
writer.WriteString("dataType", "CalculationResults");

writer.WriteStartArray("data");

foreach (CalculationResult result in results)
{
    writer.WriteStartObject();
    writer.WriteString("measurement", result.Measurement);

    writer.WritePropertyName("value");
    // Write raw JSON numeric value using FormatNumberValue (not defined in the example)
    byte[] formattedValue = FormatNumberValue(result.Value);
    writer.WriteRawValue(formattedValue, skipValidation: true);

    writer.WriteEndObject();
}

writer.WriteEndArray();
writer.WriteEndObject();

The following is a description of what the code above — particularly FormatNumberValue — is doing. For performance, System.Text.Json omits the decimal points/values when the number is whole, like 1.0. The rationale is that writing fewer bytes is good for perf. In some scenarios, it might be important to retain decimal values because the consumer treats numbers without decimals as integers, otherwise as doubles. This new “raw value” model allows you to have that level of control wherever you need it.

Libraries: Synchronous stream overloads on JsonSerializer

We’ve added new synchronous APIs to JsonSerializer for serializing and deserializing JSON data to/from a stream. You can see that demonstrated in the following example.

using MemoryStream ms = GetMyStream();
MyPoco poco = JsonSerializer.Deserialize<MyPoco>(ms);

These new synchronous APIs include overloads that are compatible and usable with the new System.Text.Json source generator, by accepting JsonTypeInfo<T> or JsonSerializerContext instances.

Libraries: System.Text.Json.Nodes.JsonNode support for dynamic is removed

Support for the C# dynamic type in the JsonSerializer has been removed. We added dynamic support in Preview 4 but later decided to be a poor design choice, including making it a required dependency of the JsonNode type.

This change is considered a breaking change from a .NET 6 preview to preview standpoint but not from .NET 5 to 6.

Libraries: System.Diagnostics Propagators

We’ve been improving support for OpenTelemetry over the last couple years. One of the key aspects of enabling great support is ensuring that all components that need to participate in telemetry production produce network headers in the right format. It’s really hard to do that, particularly as the OpenTelemetry specification changes. OpenTelemetry defines the propagation concept to help with this situation. We’re in the process of adopting propagation to enable a general model for header customization.

Context on the broader concepts:

The following code demonstrates the general approach for using propagation.

DistributedContextPropagator propagator = DistributedContextPropagator.Current;
propagator.Inject(activity, carrier, (object theCarrier, string fieldName, string value) =>
{
   // Extract the context from the activity then inject it to the carrier.
});

You can also choose to use a different propagator.

// Set the current propagation behavior to not transmit any distributed context information in outbound network messages.
DistributedContextPropagator.Current = DistributedContextPropagator.CreateNoOutputPropagator();

The DistributedContextPropagator abstract class determines if and how distributed context information is encoded and decoded as it traverses the network. The encoding can be transported over any network protocol that supports key-value string pairs. DistributedContextPropagator inject values into and extracts values from carriers as key/value string pairs. By adding support for propagators, we’ve enabled two things:

  • You’re no longer required to use the W3C TraceContext headers. You can write a custom propagator (i.e., use your own headers names including not sending them at all) without the libraries HttpClient, ASP.NET Core having a priori knowledge of this custom format
  • If you implement a library with a custom transport (e.g., message queue), you can now support various wire formats as long as you support sending and receiving a text map (e.g. Dictionary<string, string>)

Most application code does not need to directly use this feature, however, it is likely that you will see it in a call-stack if you use OpenTelemetry. Some library code will want to participate in this model if it cares about tracing and causality.

Libraries: Simplified call patterns for cryptographic operations

The .NET encryption and decryption routines were designed around streaming, with no real concept for defining when the payload is already in memory. The new Encrypt- and Decrypt- methods on SymmetricAlgorithm accelerate the already-in-memory case, and are intended to provide clarity to the caller and the code reviewer. Additionally, they support reading from and writing to spans.

The new simplified methods offer a straightforward approach to using cryptographic APIs:

private static byte[] Decrypt(byte[] key, byte[] iv, byte[] ciphertext)
{
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;

        return aes.DecryptCbc(ciphertext, iv);
    }
}

With the new Encrypt- and Decrypt-methods, only the key property is used from the SymmetricAlgorithm instance. The new DecryptCbc method supports choosing the padding algorithm, but PKCS#7 is used with CBC so often that it’s a default argument. If you like the clarity, just specify it:

private static byte[] Decrypt(byte[] key, byte[] iv, byte[] ciphertext)
{
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;

        return aes.DecryptCbc(ciphertext, iv, PaddingMode.PKCS7);
    }
}

You can see that the existing pattern — with .NET 5 — required significantly more plumbing for the same outcome.

private static byte[] Decrypt(byte[] key, byte[] iv, byte[] ciphertext)
{
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;
        aes.IV = iv;

        // These are the defaults, but let's set them anyways.
        aes.Padding = PaddingMode.PKCS7;
        aes.Mode = CipherMode.CBC;

        using (MemoryStream destination = new MemoryStream())
        using (ICryptoTransform transform = aes.CreateDecryptor())
        using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
        {
            cryptoStream.Write(ciphertext, 0, ciphertext.Length);
            cryptoStream.FlushFinalBlock();
            return destination.ToArray();
        }
    }
}

Libraries: Full Case Mapping Support in Globalization Invariant Mode

Globalization Invariant Mode enables you to remove application dependencies on globalization data and behavior in exchange for smaller applications (primarily on Linux). We’ve improved Globalization Invariant Mode to support case mapping of the full Unicode character set. Previously, this mode only supported ASCII range characters for operations like String.ToUpper, String.ToLower, and string comparisons and searching with the IgnoreCase option.

Alpine-based .NET container images are the only environment where we enable globalization environment mode by default.

Runtime: W^X (write xor execute) support for all platforms and architectures

The runtime now has a mode in which it doesn’t create or use any memory pages that are writeable and executable at the same time. All executable memory is mapped as read-execute only. This feature was enabled on macOS — for Apple Silicon — earlier in the release. On Apple Silicon machines, memory mappings that are writeable and executable at the same time are prohibited.

This capability is now enabled and supported on all other platforms as an opt-in experience. On these platforms, executable code generation / modification is done via separate read-write memory mappings. This is true for both JIT’d code and runtime-generated helpers. These mappings are created at virtual memory addresses that are different from the executable code address and exist only for a very brief period of time when the writing is performed. For example, the JIT now generates code into a scratch buffer that is copied into the executable memory using a single memory copy function call after the whole method is jitted. And the writeable mapping lifetime spans only the time of the memory copy.

This new feature can be enabled by setting the environment variable DOTNET_EnableWriteXorExecute to 1. This feature is opt-in in .NET 6 because it has a startup regression (except on Apple Silicon). The regression is ~10% in our ASP.Net benchmark tests when compiled with Ready To Run (R2R). However, the steady state performance was measured to be the same with and without the feature enabled. For applications where startup performance isn’t critical, we recommend enabling this feature for the improved security that it offers. We intend to resolve the performance regression as part of .NET 7 and enable the feature by default at that time.

Runtime: CodeGen changelog

The following changes were made in code generation in Preview 7.

Community PRs

The following PRs were all from @SingleAccretion:

Dynamic PGO

The following PRs support the dynamic PGO project.

LSRA

The following PRs support the LRSA project.

Loop Optimization

The following PRs improve loop optimization.

Structs

The following PRs improve struct handling.

Optimizations

The following PRs provide general optimizations.

Contributor Showcase

We’re coming — as mentioned multiple times — to the end of the release. We thought we’d take a few moments to showcase some community contributors who have been significant contributions. There are many folks who have contributed significantly. The following folks were nominated for this showcase from a few folks on the team. We can continue with this showcase section if people like it.

The text is written in the contributor’s own words.

Andrii Kurdiumov (@kant2002)

I’m Andrii Kurdiumov, and I’m living in Almaty, Kazakhstan. By trade I work on building new products in enterprise space.

I decided to contribute to .NET because I see opportunities to enter new markets with new MS technologies. Blazor WebAssembly and .NET on Raspberry are working well, but I fear these technologies can break under extreme or unusual requirements. So I decided to invest a bit of my time and check if NativeAOT can help me. So far, my contributions mostly centered around NativeAOT project, even WinForms one. And for some time I plan to follow that direction.

Those were the reasons why I started contributing. Reasons why I continue contributing are the following: I constantly feel support from people working on .NET, I know that they have a lot of experience in the vast areas of software dev. And people working around .NET care a great deal about high quality standards and that immediately set the tone for anybody around. I personally like these kinds of environments.

SingleAccretion

I am an aspiring young programmer, from, as surprisingly many of us .NET people are, Russia. As a person writing code, I’ve started with .NET and progressively went from the higher layers of the stack to lower ones, ending up working on the compiler, just at the edge of the void that is the VM :), . It is a rather fascinating kind of activity, understanding the system as complex and old as RyuJit, but that is also its unique draw, I suppose, at least for curious people like myself.

However, that is not the reason why I contribute to .NET, after all, I could’ve just started my own fork. I think it would be fair of me to say, having waded through so many GH threads throughout these months, that is not at any place one can see people with such deep and rich understanding of the problems in our field. Becoming part of this community, and being able to reason the problems that are reasoned about in it, is something I truly treasure.

Closing

We’re at that point in the release where we consider new features and improvements done. Nice work, team. That’s a wrap on another season of .NET previews.

We continue to want and rely on your feedback. We will focus the rest of .NET 6 on regressions (functional and performance) and bugs found in the new features. Functional improvements in most cases will need to wait for .NET 7. Please share any and all feedback you have and we’ll be happy to categorize it.

Thanks for everyone who has contributed to making .NET 6 another great release.

Thanks for being a .NET developer.

65 comments

Leave a comment

    • Damian EdwardsMicrosoft employee

      In preview 7 you can disable the feature by setting the MSBuild property DisableImplicitNamespaceImports to “true”. Note that in the next preview release this feature is changing to be opt-in (rather than opt-out) and the property controlling whether it’s enabled or not is changing to ImplicitUsings. New projects created will have that property set to “enabled” in their project file.

  • Emmanuel Adebiyi

    This is a little bit out of context but can anyone point me in the right direction to learn how to write such low level code code from the runtime.

    By the way, great work from the team and the community. .NET 6 is just awesome!

    • Richard LanderMicrosoft employee

      Can you elaborate? Are you asking how you can learn to contribute to the runtime? If so, I’d probably start with participating on issues or a PR that you find approachable (on dotnet/runtime or another repo). That’s the same model that pretty much all of us have used.

      • Dan MoseleyMicrosoft employee

        As Richard wrote, we would be happy for you to contribute. Every long term contributor started with a single PR. In the repo that is most relevant to you (eg., dotnet/aspnetcore, or dotnet/runtime for core runtime and libraries) search through issues with the “up-for-grabs” label. Tag a regular contributor in that area if you need help.

  • minhphuc

    The team has been burning the midnight oil and the candle at both ends getting the last set of features in

    It is an unhealthy work ethic if this is true.

      • Richard LanderMicrosoft employee

        The main point is that the team has been very focused and working hard on getting changes in for the last preview. Preview 7 is/was the last train leaving the station for new features.

        Our team supports millions of developers around the world. We have to balance three things:

        • Add a lot of features each release to provide new value.
        • New releases are high-quality.
        • Ship according to our annual November schedule.

        This model makes the last preview very important. People work hard to ensure their work gets in by the last preview.

  • schbaem

    I do not understand why its a good thing that the default templates will now produce such ‘useless’ default files. Useless because every real application will have to copy the missing things back. How much console applications do you write which do not need args? Where do you put the documentation for the functions used in Program.cs?

    So please elaborate why and how we as developers profit from this change.

    • Tanner Gooding Microsoft employee

      As per https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements and https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements, you still implicitly get all the boilerplate you’d expect and things like args are fully available.

      That is, Console.WriteLine(args); is a valid top level program and works exactly as you’d expect.

      You can also check out https://aka.ms/new-console-template, which has additional details on the new console template.

    • schbaem

      So from what i gather from the links you provided, this is only for “simple programs and new programmers”. Is there a config option for this available or do you force this on all of us?

        • schbaem

          I took this straight from the posted links, where it says:

          “Top-level statements let you write simple programs for small utilities such as Azure Functions and GitHub Actions. They also make it simpler for new C# programmers to get started learning and writing code.”

          To me your posted non-simple sample looks like a nightmare which would not pass a code review at our site. I expect from Program.cs to setup the application. I dont’t expect that it is the applictaion. I expect you put code into files which describe what they are doing.

          The change to the default template will force me to add the removed things back to every console application i write. Why won’t you let us configure how the template sets things up? Where is the problem putting in a checkbox which controls if Program.cs should be created with top-level statements? It could even be selected by default.

          • Richard LanderMicrosoft employee

            To me your posted non-simple sample looks like a nightmare

            I agree with that, however, that’s not the point. The point is that you can do a lot with that model. What’s missing? What would you need to add back to achieve the level of expressivity that you want?

          • schbaem

            I would just add the whole stuff back as it is now. To me it would look totally irritating that Program.cs differs in style from all other code files in the project.

            I don’t like that i cannot see the namespace. I don’t like that the program class and its main method (which are now implementation details) cannot be documented like all other classes and methods.

            If i understand the docs correctly all other methods defined in Program.cs are now local functions to the Main method?

        • Christian Andritzky

          @Rich Lander I completely agree with @schbaem here. You should “make it as simple as possible, but not simpler.”.

          Regarding moving code from Startup.cs into Program.cs for “web” and “mvc” templates:
          As soon as you start writing integration tests using the TestServer helper, you will need the Startup class back again.
          Now we have to figure out which parts need to go to Startup.ConfigureServices, and what needs to go to Startup.Configure.

          Please reconsider your decision. I think this new “noob mode” is a big step backwards for “pros”.

  • David Ullrich

    First of all: excellent work and I was always looking forward to the news in the previews during the last months.

    Are there also interfaces similar to IJsonOnDeserialized for (de)serializing XML?

  • Arda Cetinkaya

    I don’t think the new project template for “console” apps. is good for new developers who are trying to learn C#. In previous project templates, at least some concepts like “class”, “namespace”, “methods” were there to give some clues about c# and soma main concepts… But now just a line that just shows a “Hello World” and then what?

    If I was a newbie who try to start c# and dotnet within these template I would be confused from where to start.

    I hope this template won’t be like in RTM

    • schbaem

      Even as experienced developer this throws me off. I do not understand that such a feature is prioritized and has developer resources allocated.

    • Miloš Perić

      Hello,
      My opinion on this is that if you are new developer you don’t need to know about classes, namespaces and other stuff. You want to make things work as simply as possible. You want to see some stuff print, add numbers etc. As you dig more into the language you learn about the classes structs and other good stuff.
      It’s same when you learn the math they learn you how to add numbers, subtract multiply and divide with integers.
      Once you get to know that stuff you learn other stuff, once you go to uni you learn about + sign, how it is defined and how it works in math world.

      I guess the point is to make some stuff look like python, nodejs etc. In python you don’t get anything, empty file and you write print(5) hit F5 and it works.

      • Richard LanderMicrosoft employee

        This is our thinking, too. Classes, namespaces and other things are not the most important first thing to learn when starting with C# and all the required ceremony offers very limited value in the typical case. These are objective truths.

        Chris Lattner described this in a recent interview. His view aligns with ours. https://www.youtube.com/watch?v=nWTvXbQHwWs&t=1376s. If you are unfamiliar with him or his credentials, I encourage you to look him up.

      • cs

        But as a professional developer I do need to know about classes, namespaces and other stuff, and I expect it to be familiar to me like I have been working with for the last 20 years.

        I get that you want to provide this for “new” developers, and even if you want to make that the default fine, but at least provide a way for it to be how it was…..it’s super annoying and unproductive to have to manually make the changes to put it back to the way you want.

        It is now making discovery harder by hiding things, and changing workflows, just a few examples:
        – say I’m looking at my large huge code base and want to find all “entry points” to programs…to compare behaviour, or add new abilities….so I do find in find in files for “Main”
        or
        – say I want to add /// xml documentation comments which document the parameters for my program, so that they get generated in docs/help , so hang those comments off “Main”…does that still happen?

        or
        – say I want to run the program and inspect the “arguments” being passed in before I call any code….so I “step into” main….or have a breakpoint somewhere….and then simply hover over the “args” parameter which is in “Main”…..can’t do that now….now I have to ensure the “locals” window is showing, or add “args” to the watch window and show that….and then switch my focus to wherever they happen to be positioned…..it’s forcing a change of workflow/muscle memory/arrangement of windows in my workspace layout.

  • Thomas Glaser

    Not sure how I feel about the new templates, on the one hand I like the brevity. On the other hand, if I want to go back to the “old” style I have to write all the boilerplate myself. If it was the opposite way, I would just have to remove the boilerplate code – that’s a lot faster.

  • Nathan

    I dont like the new templates my reason is the same as the other people here. At minimum please make this optional and provide the old templates under a new name. Prefeably keep the old templates as default and make the new ones avaible as console_minimal, console_toplevel, or somthing like that.

  • Paulo Pinto

    I get that C# is trying to compete with Python and JavaScript for attention of younger devs, but can you please don’t remove the expert templates?

    • Richard LanderMicrosoft employee

      It’s not that. These templates are rooted in a model from (at least) 40 years ago. They don’t pivot on expertise. It’s more like old school / new school.

      Sure, we do believe that these templates will make it easier to learn C# for folks familiar with Python or JavaScript or completely new to programming. Perhaps that’s half the reason for the change. The other half is that the current templates don’t carry their weight and are also confusing. I’ve had to look up the syntax for AsyncMain multiple times (even though it is trivial). Top-level statements erases that whole problem. That’s a virtue for exerts and novices alike.

    • Jiří Zídek

      I am afraid “Console” has never been “expert template”, and in fact I don’t remember where I have used in real project any other template than ASP-NET Web (Empty). The “templates” are good only for tutorial and learning, but not for real project, simply because they are bloated with lot of useless things you then just need to get rid off.