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

  • Reilly Wood

    I’m thrilled by the new “minimal” templates and features – huge thanks to the people on the .NET team who made this happen!

    This moves us toward a .NET that requires radically less ceremony and boilerplate, which I think is both more approachable for beginners and more convenient for experts. I’ve already been able to use the new ASP.NET minimal hosting APIs to dramatically simplify a fairly advanced scenario.

    This feels like a huge step in the right direction, and I’m sure it will get even better as the team iterates. Two thumbs up from me.

    • Someone

      As I said these new templates make me more confused. I am not expert and not beginner and I think it is better to explicitly use Program class and Main method than writing top-level statements.

  • Rob Hubbell

    @Rich Lander and @schbaem,

    Hello, I work for a small company where we like to give our customers the freedom to take like a Grid or a Table and fill out this table with usually: ints, doubles and string parameters, and the code behind is generated, where we do not interact with Xaml at all.

    We used to have this capability with the older Windows Form technology and Visual Studio 2010, but for some reason now, Microsoft uses the two part partial class solution, where The code generation is now hidden. Can you give us back the ability to inherit from a base class and have the code generator that is not hidden. To see the hidden code I put mouse over Initialize component and press F12, but the code behind is hidden or is now part of the executable.

    We really need back the full code generation so our customers have freedom to create code behind without Xaml.

  • Simon Niewinski

    Just created fresh C# console project. I don’t have a single using namespace in my code but I can reach Path, Thread, HttpClient and other classes that should be irrelevant if I’m not using them. Is that a new feature or I just stumbled on a bug?

    • LUIS HENRIQUE GOLL

      This is due to the new “global usings”. Please check first comment of this post to see how to disable it if you don’t want it.

  • Martin KearnMicrosoft employee

    Is there an article, page or something that can explain everything that is new and of interest in .net 6 all-up (not just this latest preview release)? I’ve not touched new .net for a while (probably since core 3.1) and want to ramp-up on what is new.

  • Luis Henrique Goll

    Having no “main method” is useful for scripts and azure functions, but that’s a huge drawback for regular projects.
    I took a look at the code Richard provided (https://github.com/dotnet/iot/blob/main/src/devices/Pn532/samples/Program.cs) and it is has several readability issues for not having a main method.
    I gotta agree with all the other folks here asking for the template to include this back.
    I get that removing the namespace and class is 99% of the time good, as your namespace and class will not be touched, but the main method is definitely an issue.

    Top reasons
    Readability
    Voice coding (how would you ask the tool to go to the main method? how would it locate itself with random code inside the file?)
    Documentation/commenting
    Code Review
    Testing

    And as someone has already stated: it is always easier to delete/remove a line than to add it back.

  • Jan Seriš

    Adding to the other comments – I also would like to add the opinion that the oversimplified templates for projects are a step backward for real programming world and also for learners (but they start to notice it only when something goes wrong with their code).
    The global usings and top-level statements in project templates are only a “cool” feature to attract newbie programmers into C# because it looks like Node.JS or Python.
    But what if anything goes wrong in their code? They will be very confused.
    This can happen pretty quickly even in their “simple” code. So I think it is a bad step forward.
    Real programmers need to know details what’s going on behind every line of their code because when it breaks, they will be the ones to fix it. There is no space for a response like “I don’t know why it’s broken/doesn’t work” in professional programming.
    Writing this being CS student for 5 years and having 2 years dev experience.
    I can closely see at the university that Javascript/Typescript development has become very attractive more and more in the very past few years.
    But any real-world C# application needs the what you call “boilerplate” back because of customizing.
    The same philosophy that the code needs simplifying can be seen in the newest dotNET YouTube channel videos telling “you won’t ever change the Startup.cs code anyways”.
    The reality is that as soon as you do any application logic or deploying, you definitely will need to understand and change it.

    • Stavros Kasidis

      I agree 100%
      The least they could do is have the old or new templates available through an option
      e.g.
      dotnet new console (creates the top-level template)
      dotnet new console –expert (creates the old style template)

  • Christian Andersen

    I’m having a weird issue after going from .NET 5 to this preview, where my Inputfile will timeout my app, if i try to upload more than 100KB