Union of enum members in C# - c#

Let us say, I have an enum BasicType, which is defined as follows:
public enum ObjectType{
A = 1,
B = 2,
C = 3,
}
The BasicType identifies performs a ternary classification of any Object. Subsequently, I realized that the objects A and B need to be treated in a similar way as compared to C, so I defined another enum ObjectGroupType as follows :
public enum ObjectGroupType
{
AB = 1,
C = 2,
}
With the new enum, I am able to bucket objects of several known types as one. So, when I receive a stream of objects in various types, I actually identify whether they belong to AB or C type. Is there an elegant workaround for this? For instance, will I be able to assign the same enum values for A and B in the ObjectGroupType?:
Edit 1 : I am unable to find the resemblance to the question here
Edit 2 : Thank you Maurice for your constructive inputs -- taking cues from your answer, I came up with this redefined ObjectGroupType.
public enum ObjectGroupType
{
AB = ObjectType.A | ObjectType.B
C = 2,
}
Is this valid?
Essentially, when I process a stream of objects of type AB, I want to ascertain Type A or Type B objects. This is quite similar to a hierarchical two-level decision tree:
object
/ \
AB C
/\
A B

I apologize in advance if I misread your intent, but it almost sounds like you want to allow multiple different enum types to be acted on in your code based on the enum value. The good thing is that you can do that already with bitwise operations and enums.
Given an enum that looks like this:
[Flags]
enum ObjectType
{
A = 1,
B = 2,
C = 4,
D = 8
}
You can set a comparison value that is the bitwise OR of several values:
var allowedValues = ObjectType.A | ObjectType.C;
This works because the values in the enum act like bit fields under the covers.
When you run your code, you do a bitwise AND on the allowedValues variable and the test variable and see if it matches your test variable. If it does, then it is one of the values you want:
if ((test & allowed) == test) ...
Below is a working example using the enum above that shows you how it works.
void Main()
{
var allowed = ObjectType.A | ObjectType.C;
var values = new int [] { 1, 2, 4, 8 };
foreach (var i in values)
{
var test = (ObjectType)i;
if ((test & allowed) == test)
{
Console.WriteLine("Found a match: {0}", test);
}
else
{
Console.WriteLine("No match: {0}", test);
}
}
}
Best of luck!

Edit:
I found the answer of Maurice Reeves very good, I only want to bring some more info:
[Flags]
public enum ObjectType
{
None=0,
A = 1,
B = 2,
C = 4,
D = 8,
E = 16,
AorB=A|B,
BorCorD=B|C|D,
}
By using [Flags] attribute, you can create sets of enum items, which can help you establishing different business rules for each set.
In order to check if and item exist in a set you can do as follow:
public static bool IsAorB(this ObjectType item)
{
return ObjectType.AorB.HasFlag(item);
}
if you want to creat on the fly new set of items, you can do:
var newGroup=ObjectType.A | ObjectType.BorCorD;
if you want to apply some business rule to a set, except an item, you can do:
var newGroupExceptC =newGroup^=ObjectType.C;
Now if you check if element C exist in the set you will get false:
bool exist=newGroupExceptC.HasFlag(ObjectType.C) // =false
more info you can find here

You might use a int instead of an enum, use values that don't overlap when combined (i.e. values whose binary representation has only one bit on) and then perform a mask operation on the ObjectType of a parameter to determine if it is AB:
class SomeClass
{
public static class ObjectType
{
public const int A = 1;
public const int B = 2;
public const int C = 4;
public const int D = 8;
}
public int MyType;
public string Title;
static void Main(string[] args)
{
List<SomeClass> list = new List<SomeClass>()
{
new SomeClass() {Title ="I am of type A", MyType = ObjectType.A }
,new SomeClass() {Title ="I am of type B", MyType = ObjectType.B }
,new SomeClass() {Title ="I am of type AB", MyType = ObjectType.A | ObjectType.B }
};
list.ForEach(p => { if (p.MyType == (ObjectType.A | ObjectType.B)) Console.WriteLine(p.Title); });
}
}
The downside of this approach is losing strong-typing of Object Type, i.e. you can assign any value not just those you define in the ObjectType.

