java泛型,利用Mono.Cecil制作出枚举类的泛型扩展方法

我的项目的某部分需要经常判断枚举的某个标识的存在,就经常出现类似于“(A & B) == B;”这样的代码。不过我看着觉得不爽,也觉得很繁琐。我知道Enum类有HasFlag()方法,但是这个会导致装箱,智能感知也感知不出来,不好。于是我就写了下面的代码:
1 public static bool Has(this TEnum rpEnum, TEnum rpValue) 2 where TEnum : struct, Enum 3 { 4 return (rpEnum & rpValue) == rpValue; 5 }
可是这代码无法通过编译
Image1利用Mono.Cecil制作出枚举类的泛型扩展方法
因为在编译器的眼中,Enum是个特殊类型,无法作为泛型约束(参考:http://msdn.microsoft.com/zh-cn/library/56b2hk61.aspx)
难道这个只能是空想吗?非也
经过搜索,发现在C++/CLI里没有这个限制(参考:http://social.msdn.microsoft.com/Forums/en-US/vs2010ctpvbcs/thread/5844d719-3ef6-4000-913c-27c108269e71)。但我的基础类库不可能全部换用C++/CLI写吧(要知道C++/CLI的语法弄得我头都大了)
不过想想,在C++/CLI可以通过编译,那就说明在IL方面是可以支持枚举作为泛型约束,只是C#编译器有问题而已。于是我就想到了Mono.Cecil这个神器,用它修改编译后的程序集来实现我要的方法(我的基础类库也不可能全部换用IL来写吧)

修改

先弄个伪方法:
Image1利用Mono.Cecil制作出枚举类的泛型扩展方法Image1利用Mono.Cecil制作出枚举类的泛型扩展方法View Code 1 public static bool Has(this TEnum rpEnum, TEnum rpValue) 2 where TEnum : struct 3 { 4 return true; 5 }
然后新建一个程序,把Mono.Cecil导入进去(下载:http://www.mono-project.com/Cecil)
下面是主要代码:
View Code 1 static void Main(string[] args) 2 { 3 //读取程序集 4 var rAssembly = AssemblyDefinition.ReadAssembly("Moen.Base.dll"); 5 //获取指定的类 6 var rType = rAssembly.MainModule.GetType("Moen", "EnumExtensions"); 7 //获取指定的方法 8 var rMethod = rType.Methods.Single(r => r.Name == "Has"); 9 10 //修改泛型约束 11 var rGenericParameter = rMethod.GenericParameters[0]; 12 rGenericParameter.Constraints[0] = rAssembly.MainModule.Import(typeof(Enum)); 13 //修改最大栈大小(这个功能需要压栈两次) 14 rMethod.Body.MaxStackSize = 2; 15 //清空方法体代码 16 rMethod.Body.Instructions.Clear(); 17 //修改方法体代码 18 var rProcessor = rMethod.Body.GetILProcessor(); 19 rProcessor.Append(rProcessor.Create(OpCodes.Ldarg_0)); 20 rProcessor.Append(rProcessor.Create(OpCodes.Ldarg_1)); 21 rProcessor.Append(rProcessor.Create(OpCodes.And)); 22 rProcessor.Append(rProcessor.Create(OpCodes.Ldarg_1)); 23 rProcessor.Append(rProcessor.Create(OpCodes.Ceq)); 24 rProcessor.Append(rProcessor.Create(OpCodes.Stloc_0)); 25 rProcessor.Append(rProcessor.Create(OpCodes.Ldloc_0)); 26 rProcessor.Append(rProcessor.Create(OpCodes.Ret)); 27 28 //保存修改 29 rAssembly.Write("Moen.Base.dll"); 30 }
PS:修改部分的那串代码,是我先编译一个指定枚举的同样功能的方法,然后从反编译出的IL代码中得到的

最后

查看方法的签名,就会像这样。C#编译器可是做不到呀
为什么约束那里除了Enum还有一个struct呢,因为我不想让已装箱的Enum对象在此方法中搞破坏(抛出异常),必须是实际的枚举类
PS:不要吐槽图中的Object2(如果你想知道真相的话,不过很简单)
Image2Image1利用Mono.Cecil制作出枚举类的泛型扩展方法
下面是测试
Image3Image2Image1利用Mono.Cecil制作出枚举类的泛型扩展方法
这样问题解决了。(为了减少操作量,可以在类库的生成后事件中调用这个修改程序)

总结

Mono.Cecil可以很方便地使枚举类成为泛型约束条件,也能做到许多意想不到的东西。当然,我希望下一个版本的C#编译器能去除这个限制
Tags:  什么是泛型 泛型集合 泛型类 泛型编程 java泛型

延伸阅读

最新评论

发表评论