Bitwise operations are important when dealing with permissions. In this case the permissions are designed as flags and the operations described will include the scenario where one variable can have one or multiple permissions, all described in the enum.
The following enum defines a set of permissions for a particular operation.
[Flags] public enum Flags : long { T1_CanDelete = 2 << 0, T1_CanEdit = 2 << 1, T2_CanDelete = 2 << 2 }
Or Operation
To define a variable that has multiple permissions:
var op1 = Flags.T1_CanDelete | Flags.T1_CanEdit;
To have a visual understanding of what’s happening you can use the following command:
Console.WriteLine(Convert.ToString((int)Flags.T1_CanDelete, 2).PadLeft(8, '0')); Console.WriteLine(Convert.ToString((int)Flags.T1_CanEdit, 2).PadLeft(8, '0')); Console.WriteLine(Convert.ToString((int)op1, 2).PadLeft(8, '0'));
The output of this is easy now to understand, each pair of bits are set to 1 if either bit is 1
00000010 00000100 00000110
And Operation
To determine if a variable has a certain flag, we can use the builtin method
Console.WriteLine(op1.HasFlag(Flags.T1_CanDelete));
But this is not the most efficient way to determine, it’s not as performant as actually checking the bits. To do this we have to perform an “and” operation using the target flag and check if the result greater then 0
op1 & Flags.T1_CanDelete != 0
The and operation will take each pair of bits and will set the value to 1 only of both pairs are 1. The combined “op1” will have “1” set on 2 columns, and one of them belongs to the flag that we are testing for. The flag Flags.T1_CanDelete has only 1 “1” value set. You can see in the image below that if we use a flag (e.g. 10000000) which does not have a correspondant “1” on the tested variable, the result is 0.
Console.WriteLine(Convert.ToString((int)op1, 2).PadLeft(8, '0')); Console.WriteLine(Convert.ToString((int)Flags.T1_CanDelete, 2).PadLeft(8, '0')); Console.WriteLine(Convert.ToString((int)(op1 & Flags.T1_CanDelete), 2).PadLeft(8, '0')); -->output 00000110 (op1) 00000010 (Flags.T1_CanDelete) 00000010 (op1 & Flags.T1_CanDelete)
Removing a flag (wrong)
The xor (^) cannot be used to remove a flag. Xor acts like a toggle, it sets 1 if it finds 0, and it sets 0 if it finds 1.
This means that if we apply an xor to op1 twice using the same flag, we end up having the same original value. You can see in the test below that the second and forth output are the same.
Console.WriteLine(Convert.ToString((int)Flags.T1_CanDelete, 2).PadLeft(8, '0')); Console.WriteLine(Convert.ToString((int)op1, 2).PadLeft(8, '0')); op1 = op1 ^ Flags.T1_CanDelete; Console.WriteLine(Convert.ToString((int)op1, 2).PadLeft(8, '0')); op1 = op1 ^ Flags.T1_CanDelete; Console.WriteLine(Convert.ToString((int)op1, 2).PadLeft(8, '0')); -->output 00000010 (Flags.T1_CanDelete) 00000110 (op1) 00000100 (op1 ^ Flags.T1_CanDelete) 00000110 (op1 ^ Flags.T1_CanDelete)
Removing a flag (good)
To properly remove a flag we must use the masking operator. The masking operation will “reverse” the values of the target flag, it makes all 0 bits into 1 and all 1 bits into 0. This masked flag if it’s added using “and” to the original value, will make sure that the target flag (now 0) will not be included in the result
op1 = Flags.T2_CanDelete | Flags.T1_CanEdit | Flags.T2_CanDelete; Console.WriteLine(Convert.ToString((int)op1, 2).PadLeft(8, '0')); Console.WriteLine(Convert.ToString((int)Flags.T2_CanDelete, 2).PadLeft(8, '0')); Console.WriteLine(Convert.ToString((int)~Flags.T2_CanDelete, 2).PadLeft(8, '0')); Console.WriteLine(Convert.ToString((int)((~Flags.T2_CanDelete) & op1), 2).PadLeft(8, '0')); --> output 00001100 (op1) 00001000 (flag) 11110111 (masked flag) 00000100 (result)