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

Comments are closed. Login to edit/delete your existing comments

  • Michael Taylor

    I tend to agree with others that I don’t agree with changing the existing templates. New templates should be added instead.

    I have been teaching C# to beginners for years and never have I had a single student struggle with the basic structure of a new console application. Most students have already learned either Java or C++ and therefore understand entry points, Main and the equivalent of using. They understand functions and so that is where we stay for the first 2 weeks. They get it.

    It is clear the top-level statements, global using, etc are designed to cut down on the code but the reality is that it is still there. The compiler is just auto-generating it. If something isn’t right then it becomes harder to diagnose. If you need to customize it then it just looks odd compared to the full version. For use in sample code and quick start guides I’m totally fine with the concept but for any real program I think it is taking a step back from good coding practices.

    OOP says to wrap your functionality in types (classes) and to use functions to break up functionality. Now it is one gigantic block of code. How is that good design? I don’t know about everybody else but we subdivide our app startup into logical chunks (often in other files) so it is easy to find stuff. Any new dev can quickly look at the files (or function names) and find the “bindings” we have configured or the “startup” logic we use. Now they are going to have to scan a potentially large block of code to find it. Or even worse a “Powershell like” block where there is an entry point buried within a file full of functions. I get that some people like this but having it forced on those of us who consider it a step backwards is undesirable. What is going to end up happening is we’ll just have to create our own custom templates that restore the solid coding practices that we use.

    Don’t even get me started about what will happen when you do need to switch from the minimalistic version to a more consistent version as your code grows. For example we have lots of extension methods off of things that are no longer contained in types so this makes it a lot harder to “extend”.

    The one thing I do like is having program.cs and startup.cs combined. It never made sense to me that .NET Core had a program.cs that nobody ever touched. But honestly we could always combine them into one by hand anyway.

    • Felix Patric

      The Startup class was solid and it made it easier with the plumbing but now they’ve merged it with Program class and i find that rather confusing.

  • Ricardo Francisco

    I see why simple templates would be useful in a world where dotnet is competing with node and python, but to be honest I would prefer MS to spend time developing realistic applications with guidance and best practices than this.
    One of the things that annoy me is the “Hello World” we live in now. Everyone is focusing on showing the best Hello Worlds and there are hundreds of people writing the same blog post with slight variations showing off the same superficial knowledge. But in the real world, we get paid to write real code to solve real problems and there is not much effort put into content that would help us get started with that.

  • Phillip Carter

    I’m just here to say how happy I am to see the template changes. This is very much the direction that C# and .NET must head moving forward, and it’s wonderful to see the C# language evolution continuing to push for less ceremony and less things people need to keep in their heads whenever they’re trying to get stuff done. Keep it going!

    I’m also a little biased 🙂

    • Stefan W

      I couldn’t agree more.
      It’s delightful to see how you guys keep up with modernizing and evolving the language. This is one of things I love the most about C# & .NET Core.

      Thanks for the excellent work!

  • Andriy Savin

    Wow, the static abstract interface members and generic math are huge, indeed! Thanks for that!

  • Mike-E

    First off, I want to say how amazing it is to watch the progress and innovation here w/ .NET and .NET6.0. Really great job out there to the team and to all the long hours (no I don’t think that’s a bad thing at all!).

    Secondly, I have been spending the day upgrading to net6.0 and the first thing that I noticed is that without any extensions (read: ReSharper 🙂 ) applied, it takes a full 2 minutes at 10-15% CPU to open my large solution (about 100 projects 5k files) in Visual Studio 2022 Preview 3.0 with net6.0. This was not the case with net5.0 and while I do not have the exact measures with me, I can pretty much guarantee it didn’t take 2 minutes for my solution to get processed.

    But that’s not all. 🙂 Additionally, when pressing basic keys in my solution now, it will take nearly a minute (54 seconds) at 8-10% CPU to process. For each keypress.

    This definitely didn’t happen in net5.0, so I did some digging. The issue is source generators:

    https://i.imgur.com/Rokaj0B.png

    With only two source generators, JsonSourceGenerator and RazorSourceGenerator, nearly a minute of 10% CPU time is taken with each keypress in a larger `net6.0` solution. And this is without R# enabled. You can only imagine how much more intensive it gets when that occurs.

    This also doesn’t account for other source generators that might get applied at some point, either through install, package, or extension.

    You can say that I am pretty concerned now here with net6.0.

    Has this been discussed/considered anywhere? It would be great to know that this is a known consideration, because as it stands it’s pretty difficult to work with .net6.0 especially when net5.0 seemed much faster and quieter. Cooler too, without all the CPU heat. 🙂

    Thank you for any discussion/thoughts/insight you can provide!

  • Rod Macdonald

    Richard, you need to incorporate something akin to BlazorFluentUI into .NET 6 so there’s a common Fluent UI stack for Blazor without relying on a React derivative. Then you need a design surface that works across the Fluent UI ‘board’.

  • Robert Sundström

    Is there an easy way to make nullable warnings into errors, without manually having to add this to the file:

    nullable

    I get that some don’t want to be this strict by default in their existing projects.

    But I’m thinking about those who have gone all in, and potential beginners. There should be an option in the templates.

  • Hazy Fish

    Where can we find the global usings declared in the Web API template?

  • Someone

    Hi!

    I study CS and I tried several different programming languages, frameworks, libraries etc. I am not expert but I want to give some feedback. I hope my opinion would be valuable for you.

    I was intersted in learning .NET/C# because of open source, cross-platform (Linux yeah), OOP, typed language, performance and so on. I started few months ago – from time to time I just casually read and watch some random stuff on Internet. Also I got 2 books, but only read few pages so far. I was learning basic syntax, creating console apps. I really like dotnet CLI tool for generating templates, building solution, adding nuget packages and so on. Also I was playing with ASP.NET Core – mostly by using webapi template because I like idea of REST webservices (microservices). It took me some time to understand basic features of ASP.NET Core webapi but finally I did it. I like idea about controller classes, service classes and using Startup.cs to register them. Finally, thanks to one guy and .NET I understood a purpose of DTO classes, AutoMapper, entity classes, returning JSON data. In short I understood how to build simple API which returns data from database. Later, I understood sln files and how to combine multiple projects – in addition with src and test directories. Then I understand basic concepts about TDD and unit tests with xunit tool. All of these thanks to modern .NET 5 and of course helpful people with their great exaplanations.

    As you can see everything was great but yesterday I saw this blog post about preview 7 and I was somehow shocked. To me changes related to new templates and top-level programming are very confusing. I thought it is first time and I should read more about these changes, sleep on it. It is the reason why I did not write this post yesterday. Now, I can confirm that I do not like these changes. I am not expert because I am only student – I want to find my favourite programming tool and dive into it. I like C# because of strong types, OOP and that it is not like Python or JavaScript which are not compiled languages. Personally, I did few assignments in both these languages and some of their features are fun but C# got me because of its explicitly stuff such as Program class and Main method. I like that approach because due to this I know what exactly is going on. However preview 7 introduces top-level statements and destroys the feeling of OOP design. I do not know why but “default new” templates and top-level programming make me feeling upset because I learnt something and then this knowledge is useless. And now when .NET 6 came out I will have to write all code from scratch. I think if C# focuses on OOP then it should stick with Program class and Main method as entry point. Look at Java, Kotlin, C, C++, Rust – they also have entry point as explicit Main function/method and it is fine. I was doing some stuff with these languages and I never had any problems with understanding this stuff. I also like idea of spliting code to different files/classes/interfaces and in my opinion writing whole source code in one file (top-level statements) seems to be very bad decision. I think top-level programming tries to change C# to something which C# should not be. Python or JavaScript are different and I think C# should not be like them. I do not like the way how Python or JavaScript apps work. They do not contain Main method so it always make me feeling that everything is like C or C++ globals and there are not “main place” where everything starts up.

    I think if there are too many ways to do something it introduces overwhelming and also makes people confused and upset. I think it would be better to see current way as Program class with Main method. It is not that hard to write read namespace, class and one static method with args. Even web template could be changed to explicitly use Program class and Main method because due to that people know what is going on.

    In conclusion, I would like to say that .NET/C# seems to be nice technology however it looks like taking wrong direction of new features and changes. I am not new to programming, also I am not expert but I hope even my opinion could be valuable for you guys.

    So, what I like the most:
    – OOP, not too much “weird” syntax, strong types
    – open source, cross-platform
    – dotnet CLI
    – Visual Studio Code for C# cross-platform development
    – compiled language
    – explicit stuff which is easy to follow and understand (Program class, Main method, importing namespaces)
    – consistency so not writing global functions or procedural stuff
    – new scoped namespaces seems to be fine (it reminds me Java’s package statement)
    – bug fixes, performance improvements

    What I do not like the most:
    – features like top-level statements which try to make C# feel like Python, JavaScript
    – new default templates
    – introducing weird and complex features, syntax changes
    – too many ways to do something
    – not well documented API, lacks of exaplanations in documentation
    – bugs and quirks in language

    Finally, I would like to say that I hope C#/.NET will be better in the future. I would like to see it on higher rank in TechEmpower benchmark. I think performance and making C#/.NET less resource consuming should be priority goals – not need to introduce weird “newbie features” which in my opinion make newcomers more confused. Java is somehow similar to C# and look people do not get confused when starting with OOP.

  • Andrew Toasterr

    Is there a way to revert back to the VS2022 Preview 2? I’m not really a fan of the new templates and i want the old ones back. From reading other comments i see there is not effort to make an option to bring back the old templates, so is there a way to download the older version?

    • Someone

      I hope templates will be back because these new ones seem to be bad design.

    • Felix Patric

      Stick to Visual Studio 2019 and never upgrade to Visual Studio 2022 cause the previews have shelf life and once dotnet 6 has got to GA, forget it.