65.9K
CodeProject 正在变化。 阅读更多。
Home

ComparerFactory - 运行时实现编译时性能

2010年2月11日

CPOL

3分钟阅读

viewsIcon

25966

downloadIcon

110

一个关于如何在运行时使用反射来实现编译时性能的例子。

引言

反射速度慢已经变成了一种常识。为了证明这个事实,本文使用了WPF ListCollectionView的默认排序行为。在对数千个条目进行排序之前,它工作良好。本文描述的技术展示了如何使用反射在运行时生成类,同时避免性能损失。

背景

我的最初任务是在ComboBox中对项目列表进行排序(都在WPF中完成,并且有一些优秀的文章展示了如何做到这一点)。我很容易地想到,使用CollectionView并在其上使用SortDescription

 private void Sort(string sortBy, ListSortDirection direction){
    ICollectionView dataView = CollectionViewSource.GetDefaultView(lv.ItemsSource);
    dataView.SortDescriptions.Clear();
    var sd = new SortDescription("Name", direction);
    dataView.SortDescriptions.Add(sd);
    dataView.Refresh();
}   

没那么快。字面上说,不是快,而是相当慢。该列表包含 2 万个项目。是的,我知道两万条记录在ComboBox中看起来不太好,无论是否排序,但这不是我的问题。我的问题是排序,我必须发明一些东西。反射是造成速度慢的原因,以下解决方案效果很好。创建一个比较器类

public class CustomComparer: IComparer {
    public int Compare(object x, object y) {
        return ((ComparedClass)x).Name.CompareTo(
            ((ComparedClass)y).Name);
    }
} 

然后使用ListCollectionViewCustomSort属性,而不是上面的SortDescriptions

dataView.CustomSort = new CustomComparer(); 

这解决了性能问题,但我的真正任务是让它适用于任何ComboBox以及列表中任何类型的对象。我太懒了,每次都写一个自定义的比较器类,所以我写了一个工厂来为我生成它们。

运行时编译

常见的反射用法是获取类定义的元数据并在运行时使用它。但真正的魔力始于你在运行时获取该元数据并在那里构建类。这些类的行为将像在编译时创建的一样快。诀窍是在运行时使用 Reflection.Emit 生成代码

这是可能的,因为 .NET 程序集是 IL(中间语言)指令的二进制代码。您可以将 IL 视为 .NET 的一种汇编语言。当程序集被执行 JIT(即时编译)时,编译器将 IL 转换为特定于机器的代码。通过System.Reflection.Emit命名空间,我们可以使用 IL 代码在运行时生成动态程序集,JIT 编译器不会注意到差异。让我们动态地创建程序集

dynamicName = new AssemblyName("dynamicAssemblyName");
dynamicAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
    dynamicName,
    AssemblyBuilderAccess.Run);
dynamicModuleBuilder = dynamicAssemblyBuilder.DefineDynamicModule(dynamicName.Name); 

这是创建类的先决条件。然后我们需要一个TypeBuilder实例

TypeBuilder tb = dynamicModuleBuilder.DefineType(
    typeName,
    TypeAttributes.Public | TypeAttributes.Class,
    null, new Type[] { typeof(IComparer) }); 

表明我们将要实现IComparer接口

tb.AddInterfaceImplementation(typeof(IComparer));
MethodBuilder methodBuilder = tb.DefineMethod("Compare",
    MethodAttributes.Public | MethodAttributes.Virtual,
    typeof(int),
    new Type[] { typeof(object), typeof(object) });
ILGenerator methodILGenerator = methodBuilder.GetILGenerator(); 

现在我们可以使用methodILGenerator来生成方法体。但是该方法在 IL 中会是什么样子呢?好问题,很高兴你问了。有一个标准和来自微软的进一步阅读,将帮助我们完成这项任务。

但是等等,还有更简单的方法。C# 编译器为我们完成了翻译成 IL 的任务。我们所需要的只是一个反汇编器来将该代码翻译回人类可读的形式。ildasm(作为免费 Windows SDK 下载的一部分提供)就是这样做的。以下是 ildasm 解读的方法定义的样子

.method public hidebysig newslot virtual final 
        instance int32  Compare(object x,
                                object y) cil managed
{
  // Code size       28 (0x1c)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  castclass  tevton.ComparerFactoryTests.ComparerFactoryTest/CustomComparer
  IL_0006:  callvirt   instance string 
	tevton.ComparerFactoryTests.ComparerFactoryTest/CustomComparer::get_Name()
  IL_000b:  ldarg.2
  IL_000c:  castclass  tevton.ComparerFactoryTests.ComparerFactoryTest/CustomComparer
  IL_0011:  callvirt   instance string 
	tevton.ComparerFactoryTests.ComparerFactoryTest/CustomComparer::get_Name()
  IL_0016:  callvirt   instance int32 [mscorlib]System.IComparable::CompareTo(object)
  IL_001b:  ret
} // end of method CustomComparer::Compare 

经过一点努力和一些实验(值类型属性比较器看起来会有点不同),这可以转化为以下 C# 代码

var getAccessor = propertyInfo.GetAccessors()
                      .Where(mi => mi.Name.StartsWith("get_")).SingleOrDefault();
methodILGenerator.Emit(OpCodes.Ldarg_1);
methodILGenerator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
methodILGenerator.Emit(OpCodes.Callvirt, getAccessor);
if(propertyInfo.PropertyType.IsValueType) {
    methodILGenerator.Emit(OpCodes.Box, propertyInfo.PropertyType);
}
                
methodILGenerator.Emit(OpCodes.Ldarg_2);
methodILGenerator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
methodILGenerator.Emit(OpCodes.Callvirt, getAccessor);
if(propertyInfo.PropertyType.IsValueType) {
    methodILGenerator.Emit(OpCodes.Box, propertyInfo.PropertyType);
}
                
methodILGenerator.Emit(OpCodes.Callvirt, typeof(IComparable).GetMethod("CompareTo"));
methodILGenerator.Emit(OpCodes.Ret); 

这处理了方法体。只需一个最后的润色 - 用构建的方法覆盖IComparer.Compare()

MethodInfo compareInfo = typeof(IComparer).GetMethod("Compare");
tb.DefineMethodOverride(methodBuilder, compareInfo);
Type t = tb.CreateType();
IComparer res = Activator.CreateInstance(t) as IComparer;  

类实例就绪!我之前提到的工厂将所有这些动态比较器包装成一个方便的包。

Using the Code

每当您需要像上面所示的IComparer类时,您可以从工厂获取一个实例

dataView.CustomSort = 
	ComparerFactory.Instance.GetComparer(typeof(ComparedClass), "Name");  

就是这样,具有强类型类性能的所有反射(一种“后期绑定”)的好处。源代码带有一个演示项目和 NUnit 测试库,以使用法清晰明了。

请在家尝试。反射比我以前想象的要强大,但请注意存在一些缺点,例如调试动态代码的难度,使 GC 处置动态创建的类型以及如果您 IL 代码错误时出现零星异常的麻烦。

历史

  • 2010 年 2 月 - 初始版本
© . All rights reserved.