Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What's new in .NET 7 Preview 6 [WIP] #7454

Closed
leecow opened this issue May 11, 2022 · 5 comments
Closed

What's new in .NET 7 Preview 6 [WIP] #7454

leecow opened this issue May 11, 2022 · 5 comments

Comments

@leecow
Copy link
Member

leecow commented May 11, 2022

What's new in .NET 7 Preview 6

This issue is for teams to highlight work for the community that will release .NET 7 Preview 6

To add content, use a new conversation entry. The entry should include the team name and feature title as the first line as shown in the template below.

## Team Name: Feature title

[link to the tracking issue or epic item for the work]

Tell the story of the feature and anything the community should pay particular attention 
to be successful using the feature.

Preview 1: #7106
Preview 2: #7107
Preview 3: #7108
Preview 4: #7378
Preview 5: #7441
Preview 6: #7454
Preview 7: #7455

@tarekgh
Copy link
Member

tarekgh commented Jun 2, 2022

Type Converters

Exposing type converters for the newly added primitive types DateOnly, TimeOnly, Int128, UInt128, and Half

dotnet/runtime#68743

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() { }
    }
}

Usage Example

            TypeConverter dateOnlyConverter = TypeDescriptor.GetConverter(typeof(DateOnly));
            // produce DateOnly value of DateOnly(1940, 10, 9)
            DateOnly? date = dateOnlyConverter.ConvertFromString("1940-10-09") as DateOnly?;

            TypeConverter timeOnlyConverter = TypeDescriptor.GetConverter(typeof(TimeOnly));
            // produce TimeOnly value of TimeOnly(20, 30, 50)
            TimeOnly? time = timeOnlyConverter.ConvertFromString("20:30:50") as TimeOnly?;

            TypeConverter halfConverter = TypeDescriptor.GetConverter(typeof(Half));
            // produce Half value of -1.2
            Half? half = halfConverter.ConvertFromString(((Half)(-1.2)).ToString()) as Half?;

            TypeConverter Int128Converter = TypeDescriptor.GetConverter(typeof(Int128));
            // produce Int128 value of Int128.MaxValue which equal 170141183460469231731687303715884105727
            Int128? int128 = Int128Converter.ConvertFromString("170141183460469231731687303715884105727") as Int128?;

            TypeConverter UInt128Converter = TypeDescriptor.GetConverter(typeof(UInt128));
            // produce UInt128 value of UInt128.MaxValue which equal 340282366920938463463374607431768211455
            UInt128? uint128 = UInt128Converter.ConvertFromString("340282366920938463463374607431768211455") as UInt128?;

@krwq
Copy link
Member

krwq commented Jun 29, 2022

JSON contract customization

In certain situations developers serializing or deserializing JSON find that they don't want to or cannot change types because they i.e. come from external library or it would greatly pollute the code but need to make some changes which influence serialization like removing property, changing how numbers get serialized, how object is created etc. They frequently are forced to either write wrappers or custom converters which are not only a hassle but also make serialization slower.

JSON contract customization allows user for more control over what and how types get serialized or deserialized.

Opting into customization

There are two basic ways developers can "plug" into the customization, they both end up assigning JsonSerializerOptions.TypeInfoResolver and require assigning resolver:

  • developer can use DefaultJsonTypeInfoResolver and add their modifier, all modifiers will be called serially. Simply:
