头图

Today we released .NET 7 Preview 6. This preview release of .NET 7 includes improvements to type converters, customizable JSON contracts, updates to the System.Formats.Tar API, constraints on .NET template authoring, and performance enhancements in the area of CodeGen.

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

.NET 7 Preview 6 has been tested on Visual Studio 17.3 Preview 3. If you want to try .NET 7 in the Visual Studio family of products, we recommend that you use the preview channel version . If you are using macOS, we recommend using the latest Visual Studio 2022 for Mac preview . Now, let's take a look at some of the latest updates in this release.

type converter

There are now primitive types for the newly added
Exposed type converters for DateOnly, TimeOnly, Int128, UInt128, and Half.

 namespace System.ComponentModel
{
    public class DateOnlyConverter : System.ComponentModel.TypeConverter
    {
        public DateOnlyConverter() { }
    }

    public class TimeOnlyConverter : System.ComponentModel.TypeConverter
    {
        public TimeOnlyConverter() { }
    }

    public class Int128Converter : System.ComponentModel.BaseNumberConverter
    {
        public Int128Converter() { }
    }

    public class UInt128Converter : System.ComponentModel.BaseNumberConverter
    {
        public UInt128Converter() { }
    }

    public class HalfConverter : System.ComponentModel.BaseNumberConverter
    {
        public HalfConverter() { }
    }
}

Example of use

 TypeConverter dateOnlyConverter = TypeDescriptor.GetConverter(typeof(DateOnly));
// 产生 DateOnly(1940, 10, 9) 的 DateOnly 值
DateOnly? date = dateOnlyConverter.ConvertFromString("1940-10-09") as DateOnly?;

TypeConverter timeOnlyConverter = TypeDescriptor.GetConverter(typeof(TimeOnly));
// 产生 TimeOnly(20, 30, 50) 的 TimeOnly 值
TimeOnly? time = timeOnlyConverter.ConvertFromString("20:30:50") as TimeOnly?;

TypeConverter halfConverter = TypeDescriptor.GetConverter(typeof(Half));
// 产生 -1.2 的一半值
Half? half = halfConverter.ConvertFromString(((Half)(-1.2)).ToString()) as Half?;

TypeConverter Int128Converter = TypeDescriptor.GetConverter(typeof(Int128));
// 产生 Int128 的 Int128 值。最大值 等于 170141183460469231731687303715884105727
Int128? int128 = Int128Converter.ConvertFromString("170141183460469231731687303715884105727") as Int128?;

TypeConverter UInt128Converter = TypeDescriptor.GetConverter(typeof(UInt128));
// 产生 UInt128 的 UInt128 值。最大值 等于 340282366920938463463374607431768211455
UInt128? uint128 = UInt128Converter.ConvertFromString("340282366920938463463374607431768211455") as UInt128?;

JSON contract customization

In some cases, developers who serialize or deserialize JSON find that they don't want or can't change the types because they either come from external libraries, or because they need to make some serialization-impacting changes that heavily pollute the code such as removing properties, Change how numbers are serialized, and how objects are created. Developers are often forced to write wrappers or custom converters, which are not only cumbersome but slow down serialization.

JSON contracts are customizable to allow users more control over what and how types are serialized or deserialized.

choose custom

Developers can "plug in" customizations in two basic ways, both of which end up assigning JsonSerializerOptions.TypeInfoResolver and requiring a resolver to be assigned:

  • Developers can use DefaultJsonTypeInfoResolver and add its modifiers, all modifiers will be called serially:
 JsonSerializerOptions options = new()
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
    {
        Modifiers =
        {
            (JsonTypeInfo jsonTypeInfo) =>
            {
                // 您在此处的修改,即:
                if (jsonTypeInfo.Type == typeof(int))
                {
                    jsonTypeInfo.NumberHandling = JsonNumberHandling.AllowReadingFromString;
                }
            }
        }
    }
};
Point point = JsonSerializer.Deserialize<Point>(@"{""X"":""12"",""Y"":""3""}", options);
Console.WriteLine($"({point.X},{point.Y})"); // (12,3)
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

  • By implementing System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver
    Write your own custom parser.
  • The code should return null when the type is not handled.
  • IJsonTypeInfoResolver can be combined with other content into a valid resolver, which will return the first non-null answer.
    For example JsonTypeInfoResolver.Combine(new
    MyResolver(), new DefaultJsonTypeInfoResolver())

customize