Related

C# reflections with enum as string

I have a structure that contains an enum:
public enum MyEnum
{
happy = 0,
sad
}
public struct MyStruct
{
public MyEnum feelings;
public int boopCounter;
}
and then I am given a text/string version of a structure and its contents:
feelings = sad
boopCounter = 12
I am trying to write a generic parser that can generate a struct object with the correctly populated fields, without having to modify the parser every time the structure gets updated. I have been generally successful using Reflections:
// Scan through each member of the structure, looking for a match
foreach (var field in typeof(MyStruct).GetFields(System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Public))
{
if(field.Name == fieldNameFromText)
{
// This is the member we need to update. Treat enums differently
var fld = typeof(MyStruct).GetField(field.Name);
if(field.FieldType.IsEnum)
{
//fld.SetValue(s, Enum.ToObject(field.FieldType, valFromText)); // Didn't work
fld.SetValue(s, Convert.ChangeType(Enum.ToObject(field.FieldType, Convert.ToInt32(valFromText)), field.FieldType)); // Worked, but only if integer version of enum is passed in
}
else
{
// Not an enum, parse directly.
fld.SetValue(s, Convert.ChangeType(valFromText, field.FieldType));
}
break;
}
}
So this code works, but it only works if my input text contains the integer version of the enum:
feelings = 1
boopCounter = 12
Is there a way to get this to work with the original string enumeration input ("sad") ? I'd like to keep it generic if possible (notice how my code doesn't ever specifically call out "MyEnum" anywhere).
Yes, you can use the non-generic version of Enum.Parse.
var fieldType = fld.FieldType;
if (fieldType.IsEnum)
{
var valueToAssign = Enum.Parse(fieldType, valFromText);
fld.SetValue(s, valueToAssign);
}

Enum with int value?

I want to make an enum for possible grades. This is a working example:
public enum Grade
{
A, B, C, D, E, F
}
However, I want the grades to be integers, like
public enum Grade
{
1, 2, 3, 4, 5
}
Why does the first one work but not the second? How can I make a similar variable that can only take values from 1-5 (and is nullable)?
You should specify Grade like this.
public enum Grade
{
A = 1,
B,
C,
D,
E,
F
}
B, C and so on will take next value.
enum elements need to have valid C# identifiers; 1, 2, 3 etc are not valid C# identifiers, so no: you can't do that. You can perhaps use One = 1 etc, or some common prefix (Grade1), but...
C# requires enum constants to be identifiers, i.e. start in a letter/underscore, and include only letters, underscores, and digits.
You have multiple options to deal with this:
Spell out the numbers - i.e. Grade.One, Grade.Two, etc.
Prefix the number with a letter, or
Prefix the number with an underscore.
In my opinion, the first option is the best, because it reads very well:
enum Grade {
One = 1
, Two
, Three
, Four
, Five
}
The last option looks odd, but if you your mind is absolutely set on using numbers, this is as close as you can get to it:
enum Grade {
_1 = 1 // Without =1 the value of _1 would be zero
, _2
, _3
, _4
, _5
}
As others have said, the names you want to use for your enum of {1, 2, 3, etc} are invalid because they're not valid C# identifiers.
If you need those values out of your enum, you can do the following:
// Declare enum
public enum Grade
{
A = 1,
B,
C,
D,
F
}
Then, when you need to access the value of say, Grade.B, you can do that like this:
int theIntGrade = (int)Grade.B // after this line, theIntGrade will be equal to 2
Note that if you had a grade as a string, such as "C", you could convert it into an enumeration value like this:
Grade theLetterGrade = (Grade)Enum.Parse(typeof(Grade), "C", true); // final parameter sets case sensitivity for comparison
How can I make a similar variable that can only take values from 1-5 (and is nullable)?
Make your own type:
public struct Grade: IEquatable<Grade>
{
private int innerValue;
private int InnerValue => isInitialized ? innerValue : 1;
private readonly bool isInitialized;
private Grade(int value)
{
if (value < 1 || value > 5)
throw new OverflowException();
innerValue = value;
isInitialized = true;
}
public static implicit operator Grade(int i) => new Grade(i);
public static explicit operator int(Grade g) => g.InnerValue;
public override bool Equals(object obj) => obj is Grade && Equals((Grade)obj);
public bool Equals(Grade other) => InnerValue == other.InnerValue;
public override int GetHashCode() => InnerValue.GetHashCode();
public override string ToString() => InnerValue.ToString();
public static bool operator ==(Grade left, Grade right) => left.Equals(right);
public static bool operator !=(Grade left, Grade right) => !left.Equals(right);
}
Now you have a type than can only hold 1, 2, 3, 4 and 5 and defaults to 1. Initializing it is as simple as Grade g = 4;.
You need it to be nullable? No sweat: Grade? g = 4;.

