Skip to content

Commit f31ce2d

Browse files
khellangBrennanConroy
authored andcommitted
Use dynamically compiled factory instead of Activator.CreateInstance in TypedClientBuilder (#14615)
1 parent d97022b commit f31ce2d

File tree

2 files changed

+66
-20
lines changed

2 files changed

+66
-20
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using BenchmarkDotNet.Attributes;
7+
using Microsoft.AspNetCore.SignalR.Internal;
8+
9+
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
10+
{
11+
public class TypedClientBuilderBenchmark
12+
{
13+
private static readonly IClientProxy Dummy = new DummyProxy();
14+
15+
[Benchmark]
16+
public ITestClient Build()
17+
{
18+
return TypedClientBuilder<ITestClient>.Build(Dummy);
19+
}
20+
21+
public interface ITestClient { }
22+
23+
private class DummyProxy : IClientProxy
24+
{
25+
public Task SendCoreAsync(string method, object[] args, CancellationToken cancellationToken = default)
26+
{
27+
return Task.CompletedTask;
28+
}
29+
}
30+
}
31+
}

src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ internal static class TypedClientBuilder<T>
2020

2121
private static readonly PropertyInfo CancellationTokenNoneProperty = typeof(CancellationToken).GetProperty("None", BindingFlags.Public | BindingFlags.Static);
2222

23+
private static readonly ConstructorInfo ObjectConstructor = typeof(object).GetConstructors().Single();
24+
25+
private static readonly Type[] ParameterTypes = new Type[] { typeof(IClientProxy) };
26+
2327
public static T Build(IClientProxy proxy)
2428
{
2529
return _builder.Value(proxy);
@@ -40,20 +44,24 @@ private static Func<IClientProxy, T> GenerateClientBuilder()
4044
var moduleBuilder = assemblyBuilder.DefineDynamicModule(ClientModuleName);
4145
var clientType = GenerateInterfaceImplementation(moduleBuilder);
4246

43-
return proxy => (T)Activator.CreateInstance(clientType, proxy);
47+
var factoryMethod = clientType.GetMethod(nameof(Build), BindingFlags.Public | BindingFlags.Static);
48+
return (Func<IClientProxy, T>)factoryMethod.CreateDelegate(typeof(Func<IClientProxy, T>));
4449
}
4550

4651
private static Type GenerateInterfaceImplementation(ModuleBuilder moduleBuilder)
4752
{
48-
var type = moduleBuilder.DefineType(
49-
ClientModuleName + "." + typeof(T).Name + "Impl",
50-
TypeAttributes.Public,
51-
typeof(Object),
52-
new[] { typeof(T) });
53+
var name = ClientModuleName + "." + typeof(T).Name + "Impl";
54+
55+
var type = moduleBuilder.DefineType(name, TypeAttributes.Public, typeof(object), new[] { typeof(T) });
56+
57+
var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private | FieldAttributes.InitOnly);
5358

54-
var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private);
59+
var ctor = BuildConstructor(type, proxyField);
5560

56-
BuildConstructor(type, proxyField);
61+
// Because a constructor doesn't return anything, it can't be wrapped in a
62+
// delegate directly, so we emit a factory method that just takes the IClientProxy,
63+
// invokes the constructor (using newobj) and returns the new instance of type T.
64+
BuildFactoryMethod(type, ctor);
5765

5866
foreach (var method in GetAllInterfaceMethods(typeof(T)))
5967
{
@@ -79,27 +87,23 @@ private static IEnumerable<MethodInfo> GetAllInterfaceMethods(Type interfaceType
7987
}
8088
}
8189

82-
private static void BuildConstructor(TypeBuilder type, FieldInfo proxyField)
90+
private static ConstructorInfo BuildConstructor(TypeBuilder type, FieldInfo proxyField)
8391
{
84-
var method = type.DefineMethod(".ctor", System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.HideBySig);
92+
var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ParameterTypes);
8593

86-
var ctor = typeof(object).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
87-
null, new Type[] { }, null);
88-
89-
method.SetReturnType(typeof(void));
90-
method.SetParameters(typeof(IClientProxy));
91-
92-
var generator = method.GetILGenerator();
94+
var generator = ctor.GetILGenerator();
9395

9496
// Call object constructor
9597
generator.Emit(OpCodes.Ldarg_0);
96-
generator.Emit(OpCodes.Call, ctor);
98+
generator.Emit(OpCodes.Call, ObjectConstructor);
9799

98100
// Assign constructor argument to the proxyField
99101
generator.Emit(OpCodes.Ldarg_0); // type
100102
generator.Emit(OpCodes.Ldarg_1); // type proxyfield
101103
generator.Emit(OpCodes.Stfld, proxyField); // type.proxyField = proxyField
102104
generator.Emit(OpCodes.Ret);
105+
106+
return ctor;
103107
}
104108

105109
private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo, FieldInfo proxyField)
@@ -187,6 +191,17 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo
187191
generator.Emit(OpCodes.Ret); // Return the Task returned by 'invokeMethod'
188192
}
189193

194+
private static void BuildFactoryMethod(TypeBuilder type, ConstructorInfo ctor)
195+
{
196+
var method = type.DefineMethod(nameof(Build), MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(T), ParameterTypes);
197+
198+
var generator = method.GetILGenerator();
199+
200+
generator.Emit(OpCodes.Ldarg_0); // Load the IClientProxy argument onto the stack
201+
generator.Emit(OpCodes.Newobj, ctor); // Call the generated constructor with the proxy
202+
generator.Emit(OpCodes.Ret); // Return the typed client
203+
}
204+
190205
private static void VerifyInterface(Type interfaceType)
191206
{
192207
if (!interfaceType.IsInterface)
@@ -206,7 +221,7 @@ private static void VerifyInterface(Type interfaceType)
206221

207222
foreach (var method in interfaceType.GetMethods())
208223
{
209-
VerifyMethod(interfaceType, method);
224+
VerifyMethod(method);
210225
}
211226

212227
foreach (var parent in interfaceType.GetInterfaces())
@@ -215,7 +230,7 @@ private static void VerifyInterface(Type interfaceType)
215230
}
216231
}
217232

218-
private static void VerifyMethod(Type interfaceType, MethodInfo interfaceMethod)
233+
private static void VerifyMethod(MethodInfo interfaceMethod)
219234
{
220235
if (interfaceMethod.ReturnType != typeof(Task))
221236
{

0 commit comments

Comments
 (0)