The job of the IJsonTypeInfoResolver is to provide JsonTypeInfo for any type serializer request - each option only happens once per type. JsonTypeInfo.Kind will determine which knobs the developer can change and based on the converter, which is determined based on the converter provided to the option. For example JsonTypeInfoKind.Object means properties can be added/modified, while JsonTypeInfoKind.None means no knobs are guaranteed to be used - which can happen when the type has custom converters.

JsonTypeInfo can be created by DefaultJsonTypeInfoResolver with pre-populated knobs from i.e. custom properties, or can be created from scratch by user: JsonTypeInfo.CreateJsonTypeInfo - Creating from scratch means user also needs to set JsonTypeInfo.CreateObject.

custom properties

Properties are only relevant if JsonTypeInfo.Kind == JsonTypeInfoKind.Object and DefaultJsonTypeInfoResolver will be pre-populated. They can be modified or created and added to the property list by using JsonTypeInfo.CreateJsonPropertyInfo, i.e. assuming you get a class from a separate library with a weirdly designed API that you can't change:

 class MyClass
{
    private string _name = string.Empty;
    public string LastName { get; set; }

    public string GetName() => _name;
    public void SetName(string name)
    {
        _name = name;
    }
}

Before this feature existed, you would need to wrap the type hierarchy or create your own custom converter for the type.
Now you can simply fix it:

 JsonSerializerOptions options = new()
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
    {
        Modifiers = { ModifyTypeInfo }
    }
};

MyClass obj = new()
{
    LastName = "Doe"
};

obj.SetName("John");

string serialized = JsonSerializer.Serialize(obj, options); // {"LastName":"Doe","Name":"John"}

static void ModifyTypeInfo(JsonTypeInfo ti)
{
    if (ti.Type != typeof(MyClass))
        return;

    JsonPropertyInfo property = ti.CreateJsonPropertyInfo(typeof(string), "Name");
    property.Get = (obj) =>
    {
        MyClass myClass = (MyClass)obj;
        return myClass.GetName();
    };

    property.Set = (obj, val) =>
    {
        MyClass myClass = (MyClass)obj;
        string value = (string)val;
        myClass.SetName(value);
    };

    ti.Properties.Add(property);
}

Conditional serialization of properties

In some usage scenarios, certain default values are required to not be serialized. For example sometimes you don't want 0 to appear in some properties in JSON. This can be done before by combining JsonIgnoreAttribute with JsonIgnoreCondition.WhenWritingDefault
used together to make this scene work. But the problem arises when your default value is non-0 like -1 or when your default value depends on external settings.

Now you can set your own predicate ShouldSerialize with whatever condition you want. For example you set a string property and you want N/A not to appear in JSON:

 // 您要自定义的字符串属性
JsonPropertyInfo property = ...;

property.ShouldSerialize = (obj, val) =>
{
// 在这个特定的例子中,我们不使用 parent 但如果需要它是可用的
MyClass parentObj = (MyClass)obj;
    string value = (string)val;
    return value != "N/A";
};

Example: ignore properties with a specific name or type

 var modifier = new IgnorePropertiesWithNameOrType();
modifier.IgnorePropertyWithType(typeof(SecretHolder));
modifier.IgnorePropertyWithName("IrrelevantDetail");
JsonSerializerOptions options = new()
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
    {
        Modifiers = { modifier.ModifyTypeInfo }
    }
};
ExampleClass obj = new()
{
    Name = "Test",
    Secret = new SecretHolder() { Value = "MySecret" },
    IrrelevantDetail = 15,
};
string output = JsonSerializer.Serialize(obj, options); // {"Name":"Test"}
class ExampleClass
{
    public string Name { get; set; }
    public SecretHolder Secret { get; set; }
    public int IrrelevantDetail { get; set; }
}
class SecretHolder
{
    public string Value { get; set; }
}
class IgnorePropertiesWithNameOrType
{
    private List<Type> _ignoredTypes = new List<Type>();
    private List<string> _ignoredNames = new List<string>();
    public void IgnorePropertyWithType(Type type)
    {
        _ignoredTypes.Add(type);
    }
    public void IgnorePropertyWithName(string name)
    {
        _ignoredNames.Add(name);
    }
    public void ModifyTypeInfo(JsonTypeInfo ti)
    {
        JsonPropertyInfo[] props = ti.Properties.Where((pi) => !_ignoredTypes.Contains(pi.PropertyType) && !_ignoredNames.Contains(pi.Name)).ToArray();
        ti.Properties.Clear();
        foreach (var pi in props)
        {
            ti.Properties.Add(pi);
        }
    }
}

System.Formats.Tar API Updates

