Use dynamically compiled factory instead of Activator.CreateInstance in TypedClientBuilder (#14615)

This commit is contained in:
Kristian Hellang 2019-10-08 17:31:40 +02:00 committed by Brennan
parent d97022b214
commit f31ce2de0f
2 changed files with 66 additions and 20 deletions

View File

@ -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<ITestClient>.Build(Dummy);
}
public interface ITestClient { }
private class DummyProxy : IClientProxy
{
public Task SendCoreAsync(string method, object[] args, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
}
}
}

View File

@ -20,6 +20,10 @@ namespace Microsoft.AspNetCore.SignalR.Internal
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 @@ namespace Microsoft.AspNetCore.SignalR.Internal
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<IClientProxy, T>)factoryMethod.CreateDelegate(typeof(Func<IClientProxy, T>));
}
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 proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private);
var type = moduleBuilder.DefineType(name, TypeAttributes.Public, typeof(object), new[] { typeof(T) });
BuildConstructor(type, proxyField);
var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private | FieldAttributes.InitOnly);
var ctor = 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 @@ namespace Microsoft.AspNetCore.SignalR.Internal
}
}
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 @@ namespace Microsoft.AspNetCore.SignalR.Internal
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 @@ namespace Microsoft.AspNetCore.SignalR.Internal
foreach (var method in interfaceType.GetMethods())
{
VerifyMethod(interfaceType, method);
VerifyMethod(method);
}
foreach (var parent in interfaceType.GetInterfaces())
@ -215,7 +230,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
}
}
private static void VerifyMethod(Type interfaceType, MethodInfo interfaceMethod)
private static void VerifyMethod(MethodInfo interfaceMethod)
{
if (interfaceMethod.ReturnType != typeof(Task))
{