GenericEnum - An Enum of Any Underlying Value Type for .NET V1.0.0
Enums in .NET are highly useful abstractions over simple constant values. However, the underlying value type of enums are restriced to integer types (int, long, etc.), which diminishes their usability in many cases. Additionally, operators, such as equality and inequality, are not defined between enums of different types. This problem is then often solved by falling back on constant values, but this has significant downsides. For example, method signatures can not determine what values are to be permitted and what values are not. All in all, codebases will soon become plagued by magic strings and magic numbers. GenericEnum intends to fix this problem, by relaxing the constraints imposed on enums and allowing any underlying value type as well as defining sensible operators.
Install the Nuget package.
See more detailed Usage instructions.
GenericEnum
- Generic Value Type
- Comparison Between Different Types
- Operators
- Performant Comparison
Enum
- Generic Value Type
- Comparison Between Different Types
- Operators
- Performant Comparison
✓ Feature Supported ◻ Feature Partially Supported ✕ Feature Not Supported
Important Features and Restrictions
This section is a summary of the most important features and restrictions of GenericEnum. Make sure that you understand these before applying GenericEnum in your own project.
Defining a Custom GenericEnum
When defining a custom GenericEnum, you should always inherit GenericEnumBase<T, TValue>
and
not GenericEnum
or GenericEnumValue<TValue>
. These "extra" classes are required
for cases in which different types are compared.
Maximum Number of Distinct Values
Currently, the maximum number of distinct values that a GenericEnum can define is 64. However, if
the underlying value type can be null
(in other words, the underlying value is a class),
then one value is reserved for null
. The restriction is based upon the number of bits in
an ulong
. Storing a "bitflag" makes the comparison of two GenericEnums of the same type
really fast. This limitation may be relaxed in a later release.
Underlying InternalValue
A GenericEnum has an InternalValue
property if and only if it was explicitly instantiated with one.
Check for this with the property HasValue
.
Underlying ValueSet
Every GenericEnum has an underlying ValueSet
property, which is a set of GenericEnums. This set contains
all associated values of a GenericEnum. If the underlying InternalValue
is defined, then its corresponding
GenericEnum is also contained here. For example, applying the union operator on 1 and 2, will result in {1, 2}.
Underlying Value
GenericEnums whose type can explicitly be defined can have a Value
property. This is just InternalValue
correctly casted to its true type. A GenericEnum can have a Value
property if and only if HasValue
is true.
Equality
The equality of two GenericEnums is based on the equality of the underlying ValueSet
.
In other words, the equality is (in practice) defined as:
GenericEnum0.ValueSet.SetEquals(GenericEnum1.ValueSet)
, where the individual GenericEnums are
then compared with A.InternalValue.Equals(B.InternalValue)
. The comparison of InternalValues
can then be based on reference or value or something else. For example, if one GenericEnum has
an InternalValue
of 1
(type: int) and another has an InternalValue
of 1.0
(type: double), then due to .NET equality conventions this will evaluate to true
.
InternalValue Mutability
The InternalValue
of a GenericEnum can not be changed. However, since the InternalValue
can be of any type, for example, a custom class, the its state may be changed. Changing the
underlying state (especially if it affects equality comparisons) is forbidden and will probably result
in incorrect GenericEnum behavior. Unfortunately, this can not be enforced and no actual errors can
be generated.
Instantiation & Constructor Visibility
When creating a custom GenericEnum (works with inheritance), the constructor should be marked either
private
or protected
. It does not make sense to generate GenericEnums during
runtime outside the initialization phase. Distinct GenericEnums should be created as static fields or
properties within the class itself. All static fields and properties of the same type as the custom
GenericEnum will then be stored within the static property Values
. See the examples below for more details.
Resulting Type After Applied Operator
GenericEnum allows one to apply operators on different types. The resulting type after each operator is the one that contains the most information. If an operator is applied on two GenericEnums of the same type, then the result is also of the same type. If an operator is applied on two GenericEnums of different types, but with the same underlying value type, then the information of the underlying value type is maintained. However, if an operator is applied on two GenericEnums of different types with different underlying value type, then the resulting type won't maintain any information about types. See the section about operators under Examples for examples.
Performance
The performance of GenericEnum is optimized for the case of comparison. Comparing two GenericEnums of the same type is really fast (basically as fast as comparing two ulongs). However, comparisons of different types are slower, as in those cases two HashSets are compared. Applying operators on GenericEnums of the same type is slightly slower than creating a new object. However, applying operators on GenericEnums of different types is slower, as in those cases the operators is in fact applied on HashSets. I might sometime give concrete performance analysis numbers.
Examples
This section contains examples of how GenericEnum works.
Basic Examples
Basic GenericEnums.
GenericEnum With int
as the Underlying Value Type
using Acmion.GenericEnums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenericEnumsDemo.GenericEnumTypes
{
public class FirstInt : GenericEnumBase<FirstInt, int>
{
public static readonly FirstInt Zero = new FirstInt(0);
public static readonly FirstInt ZeroZero = new FirstInt(0);
public static readonly FirstInt One = new FirstInt(1);
public static readonly FirstInt Two = new FirstInt(2);
public static readonly FirstInt Three = new FirstInt(3);
// The static property Values (in this case FirstInt.Values) is defined as follows:
// 1. Include fields of type FirstInt in the order they appear. Note: Properties may have generated backing fields.
// 2. Include properties of type FirstInt.
// 3. Include no duplicates.
// Thus,
// FirstInt.Values = [Zero, One, Two, Three]
// Note: ZeroZero is not included, because it is the same as Zero (same value).
protected FirstInt(int number) : base(number)
{ }
}
}
GenericEnum With double
as the Underlying Value Type
using Acmion.GenericEnums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenericEnumsDemo.GenericEnumTypes
{
public class FirstDouble : GenericEnumBase<FirstDouble, double>
{
public static FirstDouble Zero { get; } = new FirstDouble(0.0);
public static readonly FirstDouble One = new FirstDouble(1.0);
public static readonly FirstDouble Two = new FirstDouble(2.0);
public static readonly FirstDouble Three = new FirstDouble(3.0);
public static readonly FirstDouble PiWith20DecimalPlaces = new FirstDouble(3.14159265358979323846);
// The static property Values (in this case FirstDouble.Values) is defined as follows:
// 1. Include fields of type FirstDouble. Note: Properties may have generated backing fields.
// 2. Include properties of type FirstDouble.
// 3. Include no duplicates.
// Thus,
// FirstDouble.Values = [Zero, One, Two, Three, PiWith20DecimalPlaces]
// Note: Zero comes first because it is a property with a backing field.
protected FirstDouble(double number) : base(number)
{ }
}
}
GenericEnum With string?
as the Underlying Value Type
using Acmion.GenericEnums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenericEnumsDemo.GenericEnumTypes
{
public class CustomType : GenericEnumBase<CustomType, string?>
{
public static CustomType Acmion { get; } = new CustomType("Acmion");
public static CustomType HelloWorld { get; } = new CustomType("Hello World");
public static CustomType HelloWorldAlt { get; } = new CustomType("Hello World");
public static CustomType Null { get; } = new CustomType(null);
public static CustomType AcmionAndNull = Acmion | Null;
public static GenericEnum AcmionAndHelloWorld = Acmion | HelloWorld;
// The static property Values (in this case CustomType.Values) is defined as follows:
// 1. Include fields of type CustomType. Note: Properties may have generated backing fields.
// 2. Include properties of type CustomType.
// 3. Include no duplicates.
// Thus,
// CustomType.Values = [Acmion, HelloWorld, Null, AcmionAndNull]
// Note: HelloWorldAlt is not included because it is a duplicate and AcmionAndHelloWorld is not included, because it's type is GenericEnum and not CustomType.
protected CustomType(string? value) : base(value)
{
}
}
}
Operators
The supported operators of GenericEnum and what they do.
The Union Operator (|)
using System;
using Acmion.GenericEnums;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GenericEnumsDemo.GenericEnumTypes;
namespace GenericEnumsDemo.Operators
{
public static class UnionOperator
{
public static void Demo()
{
// The Union Operator (|)
// This operator calculates the union, without duplicates, of the given arguments.
// SSS
Console.WriteLine(FirstInt.One | FirstInt.One);
// Returns: 1.
// Type: FirstInt. Because all types are FirstInt.
// SSD
Console.WriteLine(FirstInt.One | FirstInt.Two);
// Returns: 1, 2.
// Type: FirstInt. Because all types are FirstInt.
// SDS
Console.WriteLine(FirstInt.One | SecondInt.One);
// Returns: 1.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// SDD
Console.WriteLine(FirstInt.One | SecondInt.Two);
// Returns: 1, 2. The types of the return entries are FirstInt and SecondInt, respectively.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// DDS
Console.WriteLine(FirstInt.One | FirstLong.One);
// Returns: 1.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// DDD
Console.WriteLine(FirstInt.One | FirstLong.Two);
// Returns: 1, 2. The types of the return entries are FirstInt and FirstLong, respectively.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// Multiple Operators
Console.WriteLine(FirstInt.One | SecondInt.Two + FirstLong.Three);
// Returns: 1, 2, 3. The types of the return entries are FirstInt, SecondInt and FirstLong, respectively.
// Type: GenericEnum. Because the types and Value types do not match.
}
}
}
The Intersect Operator (&)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Acmion.GenericEnums;
using GenericEnumsDemo.GenericEnumTypes;
namespace GenericEnumsDemo.Operators
{
public static class IntersectOperator
{
public static void Demo()
{
// The Intersect Operator (&)
// This operator calculates the intersection between two GenericEnums.
var firstIntSet = FirstInt.One + FirstInt.Two + FirstInt.Three;
var secondIntSet = SecondInt.One + SecondInt.Two + SecondInt.Three;
var firstLongSet = FirstLong.One + FirstLong.Two + FirstLong.Three;
// SSS
Console.WriteLine(firstIntSet & firstIntSet);
// Returns: 1, 2, 3.
// Type: FirstInt. Because all types are FirstInt.
// SSD
Console.WriteLine(firstIntSet & FirstInt.Three);
// Returns: 3.
// Type: FirstInt. Because all types are FirstInt.
// SDS
Console.WriteLine(firstIntSet & secondIntSet);
// Returns: 1, 2, 3.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// SDD
Console.WriteLine(firstIntSet & SecondInt.Two);
// Returns: 2.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// DDS
Console.WriteLine(firstIntSet & firstLongSet);
// Returns: 1, 2, 3.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// DDD
Console.WriteLine(firstIntSet & FirstLong.Three);
// Returns: 3.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// Multiple Operators
Console.WriteLine(firstIntSet & FirstInt.One & FirstLong.Two);
// Returns: <EMPTY SET>.
// Type: GenericEnum. Because the types and Value types do not match.
}
}
}
The Except Operator (-)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Acmion.GenericEnums;
using GenericEnumsDemo.GenericEnumTypes;
namespace GenericEnumsDemo.Operators
{
public static class ExceptOperator
{
public static void Demo()
{
// The Except Operator (-)
// This operator removes the entries of the second GenericEnum from the first GenericEnum. The result will not contain "negative values".
var firstIntSet = FirstInt.One + FirstInt.Two + FirstInt.Three;
var secondIntSet = SecondInt.One + SecondInt.Two + SecondInt.Three;
var firstLongSet = FirstLong.One + FirstLong.Two + FirstLong.Three;
// SSS
Console.WriteLine(firstIntSet - firstIntSet);
// Returns: <EMPTY SET>.
// Type: FirstInt. Because all types are FirstInt.
// SSD
Console.WriteLine(firstIntSet - FirstInt.Three);
// Returns: 1, 2.
// Type: FirstInt. Because all types are FirstInt.
// SDS
Console.WriteLine(firstIntSet - secondIntSet);
// Returns: <EMPTY SET>.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// SDD
Console.WriteLine(firstIntSet - SecondInt.Two);
// Returns: 1, 3.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// DDS
Console.WriteLine(firstIntSet - firstLongSet);
// Returns: <EMPTY SET>
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// DDD
Console.WriteLine(firstIntSet - FirstLong.Three);
// Returns: 1, 2.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// Multiple Operators
Console.WriteLine(firstIntSet - FirstInt.One - FirstLong.Two);
// Returns: 3.
// Type: GenericEnum. Because the types and Value types do not match.
}
}
}
The Symmetric Except Operator (^)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Acmion.GenericEnums;
using GenericEnumsDemo.GenericEnumTypes;
namespace GenericEnumsDemo.Operators
{
public static class SymmetricExceptOperator
{
public static void Demo()
{
// The Symmetric Exception Operator (^)
// This operator calculates the symmetrice exception (XOR) between two GenericEnums.
var firstIntSet = FirstInt.One + FirstInt.Two + FirstInt.Three;
var secondIntSet = SecondInt.One + SecondInt.Two + SecondInt.Three;
var firstLongSet = FirstLong.One + FirstLong.Two + FirstLong.Three;
// SSS
Console.WriteLine(firstIntSet ^ firstIntSet);
// Returns: <EMPTY SET>.
// Type: FirstInt. Because all types are FirstInt.
// SSD
Console.WriteLine(firstIntSet ^ FirstInt.Three);
// Returns: 1, 2.
// Type: FirstInt. Because all types are FirstInt.
// SDS
Console.WriteLine(firstIntSet ^ secondIntSet);
// Returns: <EMPTY SET>.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// SDD
Console.WriteLine(firstIntSet ^ SecondInt.Two);
// Returns: 1, 3.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// DDS
Console.WriteLine(firstIntSet ^ firstLongSet);
// Returns: <EMPTY SET>.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// DDD
Console.WriteLine(firstIntSet ^ FirstLong.Three);
// Returns: 1, 2.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// Multiple Operators
Console.WriteLine(firstIntSet ^ FirstInt.One ^ FirstLong.Two);
// Returns: 3.
// Type: GenericEnum. Because the types and Value types do not match.
}
}
}
The Secondary Union Operator (+)
using System;
using Acmion.GenericEnums;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GenericEnumsDemo.GenericEnumTypes;
namespace GenericEnumsDemo.Operators
{
public static class SecondaryUnionOperator
{
public static void Demo()
{
// The Secondary Union Operator (+)
// This operator calculates the union, without duplicates, of the given arguments.
// This operator will give the same results as the union operator (|).
// SSS
Console.WriteLine(FirstInt.One + FirstInt.One);
// Returns: 1.
// Type: FirstInt. Because all types are FirstInt.
// SSD
Console.WriteLine(FirstInt.One + FirstInt.Two);
// Returns: 1, 2.
// Type: FirstInt. Because all types are FirstInt.
// SDS
Console.WriteLine(FirstInt.One + SecondInt.One);
// Returns: 1.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// SDD
Console.WriteLine(FirstInt.One + SecondInt.Two);
// Returns: 1, 2. The types of the return entries are FirstInt and SecondInt, respectively.
// Type: GenericEnumValue<int>. Because FirstInt and SecondInt are not the same type, but they share the Value type, which is int.
// DDS
Console.WriteLine(FirstInt.One + FirstLong.One);
// Returns: 1.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// DDD
Console.WriteLine(FirstInt.One + FirstLong.Two);
// Returns: 1, 2. The types of the return entries are FirstInt and FirstLong, respectively.
// Type: GenericEnum. Because FirstInt and FirstLong are not the same type and they do not share the Value type.
// Multiple Operators
Console.WriteLine(FirstInt.One + SecondInt.Two + FirstLong.Three);
// Returns: 1, 2, 3. The types of the return entries are FirstInt, SecondInt and FirstLong, respectively.
// Type: GenericEnum. Because the types and Value types do not match.
}
}
}
The Equality Operator (==)
using GenericEnumsDemo.GenericEnumTypes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenericEnumsDemo.Operators
{
public static class EqualityOperator
{
public static void Demo()
{
// The Equality Operator (==)
// This operator compares whether two GenericEnums are equal or not. The equality is based on the equality of the Value or ValueSet properties.
var firstIntSet = FirstInt.One + FirstInt.Two + FirstInt.Three;
var secondIntSet = SecondInt.One + SecondInt.Two + SecondInt.Three;
var firstLongSet = FirstLong.One + FirstLong.Two + FirstLong.Three;
// Just a convoluted way to describe FirstInt.One. Note that this GenericEnum does not have a value (HasValue = false).
var convolutedFirstIntOne = (FirstInt.One + FirstInt.Two) & FirstInt.One;
// Just a convoluted way to describe an equivalent to firstIntSet.
var convolutedFirstIntSet = FirstInt.Zero + FirstInt.One + SecondInt.Two + FirstLong.Three - FirstLong.Zero;
// SSS
Console.WriteLine(FirstInt.Zero == FirstInt.ZeroZero);
// Returns: True. Because the Value of both are the same.
// SSD
Console.WriteLine(FirstInt.One == FirstInt.Two);
// Returns: False. Because the Value of both are not the same.
// SDS
Console.WriteLine(FirstInt.One == SecondInt.One);
// Returns: True. Because the Value of both are the same, even if the GenericEnums are of a different type.
// SDD
Console.WriteLine(FirstInt.One == SecondInt.Two);
// Returns: False. Because the Value of both are not the same.
// DDS
Console.WriteLine(FirstInt.One == FirstLong.One);
// Returns: True. Because the Value of both are the same, even if the GenericEnums are of a different type.
// DDD
Console.WriteLine(FirstInt.One == FirstLong.Two);
// Returns: False. Because the Value of both are not the same.
// Extra
Console.WriteLine(FirstInt.One == convolutedFirstIntOne);
// Returns: True. Because the ValueSet of both are equal.
// SSS
Console.WriteLine(firstIntSet == firstIntSet);
// Returns: True. Because the ValueSet of both are the same (the reference is also the same in this case).
// SSD
Console.WriteLine(firstIntSet == FirstInt.Two);
// Returns: False. Because the ValueSet of both are not the same.
// SDS
Console.WriteLine(firstIntSet == secondIntSet);
// Returns: True. Because the ValueSet of both are the same, even if the GenericEnums are of a different type.
// SDD
Console.WriteLine(firstIntSet == SecondInt.Two);
// Returns: False. Because the ValueSet of both are not the same.
// DDS
Console.WriteLine(firstIntSet == firstLongSet);
// Returns: True. Because the ValueSet of both are the same, even if the GenericEnums are of a different type.
// DDD
Console.WriteLine(firstIntSet == FirstLong.Two);
// Returns: False. Because the ValueSet of both are not the same.
// Extra
Console.WriteLine(firstIntSet == convolutedFirstIntSet);
// Returns: True. Because the ValueSet of both are equal.
}
}
}
The Inequality Operator (!=)
using GenericEnumsDemo.GenericEnumTypes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenericEnumsDemo.Operators
{
public static class InequalityOperator
{
public static void Demo()
{
// The Inequality Operator (!=)
// This operator compares whether two GenericEnums are not equal. The equality is based on the equality of the Value or ValueSet properties.
var firstIntSet = FirstInt.One + FirstInt.Two + FirstInt.Three;
var secondIntSet = SecondInt.One + SecondInt.Two + SecondInt.Three;
var firstLongSet = FirstLong.One + FirstLong.Two + FirstLong.Three;
// Just a convoluted way to describe FirstInt.One. Note that this GenericEnum does not have a value (HasValue = false).
var convolutedFirstIntOne = (FirstInt.One + FirstInt.Two) & FirstInt.One;
// Just a convoluted way to describe an equivalent to firstIntSet.
var convolutedFirstIntSet = FirstInt.Zero + FirstInt.One + SecondInt.Two + FirstLong.Three - FirstLong.Zero;
// SSS
Console.WriteLine(FirstInt.Zero != FirstInt.ZeroZero);
// Returns: False. Because the Value of both are the same.
// SSD
Console.WriteLine(FirstInt.One != FirstInt.Two);
// Returns: True. Because the Value of both are not the same.
// SDS
Console.WriteLine(FirstInt.One != SecondInt.One);
// Returns: False. Because the Value of both are the same, even if the GenericEnums are of a different type.
// SDD
Console.WriteLine(FirstInt.One != SecondInt.Two);
// Returns: True. Because the Value of both are not the same.
// DDS
Console.WriteLine(FirstInt.One != FirstLong.One);
// Returns: False. Because the Value of both are the same, even if the GenericEnums are of a different type.
// DDD
Console.WriteLine(FirstInt.One != FirstLong.Two);
// Returns: True. Because the Value of both are not the same.
// Extra
Console.WriteLine(FirstInt.One != convolutedFirstIntOne);
// Returns: False. Because the ValueSet of both are equal.
// SSS
Console.WriteLine(firstIntSet != firstIntSet);
// Returns: False. Because the ValueSet of both are the same (the reference is also the same in this case).
// SSD
Console.WriteLine(firstIntSet != FirstInt.Two);
// Returns: True. Because the ValueSet of both are not the same.
// SDS
Console.WriteLine(firstIntSet != secondIntSet);
// Returns: False. Because the ValueSet of both are the same, even if the GenericEnums are of a different type.
// SDD
Console.WriteLine(firstIntSet != SecondInt.Two);
// Returns: True. Because the ValueSet of both are not the same.
// DDS
Console.WriteLine(firstIntSet != firstLongSet);
// Returns: False. Because the ValueSet of both are the same, even if the GenericEnums are of a different type.
// DDD
Console.WriteLine(firstIntSet != FirstLong.Two);
// Returns: True. Because the ValueSet of both are not the same.
// Extra
Console.WriteLine(firstIntSet != convolutedFirstIntSet);
// Returns: False. Because the ValueSet of both are equal.
}
}
}
HashCode Calculation
The basics of how HashCodes are calculated for GenericEnums. May have some implications in certain applications, but should be just fine in most cases.
HashCode Calculation Examples
using GenericEnumsDemo.GenericEnumTypes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenericEnumsDemo.Other
{
public static class HashCodeDemo
{
public static void Demo()
{
// HashCode Calculation
// Demonstration of how HashCodes are calculated.
// Basically, HashCodes are calculated on the basis of the Value property, however, most numeric types are casted to double.
// The cast to double is made so that same numeric values of different types match.
// Note: This does not apply for decimal. The reason is that the == opertor is not defined for, for example, decimal == double.
// The HashCodes are also cached for performance reasons.
// If a GenericEnum has more than one value in it's ValueSet, then the HashCode is the XOR result of all GenericEnums in the ValueSet.
// Compare the HashCodes of (int) 1 and (double) 1.0.
Console.WriteLine(1.GetHashCode() == 1.0.GetHashCode());
// Returns: False.
// Compare the HashCodes of FirstInt.One (Value = (int)1) and FirstDouble.One (Value = (double)1.0).
Console.WriteLine(FirstInt.One.GetHashCode() == FirstDouble.One.GetHashCode());
// Returns: True. Because numeric (except decimal) Values are always casted to double.
// Compare the HashCodes of FirstInt.One and (double) 1.0.
Console.WriteLine(FirstInt.One.GetHashCode() == 1.0.GetHashCode());
// Returns: True. Because numeric (except decimal) Values are always casted to double.
// Compare the HashCodes of FirstDouble.One and (double) 1.0.
Console.WriteLine(FirstDouble.One.GetHashCode() == 1.0.GetHashCode());
// Returns: True.
// Showcases the logic behind a sets HashCode.
Console.WriteLine((FirstInt.One | FirstInt.Two).GetHashCode() == (1.0.GetHashCode() ^ 2.0.GetHashCode()));
// Returns True. Because this is how the HashCode is internally calculated.
}
}
}
Core
This section documents the inner workings of GenericEnum.
GenericEnum
GenericEnum
is a class that should neither be instantiated
nor be inherited directly, even if that is technically possible.
Instance Properties
The properties of GenericEnum
.
public bool HasValue
A property that describes whether an instance of a GenericEnum was instantiated with an explicit value or not.
public dynamic? InternalValue
A property that contains the underlying value of GenericEnum.
public HashSet<GenericEnum> ValueSet
A property that contains all associated GenericEnums to an instance. For example, one can calculate the union of two GenericEnums and in that case ValueSet will contain them both.
Instance Constructors
The constructors of GenericEnum
, their arguments and the key properties that they set.
protected GenericEnum(dynamic? internalValue)
HasValue: true
InternalValue: internalValue
ValueSet: new HashSet<GenericEnum>() { this }
Instance Methods
The instance methods of GenericEnum
, their arguments and what they do and return.
public bool HasFlag(GenericEnum other)
Determines whether the ValueSet of this
is a superset of the ValueSet of other
.
Works like the HasFlag method of the standard enum type.
public bool Equals(object? obj)
Determines whether this
and obj
are equal.
The equality is based on the equality comparison of the underlying ValueSet.
public bool Equals(GenericEnum? other)
Determines whether this
and other
are equal.
The equality is based on the equality comparison of the underlying ValueSet.
public string ToString()
Creates a string representation of a GenericEnum. Joins together all values from the ValueSet.
public int GetHashCode()
Gets the HashCode. The computation of the HashCode is somewhat expensive and so it is cached.
Static Properties
GenericEnum
does not have any static properties.
Static Methods
The static methods of GenericEnum
, their arguments and what they do and return.
public static IReadOnlyList<GenericEnum> GetValues(Type genericEnumType)
Gets all statically defined distinct GenericEnums of a specific type.
Operators
The operators of GenericEnum
, their arguments and what they do and return.
public static bool operator ==(GenericEnum genericEnum0, GenericEnum genericEnum1)
Determines whether two GenericEnums are equal.
public static bool operator !=(GenericEnum genericEnum0, GenericEnum genericEnum1)
Determines whether two GenericEnums are not equal.
public static GenericEnum operator +(GenericEnum genericEnum0, GenericEnum genericEnum1)
Applies the union operator on two GenericEnums. In essence, adds genericEnum0.ValueSet
and genericEnum1.ValueSet
together, but without duplicates.
public static GenericEnum operator -(GenericEnum genericEnum0, GenericEnum genericEnum1)
Applies the except operator on two GenericEnums. In essence, the genericEnum1.ValueSet
is removed from genericEnum0.ValueSet
.
public static GenericEnum operator &(GenericEnum genericEnum0, GenericEnum genericEnum1)
Applies the intersect operator on two GenericEnums. In essence, an intersection is calculated
between genericEnum0.ValueSet
and genericEnum1.ValueSet
.
public static GenericEnum operator |(GenericEnum genericEnum0, GenericEnum genericEnum1)
Applies the union operator on two GenericEnums. In essence, adds genericEnum0.ValueSet
and genericEnum1.ValueSet
together, but without duplicates.
public static GenericEnum operator ^(GenericEnum genericEnum0, GenericEnum genericEnum1)
Applies the symmetric except operator on two GenericEnums. In essence, calculates the values
that exist in genericEnum0.ValueSet
or genericEnum1.ValueSet
, but no both.
Commonly known as the XOR operator.
GenericEnumValue<TValue>
GenericEnumValue<TValue>
is a class that should neither be instantiated
nor be inherited directly, even if that is technically possible. Inherits GenericEnum
.
The type TValue
is the type of the underlying value.
Instance Properties
The properties of GenericEnumValue<TValue>
(not including inherited).
public TValue? Value
A property that contains the underlying value of GenericEnum, but correctly casted. The casting is done in the constructor, so the negative performance implications of boxing are avoided.
Instance Constructors
The constructors of GenericEnumValue<TValue>
, their arguments and the key properties that they set.
protected GenericEnumValue(TValue value)
Value: valueInstance Methods
The instance methods of GenericEnumValue<TValue>
, their arguments and what they
do and return (not including inherited).
public bool Equals(object? obj)
Determines whether this
and obj
are equal.
The equality is based on the equality comparison of the underlying ValueSet.
public bool Equals(GenericEnum? other)
Determines whether this
and other
are equal.
The equality is based on the equality comparison of the underlying ValueSet.
public bool Equals(GenericEnum<TValue>? other)
Determines whether this
and other
are equal.
The equality is based on the equality comparison of the underlying ValueSet.
public int GetHashCode()
Returns base.GetHashCode()
.
Static Properties
GenericEnumValue<TValue>
does not have any static properties.
Static Methods
GenericEnumValue<TValue>
does not have any static methods (not including inherited).
Operators
The operators of GenericEnum<TValue>
, their arguments and what they do and return.
public static bool operator ==(GenericEnumValue<TValue> genericEnum0, GenericEnumValue<TValue> genericEnum1)
Determines whether two GenericEnumValue<TValue> are equal.
public static bool operator !=(GenericEnumValue<TValue> genericEnum0, GenericEnumValue<TValue> genericEnum1)
Determines whether two GenericEnumValue<TValue> are not equal.
public static GenericEnumValue<TValue> operator +(GenericEnumValue<TValue> genericEnum0, GenericEnumValue<TValue> genericEnum1)
Applies the union operator on two GenericEnumValue<TValue>. In essence, adds genericEnum0.ValueSet
and genericEnum1.ValueSet
together, but without duplicates.
public static GenericEnumValue<TValue> operator -(GenericEnumValue<TValue> genericEnum0, GenericEnumValue<TValue> genericEnum1)
Applies the except operator on two GenericEnumValue<TValue>. In essence, the genericEnum1.ValueSet
is removed from genericEnum0.ValueSet
.
public static GenericEnumValue<TValue> operator &(GenericEnumValue<TValue> genericEnum0, GenericEnumValue<TValue> genericEnum1)
Applies the intersect operator on two GenericEnumValue<TValue>. In essence, an intersection is calculated
between genericEnum0.ValueSet
and genericEnum1.ValueSet
.
public static GenericEnumValue<TValue> operator |(GenericEnumValue<TValue> genericEnum0, GenericEnumValue<TValue> genericEnum1)
Applies the union operator on two GenericEnumValue<TValue>. In essence, adds genericEnum0.ValueSet
and genericEnum1.ValueSet
together, but without duplicates.
public static GenericEnumValue<TValue> operator ^(GenericEnumValue<TValue> genericEnum0, GenericEnumValue<TValue> genericEnum1)
Applies the symmetric except operator on two GenericEnumValue<TValue>. In essence, calculates the values
that exist in genericEnum0.ValueSet
or genericEnum1.ValueSet
, but no both.
Commonly known as the XOR operator.
GenericEnumBase<T, TValue>
GenericEnumBase<T, TValue>
is the base class which all custom GenericEnums should inherit.
T
is the type of the custom GenericEnum and TValue
is the type of the underlying value.
This class inherits GenericEnum
and GenericEnumValue<TValue>
. This class should be
inherited like this (int is the type of the underlying value in this case):
public class MyGenericEnum : GenericEnumBase<MyGenericEnum, int> { ... }
Instance Properties
GenericEnumBase<T, TValue>
does not have any properties (not including inherited).
Instance Constructors
The constructors of GenericEnumBase<T, TValue>
, their arguments and the key properties that they set.
protected GenericEnumBase<T, TValue>(TValue value)
Value: value
Instance Methods
The instance methods of GenericEnumValue<TValue>
, their arguments and what they
do and return (not including inherited).
public bool Equals(object? obj)
Determines whether this
and obj
are equal.
The equality is based on the equality comparison of the underlying ValueSet.
public bool Equals(GenericEnum? other)
Determines whether this
and other
are equal.
The equality is based on the equality comparison of the underlying ValueSet.
public bool Equals(GenericEnum<TValue>? other)
Determines whether this
and other
are equal.
The equality is based on the equality comparison of the underlying ValueSet.
public int GetHashCode()
Returns base.GetHashCode()
.
Static Properties
The static properties of GenericEnumBase<T, TValue>
.
public static IReadOnlyList<T> Values
The statically defined distinct values of a specific custom GenericEnum. These distinct values must be declared within the custom class as properties or fields and must be of the specific type. See Examples for concrete examples.
public static Dictionary<ulong, T> ValuesByBitValue
A dictionary of the bitvalues of the property Values
and their respective GenericEnums.
Static Methods
The static methods of GenericEnumBase<T, TValue>
, their arguments and what they do and return.
public static IReadOnlyList<T> GetValues()
Returns the Values
property.
Operators
The operators of GenericEnum<TValue>
, their arguments and what they do and return.
Note that the operators return either a bool
or an object of type T
.
public static bool operator ==(GenericEnumBase<T, TValue> genericEnum0, GenericEnumBase<T, TValue> genericEnum1)
Determines whether two GenericEnumBase<T, TValue>
are equal.
public static bool operator !=(GenericEnumBase<T, TValue> genericEnum0, GenericEnumBase<T, TValue> genericEnum1)
Determines whether two GenericEnumBase<T, TValue>
are not equal.
public static T operator +(GenericEnumBase<T, TValue> genericEnum0, GenericEnumBase<T, TValue> genericEnum1)
Applies the union operator on two GenericEnumBase<T, TValue>
. In essence, adds genericEnum0.ValueSet
and genericEnum1.ValueSet
together, but without duplicates.
public static T operator -(GenericEnumBase<T, TValue> genericEnum0, GenericEnumBase<T, TValue> genericEnum1)
Applies the except operator on two GenericEnumBase<T, TValue>
. In essence, the genericEnum1.ValueSet
is removed from genericEnum0.ValueSet
.
public static T operator &(GenericEnumBase<T, TValue> genericEnum0, GenericEnumBase<T, TValue> genericEnum1)
Applies the intersect operator on two GenericEnumBase<T, TValue>
. In essence, an intersection is calculated
between genericEnum0.ValueSet
and genericEnum1.ValueSet
.
public static T operator |(GenericEnumBase<T, TValue> genericEnum0, GenericEnumBase<T, TValue> genericEnum1)
Applies the union operator on two GenericEnumBase<T, TValue>
. In essence, adds genericEnum0.ValueSet
and genericEnum1.ValueSet
together, but without duplicates.
public static T operator ^(GenericEnumBase<T, TValue> genericEnum0, GenericEnumBase<T, TValue> genericEnum1)
Applies the symmetric except operator on two GenericEnumBase<T, TValue>
. In essence, calculates the values
that exist in genericEnum0.ValueSet
or genericEnum1.ValueSet
, but no both.
Commonly known as the XOR operator.
Usage
This section documents how GenericEnum should be installed and how to create custom GenericEnums.
- Install the Nuget package.
-
Create a custom class
CustomGenericEnum
and inheritGenericEnumBase<T, TValue>
, whereT
is the custom GenericEnum type (in this caseCustomGenericEnum
) andTValue
is the type of the underlying value. Concretely withdouble
as the underlying value type:public class CustomGenericEnum : GenericEnumBase<CustomGenericEnum, double> { ... }
-
Create a constructor with one argument and of type
TValue
and make itprivate
. Call thebase
constructor with this argument. Concretely:public class CustomGenericEnum : GenericEnumBase<CustomGenericEnum, double> { private CustomGenericEnum(double value) : base(value) { } }
-
Declare public static fields or properties, which will act as the distinct values of a specific GenericEnum.
Concretely:
public class CustomGenericEnum : GenericEnumBase<CustomGenericEnum, double> { public static readonly CustomGenericEnum Zero = new CustomGenericEnum(0); public static readonly CustomGenericEnum Pi = new CustomGenericEnum(3.14159); public static CustomGenericEnum One { get; } = new CustomGenericEnum(1); public static CustomGenericEnum PiSquared { get; } = new CustomGenericEnum(3.14159 * 3.14159); private CustomGenericEnum(double value) : base(value) { } }
- That's all! Now go and use GenericEnum for your own applications!
Changelog
V1.0.0
V1.0.0 DocumentationPublished: Jan 11, 2021
The initial release.
Credits
GenericEnum was developed by Acmion (GitHub).
Contribute to GenericEnum in it's GitHub repository.