In Preview 4, the System.Formats.Tar assembly was introduced. It provides APIs for manipulating TAR archives.

In Preview 6, some changes were made to cover some special cases:

Global extended attribute special class

The original design assumed that only a PAX TAR archive could contain a single Global Extended Attribute (GEA) entry in the first position, but it was discovered that a TAR archive can contain multiple GEA entries, which affects all subsequent entries until a new GEA entry is encountered or end of archive.

It was also found that GEA entries should not only appear in archives containing only PAX entries: they can appear in archives that mix entries of different formats. So a new class was added to describe GEA entries:

 + public sealed partial class PaxGlobalExtendedAttributesTarEntry : PosixTarEntry
+ {
+     public PaxGlobalExtendedAttributesTarEntry(IEnumerable<KeyValuePair<string, string>> globalExtendedAttributes) { }
+     public IReadOnlyDictionary<string, string> GlobalExtendedAttributes { get { throw null; } }
+ }

Entry format, not archive format

Since it was also found that entries of different formats can be mixed in one TAR archive, the TarFormat enumeration was renamed to TarEntryFormat:

 -public enum TarFormat
+public enum TarEntryFormat
{
    ...
}

And added a new property to TarEntry to expose the format of the entry:

 public abstract partial class TarEntry
{
    ...
+    public TarEntryFormat Format { get { throw null; } }
    ...
}

Changes in writing and reading

The Format property has been removed from TarReader because it is not desirable for any archive to have all entries in a single format.

Since GEA entries are now described using their own dedicated class, and multiple entries of this type can be found in a single archive, the TarReader's dictionary property has also been removed:

 public sealed partial class TarReader : IDisposable
{
    ...
-    public TarFormat Format { get { throw null; } }
-    public IReadOnlyDictionary<string, string>? GlobalExtendedAttributes { get { throw null; } }
    ...
}

The addition of specialized GEA classes also has an impact on TarWriter:

  • Removed the constructor that used to get a dictionary of a single leading GEA entry.
  • Added a new constructor that only accepts stream and leaveOpen booleans.
  • The constructor that takes TarFormat is kept, but the enumeration is renamed and the default value is set to Pax. The documentation for this method has been changed to explain that the specified format parameter applies only to the TarWriter.WriteEntry method that adds entries from a file.
 public sealed partial class TarWriter : IDisposable
{
    ...
-    public TarWriter(Stream archiveStream, IEnumerable<KeyValuePair<string, string>>? globalExtendedAttributes = null, bool leaveOpen = false) { }
+    public TarWriter(Stream archiveStream, bool leaveOpen = false) { }
-    public TarWriter(Stream archiveStream, TarFormat archiveFormat, bool leaveOpen = false) { }
+    public TarWriter(Stream archiveStream, TarEntryFormat format = TarEntryFormat.Pax, bool leaveOpen = false) { }
     public void WriteEntry(string fileName, string? entryName) { }
    ...
}

Template creation

constraint

Preview 6 introduced the concept of constraints to .NET templates. Constraints allow you to define the context in which templates are allowed - this helps the template engine determine which templates it should display in commands such as dotnet new list. For this release, we've added support for three types of constraints:

  • Operating System - Restrict templates based on the user's operating system
  • Template engine host - which restricts templates based on which host executes the template engine - this is usually the .NET CLI itself, or embedded scenarios like the new project dialog in Visual Studio/Visual Studio for Mac.
  • Installed Workloads – Requires the specified .NET SDK workload to be installed before the template is available

In all cases, describing these constraints is as simple as adding a new constraints section to the template's configuration file:

 "constraints": {
       "web-assembly": {
           "type": "workload",
           "args": "wasm-tools"
       },
}

These templates can be named, and we'll use that name when informing users why they can't call your template.

Currently, the .NET CLI supports these constraints, and we're working with partners on the Visual Studio team to incorporate them into projects and project creation experiences that you already know.

We hope this feature will lead to a more consistent experience for SDK users regardless of their editor of choice, make it easier to guide users through the necessary template prerequisites, and help us organize template lists for common scenarios, such as the dotnet new list. In a future preview of .NET 7, we plan to add support for generic MSBuild property based constraints!

See the constraints documentation for more examples, and join the discussion in the template engine repository for a discussion of the new type of constraints.

Multiple choice parameters

Preview 6 also adds a new feature to the choice parameter - users can specify multiple values in a single choice. This can be used like a Flags-style enumeration. Common examples of such parameters might be:

  • Choose from multiple forms of authentication on web templates
  • Select multiple target platforms (ios, android, web) at once in maui template

