Particular / NServiceBus

Build, version, and monitor better microservices with the most powerful service platform for .NET
https://particular.net/nservicebus/
Other
2.07k stars 650 forks source link

Robust and faster handling of AssemblyQualifiedNames in MessageMetadataRegistry #7086

Closed danielmarbach closed 2 weeks ago

danielmarbach commented 3 weeks ago

The current fallback logic to extract the fullname bluntly assumes it is OK to split by , and then take the first part. While this works in some cases it fails with more advanced types like generics. We are aware that generic are not officially supported, but regardless of that, the parsing logic should treat the assembly qualified names properly and not lead to invalid data types.

Instead of string splitting or trying to do a fancy regular expression match and replace (with all the consequences of those potentially timing out etc.), I came up with a straightforward approach that should be robust and reliable (see test cases) and even in the cases when we get a full name already even faster since we are not attempting to split that also involves an unnecessary array allocation.

Benchmark

image

```csharp using System; using System.Collections.Generic; using BenchmarkDotNet.Attributes; namespace MicroBenchmarks.NServiceBus; [SimpleJob] [MemoryDiagnoser] public class AssemblyQualifiedNameParser { [Benchmark(Baseline = true)] [ArgumentsSource(nameof(Arguments))] public string Before(string messageIdentifier) => GetMessageTypeNameWithoutAssemblyOld(messageIdentifier); [Benchmark] [ArgumentsSource(nameof(Arguments))] public string After(string messageIdentifier) => GetMessageTypeNameWithoutAssembly(messageIdentifier); public IEnumerable Arguments() { yield return [typeof(ICommand).FullName]; yield return [typeof(ICommand).AssemblyQualifiedName]; yield return [typeof(MyEvent).FullName]; yield return [typeof(MyEvent).AssemblyQualifiedName]; yield return [typeof(MyEventGeneric<>).AssemblyQualifiedName]; yield return [typeof(MyEventGeneric).AssemblyQualifiedName]; yield return [typeof(MyEventGeneric>).AssemblyQualifiedName]; } public static string GetMessageTypeNameWithoutAssembly(string messageTypeIdentifier) { int lastIndexOf = messageTypeIdentifier.LastIndexOf(']'); if (lastIndexOf > 0) { var messageType = messageTypeIdentifier.AsSpan()[..++lastIndexOf].ToString(); return messageType; } int firstIndexOfComma = messageTypeIdentifier.IndexOf(','); if (firstIndexOfComma > 0) { var messageType = messageTypeIdentifier.AsSpan()[..firstIndexOfComma].ToString(); return messageType; } return messageTypeIdentifier; } public static string GetMessageTypeNameWithoutAssemblyOld(string messageTypeIdentifier) { var typeParts = messageTypeIdentifier.Split(','); if (typeParts.Length > 1) { return typeParts[0]; //Take the type part only } return messageTypeIdentifier; } class MyEvent { } class MyEventGeneric { public T Payload { get; set; } } } ```