From b6f7c747e70c171c64f6f86021a5895eca9670d4 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 17 May 2023 03:37:18 -0400 Subject: [PATCH] InProcessNoEmitToolchain changes: - Overhead matches workload return type. - Use Consumer instead of writing to generic field. - Added support for pointer, ByRef, and ValueTask returns. --- .../Extensions/ReflectionExtensions.cs | 8 +- .../InProcess/NoEmit/BenchmarkAction.cs | 8 +- .../NoEmit/BenchmarkActionFactory.cs | 42 +++- .../BenchmarkActionFactory_Implementations.cs | 229 ++++++++++++++++-- .../InProcessTest.cs | 111 ++++++--- 5 files changed, 325 insertions(+), 73 deletions(-) diff --git a/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs b/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs index b38691f2c9..be926645cf 100644 --- a/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs +++ b/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs @@ -185,9 +185,7 @@ internal static bool IsStackOnlyWithImplicitCast(this Type argumentType, object? if (argumentInstance == null) return false; - // IsByRefLikeAttribute is not exposed for older runtimes, so we need to check it in an ugly way ;) - bool isByRefLike = argumentType.GetCustomAttributes().Any(attribute => attribute.ToString()?.Contains("IsByRefLike") ?? false); - if (!isByRefLike) + if (!argumentType.IsByRefLike()) return false; var instanceType = argumentInstance.GetType(); @@ -209,5 +207,9 @@ private static bool IsRunnableGenericType(TypeInfo typeInfo) && typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0); // we need public parameterless ctor to create it internal static bool IsLinqPad(this Assembly assembly) => assembly.FullName.IndexOf("LINQPAD", StringComparison.OrdinalIgnoreCase) >= 0; + + internal static bool IsByRefLike(this Type type) + // Type.IsByRefLike is not available in netstandard2.0. + => type.IsValueType && type.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute"); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs index df1911d0b0..02cc14d5e8 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs @@ -1,11 +1,8 @@ using System; -using JetBrains.Annotations; - namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit { /// Common API to run the Setup/Clean/Idle/Run methods - [PublicAPI] public abstract class BenchmarkAction { /// Gets or sets invoke single callback. @@ -16,8 +13,7 @@ public abstract class BenchmarkAction /// Invoke multiple times callback. public Action InvokeMultiple { get; protected set; } - /// Gets the last run result. - /// The last run result. - public virtual object LastRunResult => null; + [Obsolete("The result is no longer stored past the iteration.", true)] + public object LastRunResult => null; } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs index 6b0f2468d8..ca40d00807 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -6,8 +7,6 @@ using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Running; -using JetBrains.Annotations; - namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit { /// Helper class that creates instances. @@ -29,9 +28,40 @@ private static BenchmarkAction CreateCore( if (resultType == typeof(void)) return new BenchmarkActionVoid(resultInstance, targetMethod, unrollFactor); + if (resultType == typeof(void*)) + return new BenchmarkActionVoidPointer(resultInstance, targetMethod, unrollFactor); + + if (resultType.IsPointer) + return Create( + typeof(BenchmarkActionPointer<>).MakeGenericType(resultType.GetElementType()), + resultInstance, + targetMethod, + unrollFactor); + + if (resultType.IsByRef) + { + var returnParameter = targetMethod?.ReturnParameter ?? fallbackIdleSignature.ReturnParameter; + // System.Runtime.CompilerServices.IsReadOnlyAttribute is part of .NET Standard 2.1, we can't use it here.. + if (returnParameter.GetCustomAttributes().Any(attribute => attribute.GetType().Name == "IsReadOnlyAttribute")) + return Create( + typeof(BenchmarkActionByRefReadonly<>).MakeGenericType(resultType.GetElementType()), + resultInstance, + targetMethod, + unrollFactor); + + return Create( + typeof(BenchmarkActionByRef<>).MakeGenericType(resultType.GetElementType()), + resultInstance, + targetMethod, + unrollFactor); + } + if (resultType == typeof(Task)) return new BenchmarkActionTask(resultInstance, targetMethod, unrollFactor); + if (resultType == typeof(ValueTask)) + return new BenchmarkActionValueTask(resultInstance, targetMethod, unrollFactor); + if (resultType.GetTypeInfo().IsGenericType) { var genericType = resultType.GetGenericTypeDefinition(); @@ -51,10 +81,6 @@ private static BenchmarkAction CreateCore( unrollFactor); } - if (targetMethod == null && resultType.GetTypeInfo().IsValueType) - // for Idle: we return int because creating bigger ValueType could take longer than benchmarked method itself. - resultType = typeof(int); - return Create( typeof(BenchmarkAction<>).MakeGenericType(resultType), resultInstance, @@ -88,6 +114,10 @@ private static void PrepareInstanceAndResultType( if (isUsingAsyncKeyword) throw new NotSupportedException("Async void is not supported by design."); } + else if (resultType.IsByRefLike()) + { + throw new NotSupportedException("InProcessNoEmitToolchain does not support consuming ByRefLike return types."); + } } /// Helper to enforce .ctor signature. diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Implementations.cs index eef3ce8997..6f7ec8eb67 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Implementations.cs @@ -1,5 +1,7 @@ -using System; +using BenchmarkDotNet.Engines; +using System; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit @@ -34,7 +36,136 @@ private void OverheadInstance() { } private void InvokeMultipleHardcoded(long repeatCount) { for (long i = 0; i < repeatCount; i++) + { unrolledCallback(); + } + } + } + + internal unsafe class BenchmarkActionVoidPointer : BenchmarkActionBase + { + private delegate void* PointerFunc(); + + private readonly PointerFunc callback; + private readonly PointerFunc unrolledCallback; + private readonly Consumer consumer = new (); + + public BenchmarkActionVoidPointer(object instance, MethodInfo method, int unrollFactor) + { + callback = CreateWorkloadOrOverhead(instance, method, OverheadStatic, OverheadInstance); + InvokeSingle = InvokeSingleHardcoded; + + unrolledCallback = Unroll(callback, unrollFactor); + InvokeMultiple = InvokeMultipleHardcoded; + } + + private static void* OverheadStatic() => default; + private void* OverheadInstance() => default; + + private void InvokeSingleHardcoded() => consumer.Consume(callback()); + + private void InvokeMultipleHardcoded(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + consumer.Consume(unrolledCallback()); + } + } + } + + internal unsafe class BenchmarkActionPointer : BenchmarkActionBase + where T : unmanaged + { + private delegate T* PointerFunc(); + + private readonly PointerFunc callback; + private readonly PointerFunc unrolledCallback; + private readonly Consumer consumer = new (); + + public BenchmarkActionPointer(object instance, MethodInfo method, int unrollFactor) + { + callback = CreateWorkloadOrOverhead(instance, method, OverheadStatic, OverheadInstance); + InvokeSingle = InvokeSingleHardcoded; + + unrolledCallback = Unroll(callback, unrollFactor); + InvokeMultiple = InvokeMultipleHardcoded; + } + + private static T* OverheadStatic() => default; + private T* OverheadInstance() => default; + + private void InvokeSingleHardcoded() => consumer.Consume(callback()); + + private void InvokeMultipleHardcoded(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + consumer.Consume(unrolledCallback()); + } + } + } + + internal unsafe class BenchmarkActionByRef : BenchmarkActionBase + { + private delegate ref T ByRefFunc(); + + private readonly ByRefFunc callback; + private readonly ByRefFunc unrolledCallback; + private readonly Consumer consumer = new (); + private static T overheadDefaultValueHolder; + + public BenchmarkActionByRef(object instance, MethodInfo method, int unrollFactor) + { + callback = CreateWorkloadOrOverhead(instance, method, OverheadStatic, OverheadInstance); + InvokeSingle = InvokeSingleHardcoded; + + unrolledCallback = Unroll(callback, unrollFactor); + InvokeMultiple = InvokeMultipleHardcoded; + } + + private static ref T OverheadStatic() => ref overheadDefaultValueHolder; + private ref T OverheadInstance() => ref overheadDefaultValueHolder; + + private void InvokeSingleHardcoded() => consumer.Consume(callback()); + + private void InvokeMultipleHardcoded(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + consumer.Consume(unrolledCallback()); + } + } + } + + internal unsafe class BenchmarkActionByRefReadonly : BenchmarkActionBase + { + private delegate ref readonly T ByRefReadonlyFunc(); + + private readonly ByRefReadonlyFunc callback; + private readonly ByRefReadonlyFunc unrolledCallback; + private readonly Consumer consumer = new (); + private static T overheadDefaultValueHolder; + + public BenchmarkActionByRefReadonly(object instance, MethodInfo method, int unrollFactor) + { + callback = CreateWorkloadOrOverhead(instance, method, OverheadStatic, OverheadInstance); + InvokeSingle = InvokeSingleHardcoded; + + unrolledCallback = Unroll(callback, unrollFactor); + InvokeMultiple = InvokeMultipleHardcoded; + } + + private static ref readonly T OverheadStatic() => ref overheadDefaultValueHolder; + private ref readonly T OverheadInstance() => ref overheadDefaultValueHolder; + + private void InvokeSingleHardcoded() => consumer.Consume(callback()); + + private void InvokeMultipleHardcoded(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + consumer.Consume(unrolledCallback()); + } } } @@ -42,7 +173,7 @@ internal class BenchmarkAction : BenchmarkActionBase { private readonly Func callback; private readonly Func unrolledCallback; - private T result; + private readonly Consumer consumer = new (); public BenchmarkAction(object instance, MethodInfo method, int unrollFactor) { @@ -53,18 +184,27 @@ public BenchmarkAction(object instance, MethodInfo method, int unrollFactor) InvokeMultiple = InvokeMultipleHardcoded; } - private static T OverheadStatic() => default; - private T OverheadInstance() => default; + private static T OverheadStatic() + { + Unsafe.SkipInit(out T value); + return value; + } + + private T OverheadInstance() + { + Unsafe.SkipInit(out T value); + return value; + } - private void InvokeSingleHardcoded() => result = callback(); + private void InvokeSingleHardcoded() => consumer.Consume(callback()); private void InvokeMultipleHardcoded(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + { + consumer.Consume(unrolledCallback()); + } } - - public override object LastRunResult => result; } internal class BenchmarkActionTask : BenchmarkActionBase @@ -102,7 +242,9 @@ private void Overhead() { } private void InvokeMultipleHardcoded(long repeatCount) { for (long i = 0; i < repeatCount; i++) + { unrolledCallback(); + } } } @@ -111,7 +253,7 @@ internal class BenchmarkActionTask : BenchmarkActionBase private readonly Func> startTaskCallback; private readonly Func callback; private readonly Func unrolledCallback; - private T result; + private readonly Consumer consumer = new (); public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) { @@ -132,20 +274,65 @@ public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) InvokeMultiple = InvokeMultipleHardcoded; } - private T Overhead() => default; + private T Overhead() + { + Unsafe.SkipInit(out T value); + return value; + } // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate private T ExecuteBlocking() => startTaskCallback().GetAwaiter().GetResult(); - private void InvokeSingleHardcoded() => result = callback(); + private void InvokeSingleHardcoded() => consumer.Consume(callback()); private void InvokeMultipleHardcoded(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + { + consumer.Consume(unrolledCallback()); + } } + } + + internal class BenchmarkActionValueTask : BenchmarkActionBase + { + private readonly Func startTaskCallback; + private readonly Action callback; + private readonly Action unrolledCallback; - public override object LastRunResult => result; + public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFactor) + { + bool isIdle = method == null; + if (!isIdle) + { + startTaskCallback = CreateWorkload>(instance, method); + callback = ExecuteBlocking; + } + else + { + callback = Overhead; + } + + InvokeSingle = callback; + + unrolledCallback = Unroll(callback, unrollFactor); + InvokeMultiple = InvokeMultipleHardcoded; + + } + + // must be kept in sync with VoidDeclarationsProvider.IdleImplementation + private void Overhead() { } + + // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate + private void ExecuteBlocking() => startTaskCallback.Invoke().GetAwaiter().GetResult(); + + private void InvokeMultipleHardcoded(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + { + unrolledCallback(); + } + } } internal class BenchmarkActionValueTask : BenchmarkActionBase @@ -153,7 +340,7 @@ internal class BenchmarkActionValueTask : BenchmarkActionBase private readonly Func> startTaskCallback; private readonly Func callback; private readonly Func unrolledCallback; - private T result; + private readonly Consumer consumer = new (); public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFactor) { @@ -175,20 +362,24 @@ public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFa InvokeMultiple = InvokeMultipleHardcoded; } - private T Overhead() => default; + private T Overhead() + { + Unsafe.SkipInit(out T value); + return value; + } // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate private T ExecuteBlocking() => startTaskCallback().GetAwaiter().GetResult(); - private void InvokeSingleHardcoded() => result = callback(); + private void InvokeSingleHardcoded() => consumer.Consume(callback()); private void InvokeMultipleHardcoded(long repeatCount) { for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + { + consumer.Consume(unrolledCallback()); + } } - - public override object LastRunResult => result; } } } \ No newline at end of file diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs index a06870ad5e..238d4f7dd8 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs @@ -43,19 +43,35 @@ public InProcessTest(ITestOutputHelper output) : base(output) public void BenchmarkActionVoidSupported() => TestInvoke(x => x.InvokeOnceVoid(), UnrollFactor); [Fact] - public void BenchmarkActionTaskSupported() => TestInvoke(x => x.InvokeOnceTaskAsync(), UnrollFactor, null); + public void BenchmarkActionTaskSupported() => TestInvoke(x => x.InvokeOnceTaskAsync(), UnrollFactor); [Fact] - public void BenchmarkActionRefTypeSupported() => TestInvoke(x => x.InvokeOnceRefType(), UnrollFactor, StringResult); + public void BenchmarkActionValueTaskSupported() => TestInvoke(x => x.InvokeOnceValueTaskAsync(), UnrollFactor); [Fact] - public void BenchmarkActionValueTypeSupported() => TestInvoke(x => x.InvokeOnceValueType(), UnrollFactor, DecimalResult); + public void BenchmarkActionRefTypeSupported() => TestInvoke(x => x.InvokeOnceRefType(), UnrollFactor); [Fact] - public void BenchmarkActionTaskOfTSupported() => TestInvoke(x => x.InvokeOnceTaskOfTAsync(), UnrollFactor, StringResult); + public void BenchmarkActionValueTypeSupported() => TestInvoke(x => x.InvokeOnceValueType(), UnrollFactor); [Fact] - public void BenchmarkActionValueTaskOfTSupported() => TestInvoke(x => x.InvokeOnceValueTaskOfT(), UnrollFactor, DecimalResult); + public void BenchmarkActionTaskOfTSupported() => TestInvoke(x => x.InvokeOnceTaskOfTAsync(), UnrollFactor); + + [Fact] + public void BenchmarkActionValueTaskOfTSupported() => TestInvoke(x => x.InvokeOnceValueTaskOfT(), UnrollFactor); + + [Fact] + public unsafe void BenchmarkActionStructPointerSupported() => TestInvoke(x => x.InvokeOnceStructPointerType(), UnrollFactor); + + [Fact] + public unsafe void BenchmarkActionVoidPointerSupported() => TestInvoke(x => x.InvokeOnceVoidPointerType(), UnrollFactor); + + // Can't use ref returns in expression, so pass the MethodInfo directly instead. + [Fact] + public void BenchmarkActionByRefTypeSupported() => TestInvoke(typeof(BenchmarkAllCases).GetMethod(nameof(BenchmarkAllCases.InvokeOnceByRefType)), UnrollFactor); + + [Fact] + public void BenchmarkActionByRefReadonlyValueTypeSupported() => TestInvoke(typeof(BenchmarkAllCases).GetMethod(nameof(BenchmarkAllCases.InvokeOnceByRefReadonlyType)), UnrollFactor); [Fact] public void BenchmarkDifferentPlatformReturnsValidationError() @@ -83,71 +99,55 @@ private void TestInvoke(Expression> methodCall, int un // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), unrollFactor); - TestInvoke(action, unrollFactor, false, null); + TestInvoke(action, unrollFactor, false); // Idle mode action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkAllCases(), unrollFactor); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true); // GlobalSetup/GlobalCleanup action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, 1, false, null); + TestInvoke(action, 1, false); action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, 1, false, null); + TestInvoke(action, 1, false); // GlobalSetup/GlobalCleanup (empty) descriptor = new Descriptor(typeof(BenchmarkAllCases), targetMethod); action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true); action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true); // Dummy (just in case something may broke) action = BenchmarkActionFactory.CreateDummy(); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true); action = BenchmarkActionFactory.CreateDummy(); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true); } [AssertionMethod] - private void TestInvoke(Expression> methodCall, int unrollFactor, object expectedResult) + private void TestInvoke(MethodInfo targetMethod, int unrollFactor) { - var targetMethod = ((MethodCallExpression)methodCall.Body).Method; var descriptor = new Descriptor(typeof(BenchmarkAllCases), targetMethod); // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), unrollFactor); - TestInvoke(action, unrollFactor, false, expectedResult); + TestInvoke(action, unrollFactor, false); // Idle mode - - bool isValueTask = typeof(T).IsConstructedGenericType && typeof(T).GetGenericTypeDefinition() == typeof(ValueTask<>); - - object idleExpected; - if (isValueTask) - idleExpected = GetDefault(typeof(T).GetGenericArguments()[0]); - else if (typeof(T).GetTypeInfo().IsValueType) - idleExpected = 0; - else if (expectedResult == null || typeof(T) == typeof(Task)) - idleExpected = null; - else - idleExpected = GetDefault(expectedResult.GetType()); - action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkAllCases(), unrollFactor); - TestInvoke(action, unrollFactor, true, idleExpected); + TestInvoke(action, unrollFactor, true); } - private static object GetDefault(Type type) + [AssertionMethod] + private void TestInvoke(Expression> methodCall, int unrollFactor) { - if (type.GetTypeInfo().IsValueType) - { - return Activator.CreateInstance(type); - } - return null; + var targetMethod = ((MethodCallExpression)methodCall.Body).Method; + TestInvoke(targetMethod, unrollFactor); } [AssertionMethod] - private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool isIdle, object expectedResult) + private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool isIdle) { try { @@ -171,8 +171,6 @@ private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool benchmarkAction.InvokeMultiple(11); Assert.Equal(BenchmarkAllCases.Counter, 1 + unrollFactor * 11); } - - Assert.Equal(benchmarkAction.LastRunResult, expectedResult); } finally { @@ -244,6 +242,13 @@ public async Task InvokeOnceTaskAsync() Interlocked.Increment(ref Counter); } + [Benchmark] + public async ValueTask InvokeOnceValueTaskAsync() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + [Benchmark] public string InvokeOnceRefType() { @@ -272,6 +277,34 @@ public ValueTask InvokeOnceValueTaskOfT() Interlocked.Increment(ref Counter); return new ValueTask(DecimalResult); } + + [Benchmark] + public ref int InvokeOnceByRefType() + { + Interlocked.Increment(ref Counter); + return ref Counter; + } + + [Benchmark] + public ref readonly int InvokeOnceByRefReadonlyType() + { + Interlocked.Increment(ref Counter); + return ref Counter; + } + + [Benchmark] + public unsafe decimal* InvokeOnceStructPointerType() + { + Interlocked.Increment(ref Counter); + return default; + } + + [Benchmark] + public unsafe void* InvokeOnceVoidPointerType() + { + Interlocked.Increment(ref Counter); + return default; + } } } } \ No newline at end of file