C#中利用泛型扩展方法辅助枚举类型进行Flags处理
来源:互联网 发布:erp开源系统源码 编辑:程序博客网 时间:2024/05/16 14:49
Flags的按位或操作是在API设计中很常见的一种模式,十分直观和方便。C#中的枚举类型也提供了同样的机制来使用Flags模式。但是想要在代码中执行一些Flags操作需要频繁的使用按位或、按位与和按位求反等操作,十分不方便,代码可读性也不高。
最近在读CLR via C#讲述枚举类型的一章时,看到Jeff在书中为特定枚举类型实现了一组扩展方法来辅助枚举类型对Flags进行处理。
代码如下:
public static class MyEnumExtensions{ public static Boolean IsSet(this MyEnum enumValue, MyEnum valueToTest) { if (valueToTest == 0) throw new ArgumentOutOfRangeException("valueToTest", "Value must not be 0"); return (enumValue & valueToTest) == valueToTest; } public static Boolean IsClear(this MyEnum enumValue, MyEnum valueToTest) { if (valueToTest == 0) throw new ArgumentOutOfRangeException("valueToTest", "Value must not be 0"); return !IsSet(enumValue, valueToTest); } public static Boolean AnyFlagsSet(this MyEnum enumValue, MyEnum testValues) { return ((enumValue & testValues) != 0); } public static MyEnum Set(this MyEnum enumValue, MyEnum setValues) { return enumValue | setValues; } public static MyEnum Clear(this MyEnum enumValue, MyEnum clearValues) { return enumValue & ~clearValues; } public static void ForEach(this MyEnum enumValue, Action<MyEnum> processValue) { if (processValue == null) throw new ArgumentNullException("processValue"); for (UInt32 bit = 1; bit != 0; bit <<= 1) { UInt32 temp = ((UInt32)enumValue) & bit; if (temp != 0) processValue((MyEnum)temp); } }}
虽然使用方便,但上述代码只能对某一种特定的枚举类型起作用。新的支持Flags的枚举类型还需要再编写同样的一组扩展方法。自然想到实现一组泛型的扩展方法应用到所有枚举类型。
着手实现的过程中逐渐发现,Jeff没有在书中给出一组泛型方法是有其考虑的。
首先,C#不支持枚举类型的泛型参数约束。
我们可以写:
void Func<T>() where T : class
和
void Func<T>() where T : struct
但我们不能写:
void Func<T>() where T : enum
或
void Func<T>() where T : System.Enum
既然不能约束类型参数为枚举,自然就会引出类型安全问题——Flags操作的操作数不一定来自于同一个枚举类型,甚至不是一个枚举类型。
所以要实现泛型就需要约束扩展方法只作用于枚举类型同时保证类型安全,在C#现有的限制下很难优美得实现这一特性。不过经过反复实验,我最终找到了一种实现方法。
代码如下:
/// <summary>/// Extensions help with handling enum with flags./// </summary>public static class FlagsExtensions{ /// <summary> /// Test if the flag is set to this value. /// </summary> public static Boolean IsSet(this Enum enumValue, Enum valueToTest) { if (enumValue.GetType() != valueToTest.GetType()) throw new ArgumentException("valueToTest", "Value must share the same type."); if (Convert.ToUInt64(valueToTest) == 0) throw new ArgumentOutOfRangeException("valueToTest", "Value must not be 0"); return (Convert.ToUInt64(enumValue) & Convert.ToUInt64(valueToTest)) == Convert.ToUInt64(valueToTest); } /// <summary> /// Test if the value is clear with this flag. /// </summary> public static Boolean IsClear(this Enum enumValue, Enum valueToTest) { if (enumValue.GetType() != valueToTest.GetType()) throw new ArgumentException("valueToTest", "Value must share the same type."); if (Convert.ToUInt64(valueToTest) == 0) throw new ArgumentOutOfRangeException("valueToTest", "Value must not be 0"); return !IsSet(enumValue, valueToTest); } /// <summary> /// Test if one of these test flags is set to this value. /// </summary> public static Boolean AnyFlagsSet(this Enum enumValue, Enum testValues) { if (enumValue.GetType() != testValues.GetType()) throw new ArgumentException("testValues", "Value must share the same type."); return ((Convert.ToUInt64(enumValue) & Convert.ToUInt64(testValues)) != 0); } /// <summary> /// Return a new value that set with specific flags. /// </summary> public static TEnum Set<TEnum>(this Enum enumValue, TEnum setValues) { if (enumValue.GetType() != setValues.GetType()) throw new ArgumentException("setValues", "Value must share the same type."); return (TEnum)Enum.ToObject(enumValue.GetType(), Convert.ToUInt64(enumValue) | Convert.ToUInt64(setValues)); } /// <summary> /// Return a new value with specific flags removed from this value. /// </summary> public static TEnum Clear<TEnum>(this Enum enumValue, TEnum clearValues) { if (enumValue.GetType() != clearValues.GetType()) throw new ArgumentException("clearValues", "Value must share the same type."); return (TEnum)Enum.ToObject(enumValue.GetType(), Convert.ToUInt64(enumValue) & ~Convert.ToUInt64(clearValues)); } /// <summary> /// For each flag in the value, perform the specific action. /// </summary> public static void ForEach<TEnum>(this Enum enumValue, Action<TEnum> processValue) { if (processValue == null) throw new ArgumentNullException("processValue"); if (enumValue.GetType() != typeof(TEnum)) throw new ArgumentException("type:TEnum", "processValue's parameter must have the same type."); for (UInt64 bit = 1; bit != 0; bit <<= 1) { UInt64 temp = Convert.ToUInt64(enumValue) & bit; if (temp != 0) processValue((TEnum)Enum.ToObject(typeof(TEnum), temp)); } }}
使用示例:
[Flags]enum EnumWithFlags{ FlagA = 0x01, FlagB = 0x02, FlagC = 0x04, FlagD = 0x08, FlagE = 0x10, FlagF = 0x20}class Program{ static void Main(string[] args) { EnumWithFlags value = EnumWithFlags.FlagA | EnumWithFlags.FlagB | EnumWithFlags.FlagC; value = value.Set(EnumWithFlags.FlagD | EnumWithFlags.FlagE); value = value.Clear(EnumWithFlags.FlagA | EnumWithFlags.FlagC); value.ForEach((EnumWithFlags v) => Console.WriteLine(v)); Console.ReadKey(); }}
这一实现主要有三个要点
- 将扩展方法的this参数设置为System.Enum类型,因为全部枚举类型均派生自System.Enum,这样可以确保扩展方法只作用于枚举类型。
- 对所有枚举类型进行类型检查,确保类型安全。
- 将所有枚举类型转换到System.UInt64进行按位操作,确保足以容纳任何基类型的枚举值。
需要注意的是调用ForEach方法时如果使用Lambda表达式,那么编译器将无法推断泛型参数的类型,所以需要显式指定参数类型。
这一实现同样有一些缺点,例如频繁的类型检查和转换。密集地调用可能导致很差的性能,比较适合于一般Flags参数的传递,修改和检测。
—————————————————
Chobi-Q欢迎转载,转载请注明出处。
- C#中利用泛型扩展方法辅助枚举类型进行Flags处理
- C# 枚举类型的扩展
- C#中字符串转换成枚举类型的方法
- C# 为类型扩展方法
- 在C#中如何实现枚举类型的特性扩展和反射获取
- ORACLE中对LONG类型进行处理的方法
- ORACLE中对LONG类型进行处理的方法
- .NET中Flags枚举的使用
- .NET中Flags枚举的使用
- C#枚举中使用Flags特性
- C#中定义枚举类型数据
- C#中枚举类型的转换问题
- C#中枚举类型的使用总结
- c#中定义一个枚举类型
- C#中枚举类型的作用
- C#中枚举类型的使用
- C#中枚举类型的使用
- c#如何扩展类型的内置方法
- 一道简单有趣的java题:输出顺序的问题
- 企业架构EA
- (JAVA SE 学习笔记)Java.SE.第010讲.面向对象之封装.续
- Mac OS X上安装OpenVPN
- VC2010编译boost
- C#中利用泛型扩展方法辅助枚举类型进行Flags处理
- C#中抽象类和接口的区别
- 属性系统-编辑器和引擎的强力粘合剂
- (JAVA SE 学习笔记)Java.SE.第011讲.面向对象之封装.续二(对一些原理机制进行了详细的解释,适合初学者学习)
- LINUX网页资源连接
- Java执行Shell脚本超时控制
- VS快捷键
- VS命令
- fedora 从源里安装 chrome 浏览器的方法