Opting in to this behavior is like adding "allowMultipleValues" to the parameter definition in the template configuration:
as simple as true. Once done, you'll have access to a number of helper functions for template content to help detect specific values selected by the user.

For a full description of this feature, see the Multiselect Parameters documentation .

Exit code unification and reporting

Preview 6 also unifies the exit codes reported by the template engine. This should help users who rely on scripts in their shell of choice have a more consistent error handling experience. Additionally, errors reported by the .NET CLI now include a link to find detailed information about each exit code:

 ➜ dotnet new unknown-template
No templates found matching: 'unknown-template'.

To list installed templates, run:
   dotnet new list
To search for the templates on NuGet.org, run:
   dotnet new search unknown-template

For details on the exit code, refer to https://aka.ms/templating-exit-codes#103

CodeGen

Dynamic PGO

  • https://github.com/dotnet/runtime/pull/68703 Added support for protected devirtualization of delegated calls. When dynamic PGO is enabled, it allows the JIT to specialize and inline delegate calls when the JIT determines that this may be profitable. This can greatly improve performance, as shown in the microbenchmark below, where dynamic PGO is now about 5x faster than without PGO (previously it was about 2.5x).

Currently only delegates bound to instance methods are supported. We expect support for static methods to appear in an early preview of .NET 8.

 public class Benchmark
{
    private readonly long[] _nums;
    public Benchmark()
    {
        _nums = Enumerable.Range(0, 100000).Select(i => (long)i).ToArray();
    }

    [Benchmark]
    public long Sum() => _nums.Sum(l => l * l);
}

image.png

  • We started implementing hot and cold separation, https://github.com/dotnet/runtime/pull/69763 is the first part of it.
    Hot/cold separation on ARM64 has been implemented in JIT (PR) . This work mainly consists of generating long pseudo-instructions for branching between hot/cold sections, and loading constants from the data section.

We also added support for hot/cold splitting of functions with exception handling ( PR ). If there is no PGO data, our heuristic will move all exception handling functions to the cold segment and copy "finally" blocks to the hot segment; we are operating under the assumption that exceptions are rare, but whether or not there are exceptions , will execute the finally block.

When running various SuperPMI collections, the JIT splits about 14% of low-end functions (without PGO data) and about 26% of high-end functions (with PGO data). See more metrics here .

Arm64

Loop optimization

  • Cyclic clone driven by type test:
    https://github.com/dotnet/runtime/pull/70377 Enables loop clones based on loop-invariant type tests, such as those added by GDV. This effectively allows fast-path loops to lift type checking outside of the loop, thereby improving performance. E.g:

图片

General optimization

  • PR https://github.com/dotnet/runtime/pull/68874 improves the handling of vector constants in the JIT, including support for value numbering, constant propagation, and other optimizations already available for other constants.

Targeting .NET 7

To target .NET 7, you need to use the .NET 7 Target Framework Moniker (TFM) in your project file. E.g:

 <TargetFramework>net7.0</TargetFramework> 

Full suite of .NET 7 TFMs, including operation-specific TFMs.

  • net7.0
  • net7.0-android
  • net7.0-ios
  • net7.0-maccatalyst
  • net7.0-macos
  • net7.0-tvos
  • net7.0-windows

We hope that upgrading from .NET 6 to .NET 7 should be simple. Please report any breaking changes you find while testing your existing applications with .NET 7.

support

.NET 7 is a Short Term Support (STS) release, which means it will receive free support and patches for 18 months from the release date. It is important to note that all versions are of the same quality. The only difference is the length of the supports. For more information on the .NET support policy, see the .NET and .NET Core Official Support Policy .

We recently changed the "Current" name to "Short Term Support (STS)". We are rolling out this change .

major changes

You can find the latest list of .NET 7 breaking changes by reading the Breaking Changes in .NET 7 document. It lists breaking changes by region and version, with links to detailed descriptions.

To see what breaking changes have been proposed but are still under review, follow the Proposed .NET Breaking Changes GitHub issue .

route map

.NET releases include products, libraries, runtimes, and tools, and represent collaboration between multiple teams inside and outside Microsoft. You can learn more about these areas by reading the product roadmap:

Finish

We thank you for all your support and contributions to .NET. Please try .NET 7 Preview 6 and let us know what you think!

图片
Long press to identify the QR code and follow Microsoft China MSDN

Click to download .NET 7 Preview 6 ~


微软技术栈
418 声望994 粉丝

微软技术生态官方平台。予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。