3

I'm using Reflection to read types inside an assembly (to generate code). I can see that some enumerations should have been marked with the [Flags] attribute but whoever wrote those enums forgot to add this attribute.

Is there any reliable way of detecting when an enum can be considered a "Flags" enum?

My strategy at the moment is to read the enum in descending order, and checking if the value of element(last -1) * 2 == element(last).

That works great in most cases, except when I have enums with 0, 1, and 2 values (which could be flags or not).


edit:

Example of an enum that I'd like to detect as being flags:

public enum EnumIsFlag1
{
    ItemA = 2,
    ItemB = 4,
    ItemC = ItemA + ItemB,
    ItemD = 32,
    ItemE = 64,
}

edit: My question is not a duplicate... The moderator clearly didn't read my question

Lindsey1986
  • 381
  • 1
  • 3
  • 15
  • Wouldn't you check that `element(last-1) * 2 == element(last)`? – Marc L. Dec 07 '15 at 16:26
  • 6
    `[Flags]` just helps the compiler choose appropriate values for the enumeration (typically when they are not specified). Any Enum that has bit-shifted values can be considered appropriate for being used as flags. – Ron Beyer Dec 07 '15 at 16:26
  • 2
    @RonBeyer I'm pretty certain that the `[Flags]` attribute is only really used when formatting the enum value as a string: it has no bearing on the enum member values assigned by the compiler. The second part of your comment is correct however. – Steven Rands Dec 07 '15 at 16:56
  • 1
    What about if you have some combination? e.g. `enum Permissions { Read, Write, Both = Read | Write }`. – Wai Ha Lee Dec 07 '15 at 17:14
  • @StevenRands Seems you are right. I always thought it would bit-shift values but after running a test it looks like it doesn't. Usually I specify the values so I don't run into it myself. – Ron Beyer Dec 07 '15 at 17:47
  • I think using the [Flags] attribute also causes the "HasFlag" method to appear as a method on the enum. Any code to do this is going to require some level of assumption. Why would you not just mark the enum as [Flags] as you find it. If you are trying to modify an enum from a class library, then I would be cautious trying to modify behavior of code that you cannot see. Even if it appears obvious that the enum should have been "flags" that doesn't mean the code will properly treat the passed enum values as such. – Mike U Dec 07 '15 at 18:00
  • @MikeU No, the flags attribute does not cause that method to appear, its there regardless of the flags attribute. – Ron Beyer Dec 07 '15 at 18:11
  • That's fair. Never looked for it on an enum I hadn't marked :) – Mike U Dec 07 '15 at 18:13
  • @WaiHaLee `Permissions.Both` results in *Write*. Either you leave out the assignment, in which case it isn't a proper flag, or you assign proper bit values to the fields. – IS4 Dec 07 '15 at 19:51
  • @IllidanS4 - good spot. I meant to assign values of 1 and 2 to `Read` and `Write`, in which case `Both` is 3. – Wai Ha Lee Dec 07 '15 at 20:47
  • Yes Marc, that is what I currently do. I fixed the question. Thanks – Lindsey1986 Dec 07 '15 at 21:22
  • Ron, that is OK... I just want to (with some certainty) decide if Flags is appropriate or not. I'm generating code from reading metadata and want to put the [Flags] attribute on the generated code if I have enough confidence that it makes sense – Lindsey1986 Dec 07 '15 at 21:24
  • Why do you want to detect this? I cant think of any pracitcal benefit of detecting stuff like this at runtime? Only for some codefix tool or similiar. disregard this comment, you answered in the same moment. – CSharpie Dec 07 '15 at 21:25
  • Hava you tried to use Roslyn? For what you are doing I would choose it. With Roslyn you could check if this **ItemC = ItemA + ItemB** happens. – Alberto Monteiro Dec 08 '15 at 01:50
  • Alberto, I cannot use Roslyn. The code that I am generating is .NET (C#), but the metadata that I'm reading is not .NET code – Lindsey1986 Dec 08 '15 at 18:43

2 Answers2

4

Clearly, this problem can only be solved heuristically but I understand that's what you are after.

Typically, flags enums have most members with a single bit set. So I would count the number of members that have only a single bit set (e.g. that are a power of two).

Then, you can devise a heuristic such as:

//Is this a flags enum?

var totalCount = ...;
var powerOfTwoCount = ...;

if (totalCount < 3) return false; //Can't decide.
if (powerOfTwoCount >= totalCount * 0.95) return true; //Looks like flags
//Probably need some cases for small values of totalCount.

The only reason multiple bits could be set in a legitimate flags enum is combinations of flags. But the number of such enum items is usually small.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Thank you. That is what I'm looking for (heuristics). This works well for perfect flags where all values are power of two, but doesn't work when there are combinations... (added example to my answer) – Lindsey1986 Dec 07 '15 at 21:17
  • What do you think about the heuristic part where I talk about combinations of flags? Clearly, this is impossible to solve in general because every possible number is a combination of some bits. – usr Dec 07 '15 at 21:54
  • I have no doubts that this is the way to go about this problem... What I am struggling with is how to develop a heuristic that is good. Changing the 0.95 to a lower value will give me lots of false positives so I think this needs some tweaking... Possibly a few iterations asking different questions (?) – Lindsey1986 Dec 08 '15 at 18:41
  • I had one more idea. It can't be a flags enum if there exists a value that is multi-bit and is not a combination of existing single bit values. This should be quite common. For example almost all sequences [0,N] do not fall under that category. And almost all non-flags enums are sequences like that. – usr Dec 08 '15 at 19:59
  • Any chance you can update your answer with some code - even pseudo-code? – Lindsey1986 Dec 08 '15 at 21:30
  • I'd first determine the bits that are covered by single bit constants. Simply take all single bit constants and | them together. Then, every multi bit constant that has bits outside of those blessed bits is invalid. – usr Dec 08 '15 at 21:37
  • "determine the bits that are covered by single bit constants" - how do I do that? I'm not familiar with bit shifting... I just know how to use the flags using sum, etc. – Lindsey1986 Dec 08 '15 at 22:26
  • OK, Google for how to determine whether an integer has a single bit set. You should be able to piece this together since, I think, the general idea is clear. Feel free to ask further concrete follow up questions. – usr Dec 08 '15 at 22:34
  • I didn't get very far... I can check which items in the enum are a power of 2 (which I think means they have 1 bit set) but can't tell when they have more than one bit set... Marking it as an answer because ideas were helpful, but would appreciate a more concrete example if you can. – Lindsey1986 Dec 09 '15 at 19:33
  • `can't tell when they have more than one bit set` ??? You know how to test for one bit. So anything else has either zero bits or multiple! What about `!ispoweroftwo(x) && x != 0`? – usr Dec 09 '15 at 20:49
  • Combinations sometimes are not a power of 2... But still valid – Lindsey1986 Dec 12 '15 at 23:13
  • Combinations *never* are a power of two. – usr Dec 12 '15 at 23:17
  • "every multi bit constant that has bits outside of those blessed bits is invalid" -> How do I translate this to code? – Lindsey1986 Dec 12 '15 at 23:19
  • bessedBits & constant != 0 I would say. – usr Dec 12 '15 at 23:21
0

This answer goes into detail about the differences between the two, and they are very minor: just some string formatting behaviour.

The strict answer to your question is that any type can be checked for the flags enum using reflection. Anything else just needs to be checked by a human. You can check for the Flags attribute directly like this;

[Flags]
enum Foo
{
    A = 0,
    B = 1,
    C = 4
}

enum Bar
{
    A = 0,
    B = 1,
    C = 4
}


bool IsFlagsEnum(Type t) 
{
    var attr = t.GetCustomAttributes(typeof(FlagsAttribute), true).FirstOrDefault();
    var result = attr != null;
    return result;
}

Console.WriteLine(IsFlagsEnum(typeof(Foo))); // True
Console.WriteLine(IsFlagsEnum(typeof(Bar))); // False
Community
  • 1
  • 1
Steve Cooper
  • 20,542
  • 15
  • 71
  • 88
  • 2
    "I can see that some enumerations should have been marked with the [Flags] attribute but whoever wrote those enums forgot to add this attribute." did you read the question? – vcsjones Dec 07 '15 at 17:21
  • Yes. There is no algorithm to detect whether an enum should be flags. Both flags and non-flags can contain the same values and a sequence like { 0,1,2,4,8 } doesn't imply a flags enum any more than { 0,1,2,3,4 } implies non-flags. Both are valid either way. All you can do is review the code and decide the meaning for yourself. This code reliably detects, via reflection, if an enum is Flags. – Steve Cooper Dec 07 '15 at 17:27
  • 1
    It does reliably detect if the enum has been *marked* as [Flags], but not if the enum should have been marked, which is what the question is actually about. – Mike U Dec 07 '15 at 17:54
  • 1
    Steve, you misunderstood my question... The attribute is not there so I want to "guess" if the attribute should have been there – Lindsey1986 Dec 07 '15 at 21:20
  • @Lindsay1986 well, that's not something a program can answer. Flags and not-flags can be used in the same way, are numbered the same way, and really only vary by the behaviour of ToString(). A flags enum often has contiguous values like { 0 1 2 3 4 } because it's useful to name the combinations. Maybe you could [use Roslyn to detect if there are any bitwise operators](https://msdn.microsoft.com/en-us/magazine/dn879356.aspx) using the enum type, but that feels heavy! But it's the only operation that flags are designed for. – Steve Cooper Dec 07 '15 at 23:17
  • 1
    Steve, I am aware that a program will not be able to detect with 100% accuracy, but I'm looking for high probability. I cannot use Roslyn because the metadata that I'm reading is not .NET - the code that I am generating is .NET (C#). – Lindsey1986 Dec 08 '15 at 18:42