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: 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

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.

  1. Install the Nuget package.
  2. Create a custom class CustomGenericEnum and inherit GenericEnumBase<T, TValue>, where T is the custom GenericEnum type (in this case CustomGenericEnum) and TValue is the type of the underlying value. Concretely with double as the underlying value type:
    public class CustomGenericEnum : GenericEnumBase<CustomGenericEnum, double> { ... }
  3. Create a constructor with one argument and of type TValue and make it private. Call the base constructor with this argument. Concretely:
    public class CustomGenericEnum : GenericEnumBase<CustomGenericEnum, double> 
    { 
        private CustomGenericEnum(double value) : base(value)
        {
    
        }
    }
  4. 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)
        {
    
        }
    }
  5. That's all! Now go and use GenericEnum for your own applications!

Changelog

V1.0.0

V1.0.0 Documentation

Published: Jan 11, 2021

The initial release.

Credits

GenericEnum was developed by Acmion (GitHub).

Contribute to GenericEnum in it's GitHub repository.