1

What I want to do here is a bit hard to describe. My current needs require that I have an enum type that can implement an interface. While not the prettiest solution, this is what I came up with;

public class EnumClass<T> where T : Enum
{
    public T Value { get; }
    public string Name { get; }

    public EnumClass(T enumValue)
    {
        Value = enumValue;
        Name = Enum.GetName(typeof(T), enumValue);
    }

    public static EnumClass<T> Parse(string name)
    {
        return new EnumClass<T>((T)Enum.Parse(typeof(T), name));
    }
}

Here is an example implementation:

public class AnimalTypes : EnumClass<AnimalTypesEnum>, IMyEnumInterface
{
    public AnimalTypes (AnimalTypesEnum value) : base(value) { }
}

public enum AnimalTypesEnum
{
    [Description("Cat")]
    CAT,
    [Description("Dog")]
    DOG,
    [Description("Horse")]
    HORSE,
    [Description("Bear")]
    BEAR
}

When I call Parse statically on an inheritor, I have to manually cast the result back to the inheritor type from the base type, since Parse returns a generic EnumClass<T> object.

ex.

AnimalTypes dog = (AnimalTypes)AnimalTypes.Parse("DOG");

My question essentially is, is there any way to write Parse such that it returns the type of the inheritor, and not the base class? I'd also like to be able to mark EnumClass<T> abstract, but if I try doing so now, the compiler will not compile Parse, stating that I cannot create an abstract instance of type EnumClass<T> with which to return.

Maurdekye
  • 3,597
  • 5
  • 26
  • 43
  • 1
    I assume you know that the "manual cast" does not work at run-time - you may want to clarify that in the post... (Overall it should be probably duplicate of https://stackoverflow.com/questions/840261/passing-arguments-to-c-sharp-generic-new-of-templated-type when you figure out that you are looking for `public static T Parse(string name) { return new T( ??????? (T)Enum.Parse(typeof(T), name)); }`) – Alexei Levenkov Sep 07 '21 at 20:34
  • does that mean you have a solution? If so, please post as an answer to the question. – Maurdekye Sep 07 '21 at 20:37
  • No, I don't have a solution... I'm only pointing out that you don't yet seem to figured what you want and what you can live with (as the code in the post does unconditionally fail to cast at run-time). Depending on the route you going to pick it may end up duplicate of the question I linked. – Alexei Levenkov Sep 07 '21 at 20:53

2 Answers2

0

You need to add another type param, in order to parametrize the return value type of Parse and enable derived/inherited types being created.

Usage:
var bear = EnumClass<AnimalTypesEnum>.Parse<AnimalTypes>("BEAR");

//AnimalTypesEnum unchanged
//AnimalTypes unchanged

public abstract class EnumClass<TEnum> where TEnum : Enum
{
    public TEnum      Value { get; }
    public string Name  { get; }

    protected EnumClass(TEnum enumValue)
    {
        Value = enumValue;
        Name  = Enum.GetName(typeof(TEnum), enumValue);
    }

    public static TEnumClass Parse<TEnumClass>(string name)
            where TEnumClass : EnumClass<TEnum>
    {
        //TODO: try/catch
        /* Contract: the derived class must have a public constructor
                     that takes 1 arg of its enum type.
           Generic constraints don't support ctors with args, so we need reflection here... */
        return (TEnumClass)Activator.CreateInstance(
                typeof(TEnumClass), Enum.Parse(typeof(TEnum), name));
    }
}
lidqy
  • 1,891
  • 1
  • 9
  • 11
  • It is also possible to specify the type parameter for the base class itself, which pretty much solves the problem. Reflection is still needed this way however and would need shapes or shape-like approach to circumvent. – IS4 Sep 07 '21 at 21:21
0

You can use a curiously recursive template pattern, but it requires default constructors and feels odd. Normally if things get this convoluted it's worth asking if your requirements can be restructured so that it's not so complicated, but it's hard to know if that's possible with the details given. That said, this may be as close to what you are asking for that you can get.

There isn't a way to specify that a method return the derived type, but you can specify the return type using a generic type. Below is the EnumClass, but modified to take two generic types. The first type is the enum type like before, but the second is for specifying the derived type (hence the recursive part of the template).

    public abstract class EnumClass<T, TDerived>
        where T : Enum where TDerived : EnumClass<T, TDerived>, new()
    {
        protected EnumClass()
        {
        }

        protected EnumClass(T enumValue)
        {
            Value = enumValue;
        }

        private T _value = default(T);
        public T Value
        {
            get => _value;
            init => _value = value;
        }

        private string _name = null;
        public string Name
        {
            get
            {
                _name = _name ?? Enum.GetName(typeof(T), Value);
                return _name;
            }
        }

        public static TDerived Parse(string name)
        {
            var enumValue = (T)Enum.Parse(typeof(T), name);
            return new TDerived() {Value = enumValue};
        }
    }

Then, a derived type using this EnumClass would look like this, where the second generic type recursively refers to itself, which means that the static Parse method in the EnumClass will return a type AnimalTypes.

    public class AnimalTypes : EnumClass<AnimalTypesEnum, AnimalTypes>
    {
        public AnimalTypes(): base()
        {
        }

        public AnimalTypes(AnimalTypesEnum value): base(value)
        {
        }
    }

In use, it would look like this

        //because we are required to have public default constructors, it's possible
        //to have a "default" AnimalTypes class that would be similar to constructing
        //a "new AnimalTypes(default(AnimalTypesEnum));"
        var defaultType = new AnimalTypes();
        //this will output "CAT, CAT"
        Console.WriteLine($"{defaultType.Value}, {defaultType.Name}");

        //Since we are using init, you can initialize the value using this format
        //instead of using the constructor
        var horseType = new AnimalTypes() {Value = AnimalTypesEnum.HORSE};
        //this will output "HORSE, HORSE"
        Console.WriteLine($"{horseType.Value}, {horseType.Name}");

        //normal constructor
        var dogType = new AnimalTypes(AnimalTypesEnum.DOG);
        //this will output "DOG, DOG"
        Console.WriteLine($"{dogType.Value}, {dogType.Name}");

        //static parser will return a type of AnimalTypes
        var bearType = AnimalTypes.Parse("BEAR");
        //this will output "BEAR, BEAR"
        Console.WriteLine($"{bearType.Value}, {bearType.Name}");
TJ Rockefeller
  • 3,178
  • 17
  • 43
  • This looks like what I'm looking for, thank you. Yes, the syntax is a bit redundant in the subclass definition, but this is probably the best I'm going to get to be honest. – Maurdekye Sep 08 '21 at 15:52