将标记的枚举转换为另一个枚举
Convert a flagged enum to another one
我有一个通用枚举,假设 G 有一些标记值(一 = 0 / 二 = 1 / 三 = 2 / 四 = 4 / 五 = 8,等等)。
然后我有另一个枚举(假设 B)"extends" G 具有这种模式:一个 = G.One / 二 = G.Two / 三 = G.Three / 四 = G.Four(仅此而已,没有五)。
我终于有了最后一个枚举(假设是 C),它也 "extends" G 具有相同的模式类型但其他值:三 = G.Three / 四 = G.Four /五 = G.Five(没有一和二)。
我想找到一个通用函数来将 B 转换为 C 或将 C 转换为 B。
例如,如果我有 "A valsAsA = A.One | A.Three | A.Four",我想要一个像这样的函数:"B valsAsB = convert(valsAsA);" 会给我 "B.Three | A.Four".
这应该是非常通用的,因为我不仅有 A 和 B 枚举,还有 C、D、E... 具有不同的可能枚举值,但值始终来自通用枚举。
是否可以在每次添加新枚举时不检查所有可能性并调整函数?
一个例子:
public enum General : int
{
One = 0,
Two = 1,
Three = 2,
Four = 4,
Five = 8
}
public enum A : int
{
One = General.One,
Two = General.Two,
Three = General.Three,
Four = General.Four,
}
public enum B : int
{
Three = General.Three,
Four = General.Four,
Five = General.Five
}
public enum C : int
{
One = General.One,
Three = General.Three,
Five = General.Five
}
public class Test
{
public void testConvert()
{
A valAsA = A.One | A.Three | A.Four;
B valAsB = convertFct(valAsA); // Should give me "B.Three | B.Four"
C valAsC = convertFct(valAsA); // Should give me "C.One | C.Three"
}
}
我测试过:
A valAsA = A.One | A.Three | A.Four;
C valAsC = (C)valAsA;
C valAsCReal = C.One | C.Three; // expected result
运气不好.. valAsC = 6 而 valAsCReal = 2...
非常感谢
妈的!!!我可以创建这个令人难以置信的函数来回答我的问题,但请...告诉我还有更优雅的东西...xD
private TRet ConvertIt<TRet, TOrig>(TOrig values) where TOrig : struct, IConvertible where TRet : struct, IConvertible
{
if (!typeof(TOrig).IsEnum ||
!typeof(TRet).IsEnum ||
!typeof(int).IsAssignableFrom(typeof(TOrig)) ||
!typeof(int).IsAssignableFrom(typeof(TRet)))
{
throw new ArgumentException("TOrig and TRet must be an enumerated type extending integer");
}
bool retEnumHasZero = false;
foreach (var flag in Enum.GetValues(typeof(TRet)))
{
if ((int)flag == 0)
{
retEnumHasZero = true;
break;
}
}
if (!retEnumHasZero)
{
throw new ArgumentException("TRet enum must have the 0 flag");
}
Dictionary<int, Enum> valsOrig = new Dictionary<int, Enum>();
foreach (var flag in Enum.GetValues(typeof(TOrig)))
{
valsOrig.Add((int)flag, (Enum)flag);
}
object valuesAsObject = values;
var valuesAsEnum = (Enum)valuesAsObject;
int returnedValue = 0;
foreach (var flag in Enum.GetValues(typeof(TRet)))
{
int flagAsInt = (int)flag;
if (valsOrig.ContainsKey(flagAsInt) && valuesAsEnum.HasFlag(valsOrig[flagAsInt]))
{
returnedValue |= flagAsInt;
}
}
return (TRet)Enum.ToObject(typeof(TRet), returnedValue);
}
使用函数:
A valAsA = A.One | A.Two | A.Three | A.Four;
C valAsC = ConvertIt<C, A>(valAsA);
编辑:这个实现看起来更好:
private T ConvertIt<T>(Enum values) where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
{
throw new ArgumentException("Type to return must be an enumerated type");
}
if (!Enum.IsDefined(typeof(T), 0))
{
throw new ArgumentException("Type to return enum must have the 0 flag");
}
int returnedValue = 0;
foreach (var flag in Enum.GetValues(values.GetType()))
{
int flagAsInt = (int)flag;
if (values.HasFlag((Enum)flag) && Enum.IsDefined(typeof(T), flagAsInt))
{
returnedValue |= flagAsInt;
}
}
return (T)Enum.ToObject(typeof(T), returnedValue);
}
使用函数:
A valAsA = A.One | A.Two | A.Three | A.Four;
C valAsC = ConvertIt<C>(valAsA);
最后,在John Wu的帮助下,这里是最终的功能=>
private T ConvertIt<T>(Enum input) where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
{
throw new ArgumentException("Type to return must be an enumerated type");
}
return (T)Enum.ToObject(typeof(T), (int)(object)input & Enum.GetValues(typeof(T)).Cast<int>().Sum());
}
使用泛型执行此操作有点棘手,因为通常无法设置允许枚举的类型约束(请参阅 this question)。你能做的最好的事情就是限制 struct, IConvertible
并进行运行时检查,就像我在这个例子中所做的那样。
如果你能应付那部分的丑陋,剩下的就相当简单了:
首先,编写两个方法与 General
进行相互转换。由于您的枚举是位掩码,因此 "conversion" 实际上只是针对所有可能值之和的二进制 and
操作,您可以使用 GetValues.
获得
执行完 and 操作后,您可以通过使用 Enum.ToObject().
转换整数来 return 适当类型的枚举
static public class ExtensionMethods
{
static public General ToGeneral<T>(this T input) where T : struct, IConvertible
{
if (!typeof(T).IsEnum) throw new ArgumentException("Input must be an enum.");
return (General)((int)(object)input & Enum.GetValues(typeof(General)).Cast<int>().Sum());
}
static public T ToEnum<T>(this General input)
{
if (!typeof(T).IsEnum) throw new ArgumentException("Output type must be an enum.");
return (T)Enum.ToObject(typeof(T), (int)input & Enum.GetValues(typeof(T)).Cast<int>().Sum());
}
}
写完这些后,与任何枚举之间的转换就很容易了:
static public TOut Convert<TIn,TOut>(TIn input) where TIn : struct, IConvertible where TOut: struct, IConvertible
{
var general = input.ToGeneral();
return general.ToEnum<TOut>();
}
测试代码:
public static void Main()
{
A valAsA = A.One | A.Three | A.Four;
B valAsB = Convert<A, B>(valAsA); // Should give me "B.Three | B.Four"
C valAsC = Convert<A, C>(valAsA); // Should give me "C.One | C.Three"
Console.WriteLine("{0} should equal {1}", valAsB, (B.Three | B.Four));
Console.WriteLine("{0} should equal {1}", valAsC, (C.One | C.Three));
}
输出:
6 should equal 6
Three should equal Three
我有一个通用枚举,假设 G 有一些标记值(一 = 0 / 二 = 1 / 三 = 2 / 四 = 4 / 五 = 8,等等)。
然后我有另一个枚举(假设 B)"extends" G 具有这种模式:一个 = G.One / 二 = G.Two / 三 = G.Three / 四 = G.Four(仅此而已,没有五)。
我终于有了最后一个枚举(假设是 C),它也 "extends" G 具有相同的模式类型但其他值:三 = G.Three / 四 = G.Four /五 = G.Five(没有一和二)。
我想找到一个通用函数来将 B 转换为 C 或将 C 转换为 B。 例如,如果我有 "A valsAsA = A.One | A.Three | A.Four",我想要一个像这样的函数:"B valsAsB = convert(valsAsA);" 会给我 "B.Three | A.Four".
这应该是非常通用的,因为我不仅有 A 和 B 枚举,还有 C、D、E... 具有不同的可能枚举值,但值始终来自通用枚举。
是否可以在每次添加新枚举时不检查所有可能性并调整函数?
一个例子:
public enum General : int
{
One = 0,
Two = 1,
Three = 2,
Four = 4,
Five = 8
}
public enum A : int
{
One = General.One,
Two = General.Two,
Three = General.Three,
Four = General.Four,
}
public enum B : int
{
Three = General.Three,
Four = General.Four,
Five = General.Five
}
public enum C : int
{
One = General.One,
Three = General.Three,
Five = General.Five
}
public class Test
{
public void testConvert()
{
A valAsA = A.One | A.Three | A.Four;
B valAsB = convertFct(valAsA); // Should give me "B.Three | B.Four"
C valAsC = convertFct(valAsA); // Should give me "C.One | C.Three"
}
}
我测试过:
A valAsA = A.One | A.Three | A.Four;
C valAsC = (C)valAsA;
C valAsCReal = C.One | C.Three; // expected result
运气不好.. valAsC = 6 而 valAsCReal = 2...
非常感谢
妈的!!!我可以创建这个令人难以置信的函数来回答我的问题,但请...告诉我还有更优雅的东西...xD
private TRet ConvertIt<TRet, TOrig>(TOrig values) where TOrig : struct, IConvertible where TRet : struct, IConvertible
{
if (!typeof(TOrig).IsEnum ||
!typeof(TRet).IsEnum ||
!typeof(int).IsAssignableFrom(typeof(TOrig)) ||
!typeof(int).IsAssignableFrom(typeof(TRet)))
{
throw new ArgumentException("TOrig and TRet must be an enumerated type extending integer");
}
bool retEnumHasZero = false;
foreach (var flag in Enum.GetValues(typeof(TRet)))
{
if ((int)flag == 0)
{
retEnumHasZero = true;
break;
}
}
if (!retEnumHasZero)
{
throw new ArgumentException("TRet enum must have the 0 flag");
}
Dictionary<int, Enum> valsOrig = new Dictionary<int, Enum>();
foreach (var flag in Enum.GetValues(typeof(TOrig)))
{
valsOrig.Add((int)flag, (Enum)flag);
}
object valuesAsObject = values;
var valuesAsEnum = (Enum)valuesAsObject;
int returnedValue = 0;
foreach (var flag in Enum.GetValues(typeof(TRet)))
{
int flagAsInt = (int)flag;
if (valsOrig.ContainsKey(flagAsInt) && valuesAsEnum.HasFlag(valsOrig[flagAsInt]))
{
returnedValue |= flagAsInt;
}
}
return (TRet)Enum.ToObject(typeof(TRet), returnedValue);
}
使用函数:
A valAsA = A.One | A.Two | A.Three | A.Four;
C valAsC = ConvertIt<C, A>(valAsA);
编辑:这个实现看起来更好:
private T ConvertIt<T>(Enum values) where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
{
throw new ArgumentException("Type to return must be an enumerated type");
}
if (!Enum.IsDefined(typeof(T), 0))
{
throw new ArgumentException("Type to return enum must have the 0 flag");
}
int returnedValue = 0;
foreach (var flag in Enum.GetValues(values.GetType()))
{
int flagAsInt = (int)flag;
if (values.HasFlag((Enum)flag) && Enum.IsDefined(typeof(T), flagAsInt))
{
returnedValue |= flagAsInt;
}
}
return (T)Enum.ToObject(typeof(T), returnedValue);
}
使用函数:
A valAsA = A.One | A.Two | A.Three | A.Four;
C valAsC = ConvertIt<C>(valAsA);
最后,在John Wu的帮助下,这里是最终的功能=>
private T ConvertIt<T>(Enum input) where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
{
throw new ArgumentException("Type to return must be an enumerated type");
}
return (T)Enum.ToObject(typeof(T), (int)(object)input & Enum.GetValues(typeof(T)).Cast<int>().Sum());
}
使用泛型执行此操作有点棘手,因为通常无法设置允许枚举的类型约束(请参阅 this question)。你能做的最好的事情就是限制 struct, IConvertible
并进行运行时检查,就像我在这个例子中所做的那样。
如果你能应付那部分的丑陋,剩下的就相当简单了:
首先,编写两个方法与 General
进行相互转换。由于您的枚举是位掩码,因此 "conversion" 实际上只是针对所有可能值之和的二进制 and
操作,您可以使用 GetValues.
执行完 and 操作后,您可以通过使用 Enum.ToObject().
转换整数来 return 适当类型的枚举static public class ExtensionMethods
{
static public General ToGeneral<T>(this T input) where T : struct, IConvertible
{
if (!typeof(T).IsEnum) throw new ArgumentException("Input must be an enum.");
return (General)((int)(object)input & Enum.GetValues(typeof(General)).Cast<int>().Sum());
}
static public T ToEnum<T>(this General input)
{
if (!typeof(T).IsEnum) throw new ArgumentException("Output type must be an enum.");
return (T)Enum.ToObject(typeof(T), (int)input & Enum.GetValues(typeof(T)).Cast<int>().Sum());
}
}
写完这些后,与任何枚举之间的转换就很容易了:
static public TOut Convert<TIn,TOut>(TIn input) where TIn : struct, IConvertible where TOut: struct, IConvertible
{
var general = input.ToGeneral();
return general.ToEnum<TOut>();
}
测试代码:
public static void Main()
{
A valAsA = A.One | A.Three | A.Four;
B valAsB = Convert<A, B>(valAsA); // Should give me "B.Three | B.Four"
C valAsC = Convert<A, C>(valAsA); // Should give me "C.One | C.Three"
Console.WriteLine("{0} should equal {1}", valAsB, (B.Three | B.Four));
Console.WriteLine("{0} should equal {1}", valAsC, (C.One | C.Three));
}
输出:
6 should equal 6
Three should equal Three