Introduction
This article presents yet another C# set class, namely CSet
. CSet
utilizes enum
types as sets of flags. CSet
provides static methods only. The parameters of those methods are of type Object
. As long as those parameters represent integer values or any other type that can function as an integer value, everything works fine. Enum
constants are numeric constants so there should be no problem.
The actual operations are done by converting/typecasting the passed in object parameters to integers and then applying bit-wise operations.
I'm coming from Borland Delphi to C# and I'm just a C# and .NET novice. Delphi provides a set type and I miss that in C#. So I decided to code one.
Well, there is already a C# set class in CodeProject, described in the article "A C# sets class" by Richard Bothne and Jim Showalter, but that Set
class works like a list where you construct a Set
object and then add objects, etc. I needed something more similar to Delphi's set types, so here it is.
Sets explained
A set type is a data type which defines a collection of values of the same base type. C# does not provide a set type but we can simulate set variables.
enum Day { Saturday, Sunday, Monday, Tuesday, Wednesday,
Thursday, Friday };
....
Day WeekEnd = Day.Saturday| Day.Sunday;
Day WorkDays = Day.Monday | Day.Tuesday | Day.Wednesday
| Day.Thursday | Day.Friday ;
It is not meaningful for a value to be included twice in a set, although it is not an error if you do that.
There are some operations we can apply to sets:
- Union is the construction of a new set which contains the elements of two other sets.
- Intersection is the construction of a new set which contains just the common elements of two other sets.
- Difference is the construction of a new set which contains the not common elements of two other sets.
- Membership is a check operation that results in true if a set A is a subset of a set B.
Sets are useful in solving many programming problems, and they promote code clarity and readability.
Not all languages provide a set type. In those cases, one could use carefully assigned integer constants and then apply bitwise operations on them. Bit-wise operations are not an un-common method in C/C++ and Win32 programming.
const int cLeft = 1;
const int cUp = 2;
const int cRight = 4;
const int cDown = 8;
...
int Directions = cRight | cDown;
if ((cRight & Directions) == cRight)
MessageBox.Show("Going right");
.NET FlagsAttribute
attribute and C#'s enum
constant assignment can be used in order to simulate set operations in C# code.
FlagsAttribute class and sets
At the FlagsAttribute
class preview topic, .NET documentation states that "...(the FlagsAttribute
class) Indicates that an enumeration can be treated as a bit field; that is, a set of flags..." and that "...Bit fields can be combined using a bitwise OR operation, whereas enumerated constants cannot...".
Based on the above, the following should not be legal:
enum Day { Saturday, Sunday, Monday, Tuesday, Wednesday, Thursday, Friday };
....
Day A = Day.Friday | Day.Tuesday;
Well, legal or not, it compiles fine.
More one that, one reading the documentation regarding the FlagsAttribute
class, may think that all he needs in order to turn his enum
type into a bit-field (call me set) is just to add that FlagsAttribute
attribute above his type declaration. Well, it takes more than that. You need to carefully assign literal integer values to each of the enum
constants. The first constant = 1, the second = 2, the third = 4, and so on. That way, your enum
constants simulate a bit-mask.
I believe enum
s defined that way should not exceed 32 elements. Not tested though.
Anyway, if you assign those values to your constants the way I described, then you can use OR-ing, AND-ing and XOR-ing against them. And it seems, we get corrects results even if we omit the FlagsAttribute
attribute definition.
enum Day { Saturday = 1, Sunday = 2, Monday = 4, Tuesday = 8,
Wednesday = 16, Thursday = 32, Friday = 64 };
...
Day A = Day.Saturday | Day.Friday | Day.Sunday;
Day B = Day.Sunday;
MessageBox.Show(A.ToString());
bool b = (((int)B & (int)A) == (int)B);
MessageBox.Show(b.ToString());
Of course, to get the most, just apply the FlagsAttribute
attribute to your enum
.
Simulating sets
Now the rest is easy. In order to simulate sets, we just convert those enum
constants to integers and then we apply bit-masking to them. Here are the those operations.
(int)A | (int)B
(int)A & (int)B
(int)((int)A | (int)B) ^ (int)B
(((int)A & (int)B) == (int)A)
We can code a function for each of the above operations. A
and B
should be defined of type Object
. Of course, we need to take some precautions and test the validity of any argument passed.
if ((A == null) || (B == null))
throw(new ArgumentNullException());
if (A.GetType() != B.GetType())
throw(new Exception("Different set types"));
The CSet class
So, now we can have the full class coded using the above. Our CSet
class is going to have just static methods.
public class CSet: object
{
public static object Or(object A, object B)
{
if ((A == null) || (B == null)) throw(new ArgumentNullException());
if (A.GetType() != B.GetType()) throw(new Exception("Different set types"));
return (int)A | (int)B;
}
public static object Union(object A, object B)
{
if ((A == null) || (B == null)) throw(new ArgumentNullException());
if (A.GetType() != B.GetType()) throw(new Exception("Different set types"));
return (int)A | (int)B;
}
public static object And(object A, object B)
{
if ((A == null) || (B == null)) throw(new ArgumentNullException());
if (A.GetType() != B.GetType()) throw(new Exception("Different set types"));
return (int)A & (int)B;
}
public static object Intersection(object A, object B)
{
if ((A == null) || (B == null)) throw(new ArgumentNullException());
if (A.GetType() != B.GetType()) throw(new Exception("Different set types"));
return (int)A & (int)B;
}
public static object Xor(object A, object B)
{
if ((A == null) || (B == null)) throw(new ArgumentNullException());
if (A.GetType() != B.GetType()) throw(new Exception("Different set types"));
return (int)((int)A | (int)B) ^ (int)B;
}
public static object Diff(object A, object B)
{
if ((A == null) || (B == null)) throw(new ArgumentNullException());
if (A.GetType() != B.GetType()) throw(new Exception("Different set types"));
return (int)((int)A | (int)B) ^ (int)B;
}
public static bool In(object A, object B)
{
if ((A == null) || (B == null)) throw(new ArgumentNullException());
if (A.GetType() != B.GetType()) throw(new Exception("Different set types"));
return (((int)A & (int)B) == (int)A);
}
}
Using the code
To use the code, copy the class into a source document, choosing an appropriate namespace and then...
[FlagsAttribute]
enum Day { Saturday = 1, Sunday = 2, Monday = 4, Tuesday = 8,
Wednesday = 16, Thursday = 32, Friday = 64 };
private void button1_Click(object sender, System.EventArgs e)
{
Day A = Day.Wednesday;
Day B = Day.Friday | Day.Tuesday;
Day C = (Day)CSet.Or(A, B);
MessageBox.Show(C.ToString());
C = (Day)CSet.And(Day.Tuesday, B);
MessageBox.Show(C.ToString());
A = Day.Wednesday;
B = Day.Wednesday | Day.Wednesday;
C = (Day)CSet.Xor(A, B);
MessageBox.Show(C.ToString());
A = Day.Wednesday | Day.Tuesday;
B = Day.Wednesday;
MessageBox.Show(CSet.In(B, A).ToString());
}
That's it. I hope you'll find this tiny class useful.
I’m a (former) musician, programmer, wanna-be system administrator and grandpa, living in Thessaloniki, Greece.