-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Enum.SetFlag and Enum.RemoveFlag to make bitwise flagging easier #14084
Comments
With the current language and runtime capabilities there's no way to implement SetFlag and RemoveFlag efficiently. Even the existing HasFlag has horrible performance compared to the "manual" way of testing flags. |
@yufeih enum is now implemented in, well, not nice way. It's half-struct half-class which can turn out be any type in practice, and framework uses tons of hacks and quirks to make it work like it should. UPD: Okay, looked at it for a bit more... I believe not much can be done here without changing specifications for enum. Now, argument for HasFlags is treated as object, so it requires boxing-unboxing and has corresponding performance hit. |
This is another example where enum constraint on a generic argument (dotnet/roslyn#262) would be useful. It would allow something like T WithFlags<T>( this T source, T flagsToAdd ) where T: enum { /* ... */ }
T WithoutFlags<T>( this T source, T flagsToRemove ) where T: enum { /* ... */ } |
@HellBrick Even with the Some of this came up on the CodePlex Roslyn forums when I proposed supporting an |
As I explained before, the bitwise operations themselves are fine. The problem is with conversions that are needed in certain cases. That's something that could probably be fixed in the runtime, at least in the specific case of enums. |
I agree with this with why I believe we shouldn't add these APIs. @weshaggard @KrzysztofCwalina what are your thoughts? |
I can see how |
@terrajobst, I agree that we don't want to simply add these APIs, but I would put this (efficient flags/bitwise operations) issue on our list of big ticket items that we would like to consider fixing as part of the "modern BCL" push. |
It would make a lot more sense to wait until C# supported generic enum constraints: |
I am not sure how enum constraint would help. The type system still needs to describe the size of the backing integral type to implement many of these operations efficiently. I think something like making enums extend Enum where T: integral type would be needed. |
@KrzysztofCwalina True. Not perfect, but wouldn't an unchecked cast to |
Not sure that would work in all cases either. What if the enum is backed by a double/float? C# doesn't support CLI does. |
@terrajobst is there online documentation covering that fact (CLI supporting non-integral backing types for enums)? Not finding any obvious results with my Google-fu |
Hmm, didn't notice this comment before. There's no such enum, CLI supports only enums of integral type:
The difference between what CLI supports and what C# supports is { bool, char, native int, native unsigned int } |
@mikedn Ah, thanks for the clarification! Are you aware of any CLI-based languages that do allow those underlying types in their spec? |
Nope. But it's not very relevant as it's conceivable that C# would support those types in a future version so it's something that one needs to consider. But those additional type can be casted to That said, casting to |
Not really. Expressions are interpreted rather than compiled in AOT environments such as .NET Native. And even if expressions are compiled the call through delegate cost is much higher than the cost of a "normal" implementation. |
I apologize for the shameless plug but I believe this is pertinent to users. I've just released version 1.0.0 of Enums.NET, a high-performance type-safe .NET enum utility library. It has the extension methods Enums.NET is available on NuGet and is compatible with .NET Standard 1.0+ as well as .NET Framework 2.0+. |
I've created a formal API proposal dotnet/corefx#15453 that addresses this request. |
I wrote this class and it works:
The Enum Class can have the SetFlage and RemoveFlag in the same way. |
There's no need for crazy complicated code to make generic enum methods work - [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasAllFlags<T>(T value, T flags) where T : unmanaged, Enum
{
if (sizeof(T) == 1)
return (Unsafe.As<T, byte>(ref value) | Unsafe.As<T, byte>(ref flags)) == Unsafe.As<T, byte>(ref value);
else if (sizeof(T) == 2)
return (Unsafe.As<T, short>(ref value) | Unsafe.As<T, short>(ref flags)) == Unsafe.As<T, short>(ref value);
else if (sizeof(T) == 4)
return (Unsafe.As<T, int>(ref value) | Unsafe.As<T, int>(ref flags)) == Unsafe.As<T, int>(ref value);
else // size == 8
return (Unsafe.As<T, long>(ref value) | Unsafe.As<T, long>(ref flags)) == Unsafe.As<T, long>(ref value);
} A similar pattern can be applied for other operations. The conditional branches are eliminated by the JIT. |
This isn't required to ship 5.0 |
@danmosemsft Is there any chance we could introduce this API in 6.0? I find myself implementing it in every project I use ;) |
@adamsitnik if you are passionate about it your next step would be to get to api-ready-for-review and represent at API review. |
There should be a non-generic version of the SetFlag/WithFlags/RemoveFlags/WithoutFlags, too. For example: Similar to the Parse/TryParse methods: #14083 |
I would propose the following api to solve this: public class Enum: ValueType
{
+public T SetFlag<T>(T flag, bool condition = true) where T:struct,Enum;
+public T UnsetFlag<T>(T flag) where T:struct,Enum => SetFlag(flag, false);
} Edit: @danmoseley, anything else needed before this api could be ready for review? I wrote this as an extension recently while updating a wpf application that bound several checkboxes to a single flagged enum. This made the properties I bound to on my object model nice and symmetric. public bool IsStolen
{
get => _flags.HasFlag(InventoryFlags.IsFromStolenSource);
set => _flags = _flags.SetFlag(InventoryFlags.IsFromStolenSource, value);
} Unfortunately the implementation itself was super hideous. [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static T SetFlag<T>(this T @enum, T flag, bool on) where T : struct, Enum
{
if (Unsafe.SizeOf<T>() == 1)
{
byte x = (byte)((Unsafe.As<T, byte>(ref @enum) & ~Unsafe.As<T, byte>(ref flag))
| (-Unsafe.As<bool, byte>(ref on) & Unsafe.As<T, byte>(ref flag)));
return Unsafe.As<byte, T>(ref x);
}
else if (Unsafe.SizeOf<T>() == 2)
{
var x = (short)((Unsafe.As<T, short>(ref @enum) & ~Unsafe.As<T, short>(ref flag))
| (-Unsafe.As<bool, byte>(ref on) & Unsafe.As<T, short>(ref flag)));
return Unsafe.As<short, T>(ref x);
}
else if (Unsafe.SizeOf<T>() == 4)
{
uint x = (Unsafe.As<T, uint>(ref @enum) & ~Unsafe.As<T, uint>(ref flag))
| ((uint)-Unsafe.As<bool, byte>(ref on) & Unsafe.As<T, uint>(ref flag));
return Unsafe.As<uint, T>(ref x);
}
else
{
ulong x = (Unsafe.As<T, ulong>(ref @enum) & ~Unsafe.As<T, ulong>(ref flag))
| ((ulong)-(long)Unsafe.As<bool, byte>(ref on) & Unsafe.As<T, ulong>(ref flag));
return Unsafe.As<ulong, T>(ref x);
}
} |
Another possible implementation: https://github.com/rsdn/CodeJam/blob/master/CodeJam.Main/EnumHelper.cs#L375 |
What is the state of this now? Before I start yet another round of API suggestions for Enum.HasAnyFlag() et al. Enum.HasFlag is now inlined without performance penalty and it makes code look better, imo. And as a bonus in DEBUG it comes with a type check. Set/Unset/Has/HasAny will also avoid endless castings to/from int as the compiler is not smart enough to do it automatically. This is 6.5 years old. There should be a decision now if it will be added or dropped. |
@jkotas, any concerns on adding additional APIs to The three core scenarios are "set", "clear", and "toggle". Which could be handled via a single API: public static TEnum SetFlag<TEnum>(TEnum value, TEnum flag, bool value); or a set of APIs: public static TEnum ClearFlag<TEnum>(TEnum value, TEnum flag);
public static TEnum SetFlag<TEnum>(TEnum value, TEnum flag);
public static TEnum ToggleFlag<TEnum>(TEnum value, TEnum flag); names up for debate, as would be whether they are instance or static. |
cc @stephentoub here, since as I recall in a previous issue, he argued against providing alternative syntactic sugar to do basic flags operations. Instead we should just make sure that the standard way of writing such things (which is not C# specific) is efficient. I have to say I agree. When I see one of these methods, I would wonder which one to use. Apologies for overlooking the ping to me higher up. |
We've closed other related proposals, e.g. #66261. @jkotas has also expressed an opinion similar to mine, e.g. #55455 (comment), and also highlighted that just doing the idiomatic bit operations are guaranteed to be efficient where such named methods aren't, e.g. #55455 (comment). |
Closing as per the comments above. |
To maintain multiple bool states in a class, instead of declaring many bool fields, an enum flag can be used to compact these states in to a single member to optimize for memory usage.
It is simple to test if a flag is set using
Enum.HasFlag(DirtyFlags.X)
, but the scenario is not complete. Without the ability to set and remove a flag, the following code needs to be maintained manually:The suggestion is to add 2 methods to Enum to help set and clear a flag.
These methods should be aggressively inlined since bitwise manipulations is very likely to be used in a performance sensitive scenario.
The text was updated successfully, but these errors were encountered: