问题
I have to build an extension method for each flag type I declare, like so:
public static EventMessageScope SetFlag(this EventMessageScope flags,
EventMessageScope flag, bool value)
{
if (value)
flags |= flag;
else
flags &= ~flag;
return flags;
}
Why isn't there an Enum.SetFlag
like there is an Enum.HasFlag
?
Also, why does this not work always?
public static bool Get(this EventMessageScope flags, EventMessageScope flag)
{
return ((flags & flag) != 0);
}
For example, if I have:
var flag = EventMessageScope.Private;
And check it like:
if(flag.Get(EventMessageScope.Public))
Where EventMessageScope.Public
really is EventMessageScope.Private | EventMessageScope.PublicOnly
, it returns true.
When it's not, because Private
is not public, it's just half public.
The same goes for:
if(flag.Get(EventMessageScope.None))
Which returns false
, except the scope is actually None
(0x0
), when it should always return true?
回答1:
The &
operator will give you the same answer with a & b
as it will with b & a
, so
(EventMessaageScope.Private).Get(EventMessageScope.Private | EventMessageScope.PublicOnly)
is the same as writing
(EventMessageScope.Private | EventMessageScope.PublicOnly).Get(EventMessaageScope.Private)
If you just want to know if the value is the same as EventMessaageScope.Public, then just use equals:
EventMessageScope.Private == EventMessageScope.Public
Your method will always return false
for (EventMessageScope.None).Get(EventMessaageScope.None)
because None == 0
and it only returns true when the result of the AND operation is not zero. 0 & 0 == 0
.
回答2:
Why isn't there an Enum.SetFlag like there is an Enum.HasFlag?
HasFlag
as a bitwise operation required more complicated logic and repeating the same flag twice
myFlagsVariable= ((myFlagsVariable & MyFlagsEnum.MyFlag) ==MyFlagsEnum.MyFlag );
so MS decided to implement it.
SetFlag and ClearFlag are concise in C#
flags |= flag;// SetFlag
flags &= ~flag; // ClearFlag
but unfortunately not intuitive. Every time I need to set (or clear) a flag, I'm spending a few seconds (or minutes) to think: what is the name of the method? Why is it not shown in intellisense? Or no, I have to use bitwise operations. Note, that some developers will also ask: what is a bitwise operation?
Should SetFlag and ClearFlag extensions be created - YES to appear in intellisense.
Should SetFlag and ClearFlag extensions be used by developers - NO, because they are not efficient.
We've created extensions in our library's class EnumFlagsHelper like in SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier, but named the function as SetFlag instead of Include and ClearFlag instead of Remove.
In the body of SetFlag methods ( and in summary comment) I decided to add
Debug.Assert( false, " do not use the extension due to performance reason, use bitwise operation with the explanatory comment instead \n
flags |= flag;// SetFlag")
and a similar message should be added to ClearFlag
Debug.Assert( false, " do not use the extension due to performance reason, use bitwise operation with the explanatory comment instead \n
flags &= ~flag; // ClearFlag ")
回答3:
public static class SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier
{
public static T IncludeAll<T>(this Enum value)
{
Type type = value.GetType();
object result = value;
string[] names = Enum.GetNames(type);
foreach (var name in names)
{
((Enum) result).Include(Enum.Parse(type, name));
}
return (T) result;
//Enum.Parse(type, result.ToString());
}
/// <summary>
/// Includes an enumerated type and returns the new value
/// </summary>
public static T Include<T>(this Enum value, T append)
{
Type type = value.GetType();
//determine the values
object result = value;
var parsed = new _Value(append, type);
if (parsed.Signed is long)
{
result = Convert.ToInt64(value) | (long) parsed.Signed;
}
else if (parsed.Unsigned is ulong)
{
result = Convert.ToUInt64(value) | (ulong) parsed.Unsigned;
}
//return the final value
return (T) Enum.Parse(type, result.ToString());
}
/// <summary>
/// Check to see if a flags enumeration has a specific flag set.
/// </summary>
/// <param name="variable">Flags enumeration to check</param>
/// <param name="value">Flag to check for</param>
/// <returns></returns>
public static bool HasFlag(this Enum variable, Enum value)
{
if (variable == null)
return false;
if (value == null)
throw new ArgumentNullException("value");
// Not as good as the .NET 4 version of this function,
// but should be good enough
if (!Enum.IsDefined(variable.GetType(), value))
{
throw new ArgumentException(string.Format(
"Enumeration type mismatch. The flag is of type '{0}', " +
"was expecting '{1}'.", value.GetType(),
variable.GetType()));
}
ulong num = Convert.ToUInt64(value);
return ((Convert.ToUInt64(variable) & num) == num);
}
/// <summary>
/// Removes an enumerated type and returns the new value
/// </summary>
public static T Remove<T>(this Enum value, T remove)
{
Type type = value.GetType();
//determine the values
object result = value;
var parsed = new _Value(remove, type);
if (parsed.Signed is long)
{
result = Convert.ToInt64(value) & ~(long) parsed.Signed;
}
else if (parsed.Unsigned is ulong)
{
result = Convert.ToUInt64(value) & ~(ulong) parsed.Unsigned;
}
//return the final value
return (T) Enum.Parse(type, result.ToString());
}
//class to simplfy narrowing values between
//a ulong and long since either value should
//cover any lesser value
private class _Value
{
//cached comparisons for tye to use
private static readonly Type _UInt32 = typeof (long);
private static readonly Type _UInt64 = typeof (ulong);
public readonly long? Signed;
public readonly ulong? Unsigned;
public _Value(object value, Type type)
{
//make sure it is even an enum to work with
if (!type.IsEnum)
{
throw new ArgumentException(
"Value provided is not an enumerated type!");
}
//then check for the enumerated value
Type compare = Enum.GetUnderlyingType(type);
//if this is an unsigned long then the only
//value that can hold it would be a ulong
if (compare.Equals(_UInt32) || compare.Equals(_UInt64))
{
Unsigned = Convert.ToUInt64(value);
}
//otherwise, a long should cover anything else
else
{
Signed = Convert.ToInt64(value);
}
}
}
}
回答4:
I've done something that works for me and that's very simple...
public static T SetFlag<T>(this Enum value, T flag, bool set)
{
Type underlyingType = Enum.GetUnderlyingType(value.GetType());
// note: AsInt mean: math integer vs enum (not the c# int type)
dynamic valueAsInt = Convert.ChangeType(value, underlyingType);
dynamic flagAsInt = Convert.ChangeType(flag, underlyingType);
if (set)
{
valueAsInt |= flagAsInt;
}
else
{
valueAsInt &= ~flagAsInt;
}
return (T)valueAsInt;
}
Usage:
var fa = FileAttributes.Normal;
fa = fa.SetFlag(FileAttributes.Hidden, true);
回答5:
To answer part of your your question: the Get function works properly according to binary logic - it checks for any match. If you want to match the whole set of flags, consider this instead:
return ((flags & flag) != flag);
Regarding "why isn't there SetFlag"... probably because it's not really needed. Flags are integers. There is already a convention for dealing with those and it applies to flags as well. If you don't want to write it with |
and &
- that's what the custom static addons are for - you can just use your own functions as you demonstrated yourself :)
回答6:
Enums got borked by the C language a long time ago. Having a modicum of type safety in the C# language was important to the designers, leaving no room for an Enum.SetFlags when the underlying type can be anything between a byte and a long. Another C induced problem btw.
The proper way to deal with it is to write this kind of code inline explicitly and not try to shove it into an extension method. You don't want to write a C macro in the C# language.
回答7:
Here is another quick and dirty way to SetFlag for any Enum:
public static T SetFlag<T>(this T flags, T flag, bool value) where T : struct, IComparable, IFormattable, IConvertible
{
int flagsInt = flags.ToInt32(NumberFormatInfo.CurrentInfo);
int flagInt = flag.ToInt32(NumberFormatInfo.CurrentInfo);
if (value)
{
flagsInt |= flagInt;
}
else
{
flagsInt &= ~flagInt;
}
return (T)(Object)flagsInt;
}
回答8:
The reason I'm finding is that since enum is a value type, you cannot pass it in and set its type. To all of you that think its stupid, I say this to you: Not all developers understand bit flags and how to turn them on or off (which is much less intuitive).
Not a stupid idea, just not possible.
来源:https://stackoverflow.com/questions/5850873/enum-hasflag-why-no-enum-setflag