Het zal mijn Borland verleden zijn, maar ik vond de implementatie van sets in C# nogal zwak. Delphi heeft daar een mooie oplossing voor.
type
TColor = ( clRed, clGreen, clBlack );
//c# public enum Color (Red, Green, Black)
TColors = set of TColor;
// in c# bestaat dit niet, je kan hooguit het [Flags] attribuut gebruiken.
In Delphi zijn het dus twee verschillende typen (en terecht: een element is geen verzameling en vice versa!). Maar in C# mag je gaan
ANDen en ORen met bitflags. Nogal low-level, en ja, ik snap het allemaal wel, maar leesbaar? Nee, dat absoluut niet.
[Flags]
public enum TestEnum
{
Value1 = 1,
Value2 = 2
}
En dat krijg je dus dit soort code om te testen of een element in een verzameling zit. Want dat is toch wat je uiteindelijk doet...
TestEnum v = TestEnum.Value1 | TestEnum.Value2;
if ( ( v & TestEnum.Value1 ) == TestEnum.Value1 )
{
//
}
En ik zou gewoon dit willen:
TestEnum v = TestEnum.Value1 | TestEnum.Value2;
if ( TestEnum.Value1 in v )
{
//
}
Maar goed, dat zal niet lukken. Tenminste niet zonder toe te treden tot het C#-compiler team bij Microsoft :-). Maar toen bedacht ik me dat een extension method dit misschien wel eens zou kunnen gaan oplossen:
public static class SetHelper
{
public static bool In( this System.Enum value, Enum flags )
{
Int64 flagInt = ( (IConvertible) flags ).ToInt64( null );
Int64 valueInt = ( (IConvertible) value ).ToInt64( null );
return ( flagInt & valueInt ) != 0;
}
}
Nu ik toch Delphi aan het promoten ben: extension methods is ontsproten aan het brein van Chuck Jazdzewski (yep, dat was een copy en paste-actie). Ooit lang geleden geimplementeerd als class helpers, en nu sinds het 3.5 Framework ook in .NET. Maar goed, met die extension method kom je al een heel eind.:
TestEnum v = TestEnum.Value1 | TestEnum.Value2;
if ( TestEnum.Value1.In( v ) )
{
//
}
En zo heb ik ook de Contains, IsSubsetOf en de IsSupersetOf gemaakt. Maar het werd pas echt grappig toen ik de Union wilde implementeren. Dit is mijn eerste poging:
public static Enum Union( this Enum set1, Enum set2 )
{
Int64 set1Value = ( (IConvertible) set1 ).ToInt64( null );
Int64 set2Value = ( (IConvertible) set2 ).ToInt64( null );
return (Enum) Enum.ToObject( set1.GetType(), ( set1Value | set2Value ) );
}
Maar dat betekende wel dat ik altijd moest gaat casten. Bovendien kon je niet garanderen dat je twee dezelfde enum typen opgaf als argumenten. Dat moest beter kunnen! Dus maar eens gekeken of ik een generic extension method kon maken. En dat blijkt geen enkel probleem te zijn. LINQ is ook grotendeels opgebouwd uit generic extension methods op interfaces:
public static T Union<T>( this T set1, T set2 )
where T : struct, IConvertible //compiler doesn't allow System.Enum
{
Int64 set1Value = ( (IConvertible) set1 ).ToInt64( null );
Int64 set2Value = ( (IConvertible) set2 ).ToInt64( null );
return (T) Enum.ToObject( typeof( T ), ( set1Value | set2Value ) );
}
public static T Intersect<T>( this T set1, T set2 )
where T : struct, IConvertible //compiler doesn't allow System.Enum
{
Int64 set1Value = ( (IConvertible) set1 ).ToInt64( null );
Int64 set2Value = ( (IConvertible) set2 ).ToInt64( null );
return (T) Enum.ToObject( typeof( T ), ( set1Value & set2Value ) );
}
En dan houden we dit soort code over:
v = TestEnum.Value1;
v = v.Union<TestEnum>( TestEnum.Value2 );
MessageBox.Show( TestEnum.Value2.In( v ) ? "Value2 is set in v" : "Value2 is not set in v" );
Ik ben al redelijk tevreden omdat ik zo een stuk leesbaarder met sets kan omgaan in C#. Iemand nog ideeen hoe we dit nog cleaner kunnen krijgen?