我正在研究代理,对于具有引用类型参数的泛型类,它非常慢。特别是对于泛型方法(大约 400 毫秒,而对于仅返回 null 的普通泛型方法则需要 3200 毫秒)。我决定尝试看看如果我用 C# 重写生成的类会如何执行,它的性能要好得多,与我的非泛型类代码的性能大致相同。
这是我写的 C# 类:: (注意我改变了命名方案但不是很多)::
namespace TestData
{
public class TestClassProxy<pR> : TestClass<pR>
{
private InvocationHandler<Func<TestClass<pR>, object>> _0_Test;
private InvocationHandler<Func<TestClass<pR>, pR, GenericToken, object>> _1_Test;
private static readonly InvocationHandler[] _proxy_handlers = new InvocationHandler[] {
new InvocationHandler<Func<TestClass<pR>, object>>(new Func<TestClass<pR>, object>(TestClassProxy<pR>.s_0_Test)),
new GenericInvocationHandler<Func<TestClass<pR>, pR, GenericToken, object>>(typeof(TestClassProxy<pR>), "s_1_Test") };
public TestClassProxy(InvocationHandler[] handlers)
{
if (handlers == null)
{
throw new ArgumentNullException("handlers");
}
if (handlers.Length != 2)
{
throw new ArgumentException("Handlers needs to be an array of 2 parameters.", "handlers");
}
this._0_Test = (InvocationHandler<Func<TestClass<pR>, object>>)(handlers[0] ?? _proxy_handlers[0]);
this._1_Test = (InvocationHandler<Func<TestClass<pR>, pR, GenericToken, object>>)(handlers[1] ?? _proxy_handlers[1]);
}
private object __0__Test()
{
return base.Test();
}
private object __1__Test<T>(pR local1) where T:IConvertible
{
return base.Test<T>(local1);
}
public static object s_0_Test(TestClass<pR> class1)
{
return ((TestClassProxy<pR>)class1).__0__Test();
}
public static object s_1_Test<T>(TestClass<pR> class1, pR local1) where T:IConvertible
{
return ((TestClassProxy<pR>)class1).__1__Test<T>(local1);
}
public override object Test()
{
return this._0_Test.Target(this);
}
public override object Test<T>(pR local1)
{
return this._1_Test.Target(this, local1, GenericToken<T>.Token);
}
}
}
这是在发布模式下编译为与我生成的代理相同的 IL 这里是它的代理类::
namespace TestData
{
public class TestClass<R>
{
public virtual object Test()
{
return default(object);
}
public virtual object Test<T>(R r) where T:IConvertible
{
return default(object);
}
}
}
有一个例外,我没有在生成的类型上设置 beforefieldinit 属性。我只是设置以下属性::public auto ansi
为什么使用 beforefieldinit 让性能提升如此之大?
(唯一的另一个区别是我没有命名我的参数,这在总体方案中并不重要。方法和字段的名称被打乱以避免与实际方法发生冲突。GenericToken 和 InvocationHandlers 是无关紧要的实现细节为了争论.
GenericToken 实际上只是用作类型化数据持有者,因为它允许我向处理程序发送“T”
InvocationHandler 只是委托字段目标的持有者,没有实际的实现细节。
GenericInvocationHandler 使用像 DLR 这样的调用站点技术来根据需要重写委托以处理传递的不同泛型参数)
编辑:: 这是测试工具::
private static void RunTests(int count = 1 << 24, bool displayResults = true)
{
var tests = Array.FindAll(Tests, t => t != null);
var maxLength = tests.Select(x => GetMethodName(x.Method).Length).Max();
for (int j = 0; j < tests.Length; j++)
{
var action = tests[j];
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
action();
}
sw.Stop();
if (displayResults)
{
Console.WriteLine("{2} {0}: {1}ms", GetMethodName(action.Method).PadRight(maxLength),
((int)sw.ElapsedMilliseconds).ToString(), j);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
private static string GetMethodName(MethodInfo method)
{
return method.IsGenericMethod
? string.Format(@"{0}<{1}>", method.Name, string.Join<Type>(",", method.GetGenericArguments()))
: method.Name;
}
在测试中,我执行以下操作:
Tests[0] = () => proxiedTestClass.Test();
Tests[1] = () => proxiedTestClass.Test<string>("2");
Tests[2] = () => handClass.Test();
Tests[3] = () => handClass.Test<string>("2");
RunTests(100, false);
RunTests();
其中 Tests 是一个Func<object>[20]
,并且proxiedTestClass
是我的程序集生成的类,并且handClass
是我手动生成的类。RunTests 被调用了两次,一次是为了“热身”,另一次是运行它并打印到屏幕上。我主要从 Jon Skeet 的帖子中获取此代码。