How to get enum order?

If I have enum:
public enum ImportState : byte
{
None = 0,
ImportedWithChanges = 44,
AwaitingApproval = 45,
Removing = 66,
Revalidating = 99,
};
How to get enum order?
For example:
GetOrder(ImportState.None)
Should return 1(first in order)
GetOrder(ImportState.AwaitingApproval )
Should return 3 (third in order)
here is the missing method GetOrder
public static int GetOrder(ImportState State)
{
return Enum.GetValues(typeof(ImportState)).Cast<ImportState>().Select((x, i) => new { item = x, index = i }).Single(x => x.item == State).index;
}
As other noticed, Enum.GetValues() returns the values of an enum sorted by value. Perhaps this isn't what you wanted... So, using a little reflection:
public class EnumOrder<TEnum> where TEnum : struct
{
private static readonly TEnum[] Values;
static EnumOrder()
{
var fields = typeof(Values).GetFields(BindingFlags.Static | BindingFlags.Public);
Values = Array.ConvertAll(fields, x => (TEnum)x.GetValue(null));
}
public static int IndexOf(TEnum value)
{
return Array.IndexOf(Values, value);
}
}
Example of use:
public enum Values
{
Foo = 10,
Bar = 1
}
int ix = EnumOrder<Values>.IndexOf(Values.Bar); // 1
Note that the C# specifications aren't clear if the "source code" ordering of an enum is maintained in the compiled program... At this time the C# compiler seems to maintain it, but there is no guarantee in the future...
The only two references I've found are:
Forward declarations are never needed in C# because, with very few exceptions, declaration order is insignificant
and
Declaration order for enum member declarations (§14.3) is significant when constant-expression values are omitted.
So as written, for the example I gave, the ordering is undefined and depends on the C# compiler!
Enumerating the enum values, casting to an IEnumerable, converting to a List. This it is a simple matter of using IndexOf().
Note that for this to work, the enum must be declared in increasing order.
namespace ConsoleApplication1
{
using System;
using System.Linq;
class Program
{
public enum ImportState : byte
{
None = 0,
ImportedWithChanges = 44,
AwaitingApproval = 45,
Removing = 66,
Revalidating = 99,
}
static void Main(string[] args)
{
Console.WriteLine(GetOrder(ImportState.None));
Console.WriteLine(GetOrder(ImportState.AwaitingApproval));
}
public static int GetOrder(ImportState state)
{
var enumValues = Enum.GetValues(typeof(ImportState)).Cast<ImportState>().ToList();
return enumValues.IndexOf(state) + 1; // +1 as the IndexOf() is zero-based
}
}
}
1
3
Press any key to continue . . .
Sth. like this?
int i = 0;
foreach (ImportState state in Enum.GetValues(typeof(ImportState)))
{
i++;
if (state == myState) return i;
}
However there is no real use for this, as enums do not provide an indexed enumeration in themselfes. They represent a value which is more what you´re probably after.
You can use this LINQ query:
int position = Enum.GetValues(typeof(ImportState)).Cast<ImportState>()
//.OrderBy(impState => (int)impState)
.TakeWhile(impState => impState != ImportState.None)
.Count() + 1;
It orders by the int-value of the enum-value, then it takes all until the searched value and counts them. I have omitted the OrderBy since Enum.GetValues automatically returns the order according to their int-value.
MSDN:
The elements of the array are sorted by the binary values of the
enumeration constants
Instead of using the order, you would be better to make better use of the flags. Consider the following
public enum ImportState : byte
{
None = 0,
ImportedWithChanges = 2,
AwaitingApproval = 4,
Removing = 6,
Revalidating = 8,
};
(double)state / Enum.GetValues(typeof(ImportState)).Cast<byte>().Max()
Example
Enums don't really have any sense of ordering, using the above probably still isn't perfect but it doesn't involve a made up order.
What about this Solution?
var result = from r in Enum.GetValues<ImportState>()
let expression =
r == ImportState.Revalidating
? 0
: r == ImportState.AwaitingApproval
? 1
: r == ImportState.Removing
? 2
: r == ImportState.ImportedWithChanges
? 3
: 4
orderby expression ascending
select r.ToString();
Console.WriteLine(string.Join(", ", result));
Output: Revalidating, AwaitingApproval, Removing, ImportedWithChanges, None

Pass multiple enum to methods and get their values

Please consider this code:
public enum Status
{
S1 = 1,
S2 = 2,
S3 = 3,
S4 = 4
}
I know I can pass multiple enum using | oerator to a method:
public void DoWork(Status S)
{
}
...
DoWork(Status.S1 | Status.S2);
Now In DoWork Method I want to get values of passed enums. For Example In above code I want to get {1, 2}. How I cas do this?
thanks
Here are few steps to follow to get flagged enum :
Use 2 exp (n) integer (1, 2, 4, 8, 16, 32, ...)
to define your enum. Why ? : Actually each active state of your
enum will take a single bit of a 32 bits integer.
Add the Flags attribute.
Then,
[Flags]
public enum Status
{
S1 = 1,
S2 = 2,
S3 = 4,
S4 = 8
}
You can use Enum.HasFlag to check if a specific status is active :
public void DoWork(Status s)
{
var statusResult = Enum.GetValues(typeof(Status)).Where(v => s.HasFlag(v)).ToArray() ;
// StatusResult should now contains {1, 2}
}
Declare your parameters with the params tag, then you get an array of enums:
public void DoWork (params Status[] args) {
Console.WriteLine(args.Length);
}
Then you just pass them in as regular parameters:
DoWork(Status.S1, Status.S2);
This doesn't require changes to your enums, and easily copes with additional values. The flags solution above may work for you as well - depends on your requirements.
If this is the Integer values of your enums that you want you can use this :
//declare your enum as byte
public enum Status : byte
{...}
**EDITED**
//then cast it to int to use the value
DoWork((int)Status.S1 , (int)Status.S2);
//change doWork to accept int[]
public void DoWork (params int[] args)

How to get the Enum Index value in C#

