代码改变世界

使用 MSIL 为 Enum.HasFlag 封装一个泛型的扩展方法

2011-01-05 21:40  Nana's Lich  阅读(2754)  评论(25编辑  收藏  举报

.NET 框架 4.0 为 Enum 类型新增了一个 HasFlag 方法,这样每一个枚举类型(并不只限于“System.Enum”这个混蛋类型)的值都可以使用 HasFlag 来检查是不是设置了某个标志。

但是由于这个 HasFlag 方法接受的参数是 Enum 类型,而不是泛型的,在使用 Visual Studio (或者 Express)编写代码的时候就会发现智能感知无法在 flag 参数处提示期望的枚举类型,虽然这只是一个小毛病,但我觉得这样很不好。

 

可是,如果尝试创造一个泛型的 HasFlag 方法的话,就会发现 Enum 类型被视为特殊类型,不可以作为泛型约束(参考:http://msdn.microsoft.com/zh-cn/library/56b2hk61.aspx)。

所以对于此类的关于枚举的扩展方法的问题,普遍的解决方案是指定 struct、IConvertable 等类型约束,然后在运行时判断类型是否为枚举类型(如:http://stackoverflow.com/questions/79126/create-generic-method-constraining-t-to-an-enum)。

 

可我还是觉得很不舒服,大概是因为我有一点洁癖吧,我总觉得在运行时判断类型特性好像有点性能低下呢。

不过,创建一个仅限定枚举类型的泛型约束是可能的,上面的关于泛型约束的类型限制也只是 C# 编译器的限制,实际上托管 C++ 和 MSIL 是没有这个限制的(#1093531)。

 

了解到这些之后,我就开始了使用托管 C++ 和 MSIL 来创建这样的扩展方法的尝试。

在经过两三个个小时的努力之后,最终用 MSIL (托管 C++ 什么的我实在是搞不好,哈哈哈……)成功编译出了可以正确运作的、使用 Enum 类型作为泛型约束的泛型扩展方法,作为对 Enum.HasFlag 的封装,我给它取了个简单的名字,“Has”。

为了编译这个扩展方法,也单独创建了一个程序集,虽然有点大炮打蚊子的感觉,不过还是相信 CLR 的 JIT 技术吧!

 

整个程序集的 MSIL 都是在“先编译一个程序集,然后使用 ILDASM 反汇编”的基础之上进行修改的,原理什么的要讲也实在没啥好讲的,扩展方法的 MSIL 实现如下:

.method public hidebysig static bool  Has<([mscorlib]System.Enum) T>(!!T target, !!T flag) cil managed
{
	.custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) 

	.maxstack  8
	ldarga.s   target
	ldarg.1
	box        !!T
	constrained. !!T
	callvirt   instance bool [mscorlib]System.Enum::HasFlag(class [mscorlib]System.Enum)
	ret
}

主要需要注意到地方就是在 Has< ... > 里、T 的前面加上作为约束的类型 ([mscorlib]System.Enum), 然后后面的 box !!T 和 constrained. !!T 不能少,要不然 CLR 产生异常。

注意: constrained. 后面的那个句点是必要的,少了这个句点是不能编译成功的。

 

完整的 MSIL 源代码以及编译后的 DLL 可以点 【这里】 下载,MSIL 源使用 ILASM 编译之后就可以在项目中使用了,我使用的编译参数是 /dll /optimize /fold。

 

测试的代码也很简单:

using System;
using GenericEnumHelper;
using System.Diagnostics;

namespace EnumHelperTest
{
  [Flags]
  enum MyEnum
  {
    Zero = 0,
    One = 1,
    Two = 2,
    Three = 3,
  }

  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine(MyEnum.Zero.Has(MyEnum.One));
      Console.WriteLine(MyEnum.One.Has(MyEnum.One));
      Console.WriteLine(MyEnum.Zero.Has(MyEnum.Zero));
      Console.WriteLine(MyEnum.One.Has(MyEnum.Zero));

      Console.WriteLine();
      
      Console.WriteLine(MyEnum.Three.Has(MyEnum.Three));

      Console.WriteLine();

      Console.WriteLine(MyEnum.Three.Has(MyEnum.Two));
      Console.WriteLine(MyEnum.Three.Has(MyEnum.One));
      Console.WriteLine(MyEnum.Three.Has(MyEnum.Zero));

      Console.WriteLine();

      Console.WriteLine(MyEnum.Zero.Has(MyEnum.Three));
      Console.WriteLine(MyEnum.One.Has(MyEnum.Three));
      Console.WriteLine(MyEnum.Two.Has(MyEnum.Three));
    }
  }
}

至于输出的文本直观不直观好不好读懂之类的问题您就别跟我认真了罢!

 

如果你在 Visual Studio (Express) 里面查看这个 Has<T> 扩展方法的签名的话,你还会看到像下面的这样的代码:

#region 程序集 GenericEnumHas.DLL, v4.0.30319
// ... \GenericEnumHas.DLL
#endregion

using System;
using System.Runtime.CompilerServices;

namespace GenericEnumHelper
{
  public static class EnumHelper
  {
    public static bool Has<T>(this T target, T flag) where T : Enum;
  }
}

不过这段骨骼精奇的 C# 代码是绝对绝对完全完全不可能通过编译的~