allow dynamic enum classes to use a parameterless constructor

This commit is contained in:
Ell 2024-06-29 00:08:59 +02:00
parent 94d12cdc5d
commit e17ebf9973
3 changed files with 29 additions and 8 deletions

View file

@ -30,13 +30,22 @@ namespace DynamicEnums {
/// </remarks> /// </remarks>
public abstract class DynamicEnum { public abstract class DynamicEnum {
private const BindingFlags ConstructorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly Type[] ConstructorTypes = {typeof(string), typeof(BigInteger), typeof(bool)};
private static readonly Dictionary<Type, Storage> Storages = new Dictionary<Type, Storage>(); private static readonly Dictionary<Type, Storage> Storages = new Dictionary<Type, Storage>();
private readonly BigInteger value;
private Dictionary<DynamicEnum, bool> allFlagsCache; private Dictionary<DynamicEnum, bool> allFlagsCache;
private Dictionary<DynamicEnum, bool> anyFlagsCache; private Dictionary<DynamicEnum, bool> anyFlagsCache;
private BigInteger value;
private string name; private string name;
/// <summary>
/// Creates a new dynamic enum instance.
/// This constructor is protected as it is only invoked via reflection.
/// This constructor is only called if the class doesn't have the <see cref="DynamicEnum(string,BigInteger,bool)"/> constructor.
/// </summary>
protected DynamicEnum() {}
/// <summary> /// <summary>
/// Creates a new dynamic enum instance. /// Creates a new dynamic enum instance.
/// This constructor is protected as it is only invoked via reflection. /// This constructor is protected as it is only invoked via reflection.
@ -45,8 +54,8 @@ namespace DynamicEnums {
/// <param name="value">The value</param> /// <param name="value">The value</param>
/// <param name="defined">Whether this enum value <see cref="IsDefined(DynamicEnum)"/>, and thus, not a combined flag.</param> /// <param name="defined">Whether this enum value <see cref="IsDefined(DynamicEnum)"/>, and thus, not a combined flag.</param>
protected DynamicEnum(string name, BigInteger value, bool defined) { protected DynamicEnum(string name, BigInteger value, bool defined) {
this.value = value;
this.name = name; this.name = name;
this.value = value;
} }
/// <summary> /// <summary>
@ -445,7 +454,17 @@ namespace DynamicEnums {
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
#endif #endif
Type type, string name, BigInteger value, bool defined) { Type type, string name, BigInteger value, bool defined) {
return (DynamicEnum) Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new object[] {name, value, defined}, CultureInfo.InvariantCulture); // try to call the constructor with parameters first
var parameterConstructor = type.GetConstructor(DynamicEnum.ConstructorFlags, null, DynamicEnum.ConstructorTypes, null);
if (parameterConstructor != null)
return (DynamicEnum) parameterConstructor.Invoke(DynamicEnum.ConstructorFlags, null, new object[] {name, value, defined}, CultureInfo.InvariantCulture);
// default to the empty constructor and set the values manually
var emptyConstructor = type.GetConstructor(DynamicEnum.ConstructorFlags, null, Type.EmptyTypes, null);
var ret = (DynamicEnum) emptyConstructor.Invoke(DynamicEnum.ConstructorFlags, null, null, CultureInfo.InvariantCulture);
ret.name = name;
ret.value = value;
return ret;
} }
private class Storage { private class Storage {

View file

@ -19,9 +19,6 @@ public class MyEnum : DynamicEnum {
public static readonly MyEnum FlagTwo = DynamicEnum.AddFlag<MyEnum>("FlagTwo"); public static readonly MyEnum FlagTwo = DynamicEnum.AddFlag<MyEnum>("FlagTwo");
public static readonly MyEnum FlagThree = DynamicEnum.AddFlag<MyEnum>("FlagThree"); public static readonly MyEnum FlagThree = DynamicEnum.AddFlag<MyEnum>("FlagThree");
// this constructor is called internally using reflection
public MyEnum(string name, BigInteger value) : base(name, value) {}
// you can optionally create operator overloads for easier operations // you can optionally create operator overloads for easier operations
public static implicit operator BigInteger(MyEnum value) => DynamicEnum.GetValue(value); public static implicit operator BigInteger(MyEnum value) => DynamicEnum.GetValue(value);
public static implicit operator MyEnum(BigInteger value) => DynamicEnum.GetEnumValue<MyEnum>(value); public static implicit operator MyEnum(BigInteger value) => DynamicEnum.GetEnumValue<MyEnum>(value);

View file

@ -35,6 +35,9 @@ public class EnumTests {
var zero = DynamicEnum.Add<TestDynamicEnum>("Zero", 0); var zero = DynamicEnum.Add<TestDynamicEnum>("Zero", 0);
var combined = DynamicEnum.Add<TestDynamicEnum>("Combined", DynamicEnum.GetValue(DynamicEnum.Or(flags[7], flags[13]))); var combined = DynamicEnum.Add<TestDynamicEnum>("Combined", DynamicEnum.GetValue(DynamicEnum.Or(flags[7], flags[13])));
DynamicEnum.Add<TestEnumWithConstructor>("Test", 10);
Assert.AreEqual(DynamicEnum.GetEnumValue<TestEnumWithConstructor>(10).ToString(), "TestModified");
Assert.AreEqual(DynamicEnum.GetValue(flags[7]), BigInteger.One << 7); Assert.AreEqual(DynamicEnum.GetValue(flags[7]), BigInteger.One << 7);
Assert.AreEqual(DynamicEnum.GetEnumValue<TestDynamicEnum>(BigInteger.One << 75), flags[75]); Assert.AreEqual(DynamicEnum.GetEnumValue<TestDynamicEnum>(BigInteger.One << 75), flags[75]);
@ -90,9 +93,11 @@ public class EnumTests {
} }
private class TestDynamicEnum : DynamicEnum { private class TestDynamicEnum : DynamicEnum;
public TestDynamicEnum(string name, BigInteger value, bool defined) : base(name, value, defined) {} private class TestEnumWithConstructor : DynamicEnum {
public TestEnumWithConstructor(string name, BigInteger value, bool defined) : base($"{name}Modified", value, defined) {}
} }