Say I have the following enum:
[Flags]
enum IntFlags
{
A = 1 << 0,
B = 1 << 1,
C = 1 << 2,
AAndB = A | B
}
and I want to build a helper method that sets flags like the following:
private static IntFlags Set(IntFlags values, IntFlags target)
{
return target | values;
}
Is there any flexible way to do some sanity checking? Like it should be impossible to set values that don't exist in the enum or are not a combination of existing values.
Stuff like the following should not be possible:
IntFlags flags = 0;
flags = Set(0, flags);
flags = Set((IntFlags)int.MaxValue, flags);
flags = Set(~IntFlags.A, flags);
I guess just doing a check against the existing values like:
private static IntFlags Set(IntFlags values, IntFlags target)
{
if (!Enum.GetValues(typeof(IntFlags)).Cast<IntFlags>().Contains(values))
throw new ArgumentException();
return target | values;
}
won't work because setting values like
IntFlags flags = 0;
flags = Set(IntFlags.A | IntFlags.C, flags);
should be allowed.
I think this might work:
IntFlags Set(IntFlags values, IntFlags target)
{
int intValue;
if (int.TryParse(values.ToString(), out intValue))
{
throw new ArgumentException();
}
...
}
but this relies on the fact that I'm using the Flags attribute on the enum.
Edit
I now found the Enum.IsDefined() method. It works nicely with
Assert.False(Enum.IsDefined(typeof(IntFlags), (IntFlags)int.MaxValue));
Assert.False(Enum.IsDefined(typeof(IntFlags), 0));
Assert.False(Enum.IsDefined(typeof(IntFlags), ~IntFlags.A));
but fails my requirements with combined values
Assert.True(Enum.IsDefined(typeof(IntFlags), IntFlags.A | IntFlags.C)); //fails
There's no way to statically prohibit users of an Enum
from creating an enum representing any value of the underlying type. You can only do such validation at runtime, as you showed in the question.
If you're just looking for a simple enough way of validating, at runtime, if a particular enum value is a valid combination of flags, then that's a simple enough check, assuming the defined enum values are actually increasing powers of two. For such an enum all valid values will go from zero to one less than the next highest power of two, so the check would become:
if((int)values > 0 && (int)values >= 1 << 3))
throw new ArgumentException();
Technically you could remove all possible unmapped values by must mapping every single value, but that's not a particularly practical solution.
All that you're left with if either of those aren't an option is to not use an Enum
, but to instead use your own custom type that has the exact semantics you want:
public class IntFlags
{
public int Value { get; private set; }
private IntFlags(int value)
{
this.Value = value;
}
public static readonly IntFlags A = new IntFlags(1);
public static readonly IntFlags B = new IntFlags(2);
public static readonly IntFlags C = new IntFlags(4);
public static IntFlags operator |(IntFlags first, IntFlags second)
{
return new IntFlags(first.Value | second.Value);
}
public override bool Equals(object obj)
{
return Equals(obj as IntFlags);
}
public override bool Equals(IntFlags obj)
{
return obj != null && Value == obj.Value;
}
}
Having a static IntFlag field where you actually add these values is a good solution and as you realized we do have a Enum.IsDefined
but just FYI, you can go the bit masking way as well. I threw up something together in LINQPad to give you an idea:
BitVector32 is in System.Collections.Specialized
[Flags]
enum IntFlags
{
None = 0,
A = 1 << 0,
B = 1 << 1,
C = 1 << 2,
AAndB = A | B
}
void Main()
{
BitVector32 bv = new BitVector32(0);
int mask1 = BitVector32.CreateMask();
int mask2 = BitVector32.CreateMask(1 << 0);
int mask3 = BitVector32.CreateMask(1 << 1);
int mask4 = BitVector32.CreateMask(1 << 2);
bv[mask1 + mask2 + mask3 + mask4] = true;
//IntFlags flag1 = IntFlags.A | IntFlags.C;
IntFlags flag1 = (IntFlags)int.MaxValue;
if((bv.Data & (int)flag1) == (int)flag1)
"Yes".Dump();
else
"No".Dump();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With