JsonSerializerOptions options = new()
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
    {
        Modifiers =
        {
            (JsonTypeInfo jsonTypeInfo) =>
            {
                // your modifications here, i.e.:
                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; }
}
  • writing own custom resolver by implementing System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver - when type is not handled code should return null - IJsonTypeInfoResolver can be combined with others into effective resolver which will return first non-null answer. For example JsonTypeInfoResolver.Combine(new MyResolver(), new DefaultJsonTypeInfoResolver())

Customizations

IJsonTypeInfoResolver job is to provide JsonTypeInfo for any Type serializer requests - this will only happen once per type per options.
JsonTypeInfo.Kind will determine what knobs developer can change and is determined based on converter which is determined based on converters provided to options. For example JsonTypeInfoKind.Object means Properties can be added/modified while JsonTypeInfoKind.None means none of the knobs is guaranteed to be used - that can happen when type has custom converter.

JsonTypeInfo is either created by DefaultJsonTypeInfoResolver with pre-populated knobs coming from i.e. custom attributes or can be created from scratch by user: JsonTypeInfo.CreateJsonTypeInfo - creating from scratch means user will also need to set JsonTypeInfo.CreateObject.

Customizing properties

Properties are only relevant when JsonTypeInfo.Kind == JsonTypeInfoKind.Object and in case of DefaultJsonTypeInfoResolver will be pre-populated.
They can be modified or created by using JsonTypeInfo.CreateJsonPropertyInfo and added to the list of properties, i.e. say you got a class from separate library which has weirdly designed APIs which 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'd need to wrap your type hierarchy or create your own custom converter for that 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 it's required that some default values don't get serialized. I.e. you don't want 0 to show up in JSON for certain properties. It was possible to get that scenario to work before by using JsonIgnoreAttribute with JsonIgnoreCondition.WhenWritingDefault.
The problem occurs when your default value is not 0 and it's something different, i.e. -1 or it depends on some external setting.

Now it's possible to set your own predicate ShouldSerialize with any condition you'd like. I.e. say you have string property and you'd want N/A to not show up in JSON:

// string property you'd like to customize
JsonPropertyInfo property = ...;

property.ShouldSerialize = (obj, val) =>
{
    // in this specific example we don't use parent but it's available if needed
    MyClass parentObj = (MyClass)obj;
    string value = (string)val;
    return value != "N/A";
};

Sample: Ignoring properties with 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);
        }
    }
}

@carlossanlop
Copy link
Member

carlossanlop commented Jul 6, 2022

System.Formats.Tar API updates

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

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

Global Extended Attributes specialized class

The initial design was assuming that only PAX TAR archives could contain a single Global Extended Attributes (GEA) entry in the first position, but it was discovered that TAR archives can contain multiple GEA entries, which can affect all subsequent entries until encountering a new GEA entry or the end of the archive.

It was also discovered that GEA entries should not be expected only in archives containing PAX entries exclusively: they can show up in archives that intermix entries of different formats. So a new class was added to describe a GEA entry:

+ 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 discovered that entries of different formats can be intermixed in a single TAR archive, the TarFormat enum was renamed to TarEntryFormat:

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

And a new property was added to TarEntry to expose the entry's format:

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

Writing and reading changes

The Format property was removed from TarReader because no archive is expected to have all its entries in a single format.

Since GEA entries are now being described with their own specialized class, and multiple entries of this type can be found in a single archive, the dictionary property from the TarReader was also removed:

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

The addition of the specialized GEA class also affected TarWriter:

  • The constructor that used to take the dictionary for a single first-position GEA entry was removed.
  • A new constructor that takes only the stream and the leaveOpen boolean was added.
  • The constructor that takes the TarFormat was kept, but the enum was renamed, and a default value was set to Pax. The method's documentation was changed to explain that the specified format parameter only applies to the TarWriter.WriteEntry method that adds an entry 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) { }
    ...
}

@JulieLeeMSFT
Copy link
Member

JulieLeeMSFT commented Jul 7, 2022

CodeGen

Community PRs (Many thanks to JIT community contributors!)

Dynamic PGO

  • Add support for delegate GDV and method-based vtable GDV runtime#68703 adds support for guarded devirtualization for delegate calls. When dynamic PGO is enabled this allows the JIT to specialize and inline delegate calls when it determines this might be profitable. This can greatly increase performance as demonstrated by the following microbenchmark where dynamic PGO is now roughly 5x faster (was roughly 2.5x before) than no PGO.

For now only delegates bound to instance methods are supported. We expect that support for static methods will come in early previews for .NET 8.

image

(note to editors: the screenshot is taken from the link above, please convert to appropriate code blocks/tables in the final blog post)

  • We started to implement hot and cold splitting and Implement fake hot/cold splitting and corresponding stress mode runtime#69763 is the first part of it.
    • Hot/Cold splitting on ARM64 has been implemented in the JIT (PR). This work largely consisted of generating long pseudo-instructions for branching between hot/cold sections, and loading constants from the data section.
    • We've also added support for hot/cold splitting of functions with exception handling (PR). Without PGO data, our heuristic moves all exception handling funclets to the cold section, and copies “finally” blocks to the hot section; we’re operating under the assumption that exceptions occur rarely, but finally blocks are executed regardless of the presence of exceptions.
    • When running various SuperPMI collections, the JIT split ~14% of functions on the low end (no PGO data), and ~26% of functions on the high end (with PGO data). See more metrics here.

Arm64

image

Loop Optimizations

image

image

General Optimizations

@leecow
Copy link
Member Author

leecow commented Jan 24, 2023

.NET 7 GA is available. Closing these pre-release issues.

@leecow leecow closed this as completed Jan 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants