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

GenericEnum With double as the Underlying Value Type

GenericEnum With string? as the Underlying Value Type

Operators

The supported operators of GenericEnum and what they do.

The Union Operator (|)

The Intersect Operator (&)

The Except Operator (-)

The Symmetric Except Operator (^)

The Secondary Union Operator (+)

The Equality Operator (==)

The Inequality Operator (!=)

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

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.