diff --git a/src/SignalR/perf/Microbenchmarks/TypedClientBuilderBenchmark.cs b/src/SignalR/perf/Microbenchmarks/TypedClientBuilderBenchmark.cs new file mode 100644 index 000000000000..8a386fd22845 --- /dev/null +++ b/src/SignalR/perf/Microbenchmarks/TypedClientBuilderBenchmark.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.SignalR.Internal; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + public class TypedClientBuilderBenchmark + { + private static readonly IClientProxy Dummy = new DummyProxy(); + + [Benchmark] + public ITestClient Build() + { + return TypedClientBuilder.Build(Dummy); + } + + public interface ITestClient { } + + private class DummyProxy : IClientProxy + { + public Task SendCoreAsync(string method, object[] args, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + } + } +} diff --git a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs index a333c600a87c..da171d9a47cd 100644 --- a/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs +++ b/src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs @@ -20,6 +20,10 @@ internal static class TypedClientBuilder private static readonly PropertyInfo CancellationTokenNoneProperty = typeof(CancellationToken).GetProperty("None", BindingFlags.Public | BindingFlags.Static); + private static readonly ConstructorInfo ObjectConstructor = typeof(object).GetConstructors().Single(); + + private static readonly Type[] ParameterTypes = new Type[] { typeof(IClientProxy) }; + public static T Build(IClientProxy proxy) { return _builder.Value(proxy); @@ -40,20 +44,24 @@ private static Func GenerateClientBuilder() var moduleBuilder = assemblyBuilder.DefineDynamicModule(ClientModuleName); var clientType = GenerateInterfaceImplementation(moduleBuilder); - return proxy => (T)Activator.CreateInstance(clientType, proxy); + var factoryMethod = clientType.GetMethod(nameof(Build), BindingFlags.Public | BindingFlags.Static); + return (Func)factoryMethod.CreateDelegate(typeof(Func)); } private static Type GenerateInterfaceImplementation(ModuleBuilder moduleBuilder) { - var type = moduleBuilder.DefineType( - ClientModuleName + "." + typeof(T).Name + "Impl", - TypeAttributes.Public, - typeof(Object), - new[] { typeof(T) }); + var name = ClientModuleName + "." + typeof(T).Name + "Impl"; + + var type = moduleBuilder.DefineType(name, TypeAttributes.Public, typeof(object), new[] { typeof(T) }); + + var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private | FieldAttributes.InitOnly); - var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private); + var ctor = BuildConstructor(type, proxyField); - BuildConstructor(type, proxyField); + // Because a constructor doesn't return anything, it can't be wrapped in a + // delegate directly, so we emit a factory method that just takes the IClientProxy, + // invokes the constructor (using newobj) and returns the new instance of type T. + BuildFactoryMethod(type, ctor); foreach (var method in GetAllInterfaceMethods(typeof(T))) { @@ -79,27 +87,23 @@ private static IEnumerable GetAllInterfaceMethods(Type interfaceType } } - private static void BuildConstructor(TypeBuilder type, FieldInfo proxyField) + private static ConstructorInfo BuildConstructor(TypeBuilder type, FieldInfo proxyField) { - var method = type.DefineMethod(".ctor", System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.HideBySig); + var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ParameterTypes); - var ctor = typeof(object).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, new Type[] { }, null); - - method.SetReturnType(typeof(void)); - method.SetParameters(typeof(IClientProxy)); - - var generator = method.GetILGenerator(); + var generator = ctor.GetILGenerator(); // Call object constructor generator.Emit(OpCodes.Ldarg_0); - generator.Emit(OpCodes.Call, ctor); + generator.Emit(OpCodes.Call, ObjectConstructor); // Assign constructor argument to the proxyField generator.Emit(OpCodes.Ldarg_0); // type generator.Emit(OpCodes.Ldarg_1); // type proxyfield generator.Emit(OpCodes.Stfld, proxyField); // type.proxyField = proxyField generator.Emit(OpCodes.Ret); + + return ctor; } private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo, FieldInfo proxyField) @@ -187,6 +191,17 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo generator.Emit(OpCodes.Ret); // Return the Task returned by 'invokeMethod' } + private static void BuildFactoryMethod(TypeBuilder type, ConstructorInfo ctor) + { + var method = type.DefineMethod(nameof(Build), MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(T), ParameterTypes); + + var generator = method.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); // Load the IClientProxy argument onto the stack + generator.Emit(OpCodes.Newobj, ctor); // Call the generated constructor with the proxy + generator.Emit(OpCodes.Ret); // Return the typed client + } + private static void VerifyInterface(Type interfaceType) { if (!interfaceType.IsInterface) @@ -206,7 +221,7 @@ private static void VerifyInterface(Type interfaceType) foreach (var method in interfaceType.GetMethods()) { - VerifyMethod(interfaceType, method); + VerifyMethod(method); } foreach (var parent in interfaceType.GetInterfaces()) @@ -215,7 +230,7 @@ private static void VerifyInterface(Type interfaceType) } } - private static void VerifyMethod(Type interfaceType, MethodInfo interfaceMethod) + private static void VerifyMethod(MethodInfo interfaceMethod) { if (interfaceMethod.ReturnType != typeof(Task)) {