In C, enums, internally equates to an integer. Therefore we can treat data types of enum as integer also.
How to achieve the same with C#?
Firstly, there could be two values that you're referring to:
Underlying Value
If you are asking about the underlying value, which could be any of these types: byte, sbyte, short, ushort, int, uint, long or ulong
Then you can simply cast it to it's underlying type. Assuming it's an int, you can do it like this:
int eValue = (int)enumValue;
However, also be aware of each items default value (first item is 0, second is 1 and so on) and the fact that each item could have been assigned a new value, which may not necessarily be in any order particular order! (Credit to #JohnStock for the poke to clarify).
This example assigns each a new value, and show the value returned:
public enum MyEnum
{
MyValue1 = 34,
MyValue2 = 27
}
(int)MyEnum.MyValue2 == 27; // True
Index Value
The above is generally the most commonly required value, and is what your question detail suggests you need, however each value also has an index value (which you refer to in the title). If you require this then please see other answers below for details.
Another way to convert an Enum-Type to an int:
enum E
{
A = 1, /* index 0 */
B = 2, /* index 1 */
C = 4, /* index 2 */
D = 4 /* index 3, duplicate use of 4 */
}
void Main()
{
E e = E.C;
int index = Array.IndexOf(Enum.GetValues(e.GetType()), e);
// index is 2
E f = (E)(Enum.GetValues(e.GetType())).GetValue(index);
// f is E.C
}
More complex but independent from the INT values assigned to the enum values.
By default the underlying type of each element in the enum is integer.
enum Values
{
A,
B,
C
}
You can also specify custom value for each item:
enum Values
{
A = 10,
B = 11,
C = 12
}
int x = (int)Values.A; // x will be 10;
Note: By default, the first enumerator has the value 0.
You can directly cast it:
enum MyMonthEnum { January = 1, February, March, April, May, June, July, August, September, October, November, December };
public static string GetMyMonthName(int MonthIndex)
{
MyMonthEnum MonthName = (MyMonthEnum)MonthIndex;
return MonthName.ToString();
}
For Example:
string MySelectedMonthName=GetMyMonthName(8);
//then MySelectedMonthName value will be August.
Use simple casting:
int value = (int) enum.item;
Refer to enum (C# Reference)
Use a cast:
public enum MyEnum : int {
A = 0,
B = 1,
AB = 2,
}
int val = (int)MyEnum.A;
using System;
public class EnumTest
{
enum Days {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};
static void Main()
{
int x = (int)Days.Sun;
int y = (int)Days.Fri;
Console.WriteLine("Sun = {0}", x);
Console.WriteLine("Fri = {0}", y);
}
}
One reason that the designers c# might have chosen to NOT have enums auto convert was to prevent accidentally mixing different enum types...
e.g. this is bad code followed by a good version
enum ParkingLevel { GroundLevel, FirstFloor};
enum ParkingFacing { North, East, South, West }
void Test()
{
var parking = ParkingFacing.North; // NOT A LEVEL
// WHOOPS at least warning in editor/compile on calls
WhichLevel(parking);
// BAD wrong type of index, no warning
var info = ParkinglevelArray[ (int)parking ];
}
// however you can write this, looks complicated
// but avoids using casts every time AND stops miss-use
void Test()
{
ParkingLevelManager levels = new ParkingLevelManager();
// assign info to each level
var parking = ParkingFacing.North;
// Next line wrong mixing type
// but great you get warning in editor or at compile time
var info=levels[parking];
// and.... no cast needed for correct use
var pl = ParkingLevel.GroundLevel;
var infoCorrect=levels[pl];
}
class ParkingLevelInfo { /*...*/ }
class ParkingLevelManager
{
List<ParkingLevelInfo> m_list;
public ParkingLevelInfo this[ParkingLevel x]
{ get{ return m_list[(int)x]; } }}
In answering this question I define 'value' as the value of the enum item, and index as is positional location in the Enum definition (which is sorted by value). The OP's question asks for 'index' and various answer have interpreted this as either 'index' or 'value' (by my definitions). Sometimes the index is equal to numerical value.
No answer has specifically addressed the case of finding the index (not value) where the Enum is an Enum flag.
Enum Flag
{
none = 0 // not a flag, thus index =-1
A = 1 << 0, // index should be 0
B = 1 << 1, // index should be 1
C = 1 << 2, // index should be 2
D = 1 << 3, // index should be 3,
AandB = A | B // index is composite, thus index = -1 indicating undefined
All = -1 //index is composite, thus index = -1 indicating undefined
}
In the case of Flag Enums, the index is simply given by
var index = (int)(Math.Log2((int)flag)); //Shows the maths, but is inefficient
However, the above solution is
(a) Inefficient as pointed out by #phuclv (Math.Log2() is floating point and costly) and
(b) Does not address the Flag.none case, nor any composite flags - flags that are composed of other flags (eg the 'AandB' flag as in my example).
DotNetCore
If using dot net core we can address both a) and b) above as follows:
int setbits = BitOperations.PopCount((uint)flag); //get number of set bits
if (setbits != 1) //Finds ECalFlags.none, and all composite flags
return -1; //undefined index
int index = BitOperations.TrailingZeroCount((uint)flag); //Efficient bit operation
Not DotNetCore
The BitOperations only work in dot net core. See #phuclv answer here for some efficient suggestions https://stackoverflow.com/a/63582586/6630192
#user1027167 answer will not work if composite flags are used, as per my comment on his answer
Thankyou to #phuclv for suggestions on improving efficiency

Categories

Resources