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 9 Preview 1 #9089

Closed
JonDouglas opened this issue Jan 26, 2024 · 11 comments
Closed

What's new in .NET 9 Preview 1 #9089

JonDouglas opened this issue Jan 26, 2024 · 11 comments

Comments

@JonDouglas
Copy link
Collaborator

JonDouglas commented Jan 26, 2024

To add content, fill out the following template as a new comment on this issue. Last day to submit content is the Friday before release.

## Contribution Template - Use to your discretion

Feature Name: [Short title of the feature]

What It Does: [Brief description of the feature]

How It Helps: [Explain the benefit or problem it solves]

How to Use: [Simple instructions on using the feature]

Future Plans: [Any upcoming updates for this feature, if any]

Documentation: [A link for readers to learn more]

Index of .NET 9 releases

@eiriktsarpalis
Copy link
Member

System.Text.Json improvements

Customizing indent character and indent size

It is now possible to customize the indentation character and size of written JSON:

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    IndentCharacter = '\t',
    IndentSize = 2,
};

JsonSerializer.Serialize(new { Value = 1 }, options);
//{
//                "Value": 1
//}

JsonSerializerOptions.Web

We now include a JsonSerializerOptions singleton configured to use JsonSerializerDefaults.Web:

JsonSerializer.Serialize(new { SomeValue = 42 }, JsonSerializerOptions.Web); // {"someValue":42} defaults to camelCase naming policy

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Jan 26, 2024

System.Linq: CountBy, AggregateBy and Index methods

We've added new Linq methods that make it possible to aggregate state by key, without needing to allocate intermediate groupings via GroupBy. You can use CountBy to quickly calculate the frequency for each key:

string sourceText = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    Sed non risus. Suspendisse lectus tortor, dignissim sit amet, 
    adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
    """;

// Find the most frequent word in a piece of text
KeyValuePair<string, int> mostFrequentWord = sourceText
    .Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(word => word.ToLowerInvariant())
    .CountBy(word => word)
    .MaxBy(pair => pair.Value);

Console.WriteLine(mostFrequentWord.Key); // amet

More general-purpose workflows can be implemented using the new AggregateBy method, here's how we can calculate scores by a given key:

(string id, int score)[] data =
[
    ("0", 42),
    ("1", 5),
    ("2", 4),
    ("1", 10),
    ("0", 25),
];

data.AggregateBy(seed: 0, (totalScore, curr) => totalScore + curr.score, keySelector: entry => entry.id);
//(0, 67)
//(1, 15)
//(2, 4)

The new Index method makes it possible to quickly extract the implicit index of an enumerable. The code:

IEnumerable<string> lines = File.ReadAllLines("file.txt");
foreach ((string line, int index) in lines.Select((line, index) => (line, index))
{
    Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}

Now becomes

foreach ((string line, int index) in lines.Index())
{
    Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}

@eiriktsarpalis
Copy link
Member

PriorityQueue.Remove

Back in .NET 6 we shipped a PriorityQueue collection that provides a simple and fast array heap implementation. One issue however with array heaps in general is that they don't support priority updates, making them prohibitive for use in algorithms such as variations of Dijkstra's algorithm.

While it wouldn't be possible to implement efficient $\mathcal O(\log n)$ priority updates in the existing collection, the new Remove method makes it possible to emulate priority updates (albeit at $\mathcal O(n)$ time):

public static void UpdatePriority<TElement, TPriority>(this PriorityQueue<TElement, TPriority> queue, TElement element, TPriority priority)
{
    queue.Remove(element, out _); // Scan the heap for entries matching the current element
    queue.Enqueue(element, priority); // Now re-insert it with the new priority.
}

This unblocks users looking to implement graph algorithms in contexts where asymptotic performance isn't a blocker (e.g. education or prototyping). For example, here's a toy implementation of Dijkstra's algorithm using the new API.

@vcsjones
Copy link
Member

vcsjones commented Jan 30, 2024

System.Security.Cryptography

KMAC

.NET 9 now provides the KMAC algorithm, as specified by NIST SP-800-185. KMAC is a pseudo random function and keyed hash function based on Keccak.

The KMAC algorithm can be used with the Kmac128, Kmac256, KmacXof128, and KmacXof256 classes all in the System.Security.Cryptography namespace. Instances can be created to accumulate data to produce a MAC, or use the static method HashData for a one-shot over a single input.

KMAC is available on Linux with OpenSSL 3.0 or newer, and Windows 11 Build 26016 or newer. The static IsSupported property can be used to determine if the platform supports the desired algorithm.

using System.Security.Cryptography;

if (Kmac128.IsSupported) {
    byte[] key = GetKmacKey();
    byte[] input = GetInputToMac();
    byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
}
else {
    // Handle scenario where KMAC is not available.
}

CryptographicOperations.HashData

Over the past few releases of .NET, static one-shot implementations of hash functions, and related functions, has been implemented such as SHA256.HashData or HMACSHA256.HashData. One-shots are preferable to use because they can provide the best possible performance and reduce or eliminate allocations.

If a developer wishes to provide an API which supports hashing where the caller defines which hash algorithm to use, this is typically done by accepting a HashAlgorithmName as a parameter to the API. However, using this with one-shots would require switching over every possible HashAlgorithmName and using the appropriate method.

Starting in .NET 9, the CryptographicOperations.HashData API can be used to produce a hash or HMAC over an input as a one-shot where the algorithm used is determined by a HashAlgorithmName.

using System.Security.Cryptography;

static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data) {
    byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
    ProcessHash(hash);
}

@buyaa-n
Copy link
Contributor

buyaa-n commented Feb 6, 2024

System.Reflection.Emit

Feature Name: Support equivalent of AssemblyBuilder.Save

What It Does and How It Helps:

Reflection Emit and AssemblyBuilder have been in .NET for many years, but with the introduction of .NET Core and .NET 5+ support was limited to a runnable AssemblyBuilder. Adding support for saving an assembly has been asked since the first release of .NET Core and it's been the most upvoted issue in the Reflection area. Many customers report it as a blocker for porting their project form .NET Framework to .NET 6+.

Prototyping was done in .NET 7.0 by @MosheWolberg to investigate the feasibility of adding support for Save() via an adapter from AssemblyBuilder using the MetadataBuilder APIs that supports writing IL to a Stream\file.

In .NET 8 we added around half of the implementation. Now in .NET 9 preview 1 we are finishing the implementation of persisted AssemblyBuilder and adding public APIs for saving an assembly.

How to Use:

The new persisted AssemblyBuilder implementation is runtime/platform independent. For creating new persisted AssemblyBuilder instance you need to use a new API AssemblyBuilder.DefinePersistedAssembly. As the existing AssemblyBuilder.DefineDynamicAssembly API accepts AssemblyName instance as fist argument, and optional custom attributes as last argument. The difference is you need to pass the core assembly, System.Private.CoreLib, that would be used for referencing base runtime types, no option for AssemblyBuilderAccess, for now the new persisted AssemblyBuilder implementation only supports saving, not running. After creating an instance of the persisted AssemblyBuilder further steps for defining a module/type/method/enum etc., writing IL, and all other usages are kept the same as the existing runtime Reflection Emit. That means you should be able to use existing Emit code as is for saving the assembly. Here is a simple example:

public void CreateAndSaveAssembly(string assemblyPath)
{
    AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(new AssemblyName("MyAssembly"), typeof(object).Assembly);
    TypeBuilder tb = ab.DefineDynamicModule("MyModule").DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);

    MethodBuilder mb = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static,
        typeof(int), [typeof(int), typeof(int)]);
    ILGenerator il = mb.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    tb.CreateType();
    ab.Save(assemblyPath); // or could save to a Stream
}

public void UseAssembly(string assemblyPath)
{
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    Type type = assembly.GetType("MyType");
    MethodInfo method = type.GetMethod("SumMethod");
    Console.WriteLine(method.Invoke(null, [5, 10]));
}

Future Plans: A few missing API implementations and bunch of bug fixes will added in preview 2. Further Entry point support will be added soon, probably within preview 2.

CC @jkotas @AaronRobinsonMSFT @steveharter @ericstj @jeffhandley in case you want to add more

@gewarren
Copy link
Contributor

gewarren commented Feb 8, 2024

@eiriktsarpalis Do the new indentation options apply to Utf8JsonWriter as well?

@eiriktsarpalis
Copy link
Member

@gewarren it does, equivalent options are exposed in the JsonWriterOptions struct. I've omitted use of those here.

@co-nl-on
Copy link

co-nl-on commented Feb 13, 2024

data.AggregateBy(seed: 0, (totalScore, curr) => totalScore += curr.score, keySelector: entry => entry.id);
//(0, 67)
//(1, 15)
//(2, 4)

I believe the second argument should be (totalScore, curr) => totalScore + curr.score

@richlander
Copy link
Member

@Coding-Lambda
Copy link

Coding-Lambda commented Feb 14, 2024

Now becomes

foreach ((string line, int index) in lines.Index())
{
    Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}

Shouldn't string line and int index be swapped, according to this issue comment and the merged PR changes of that issue?

@eiriktsarpalis
Copy link
Member

@Coding-Lambda Thanks, the sample has been updated #9186

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

8 participants