从抽象泛型方法派生类的返回实例



我在这里要做的事情有点难以描述。我当前的需求要求我有一个可以实现接口的enum类型。虽然不是最漂亮的解决方案,但这是我想到的;

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));
}
}

下面是一个示例实现:

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
}

当我在继承上静态调用Parse时,我必须手动将结果从基类型转换回继承类型,因为Parse返回一个泛型EnumClass<T>对象。

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

我的问题本质上是,有没有办法写Parse,使它返回继承的类型,而不是基类?我也想能够标记EnumClass<T>抽象,但如果我现在尝试这样做,编译器将不会编译Parse,说明我不能创建类型EnumClass<T>的抽象实例返回。

您可以使用奇怪的递归模板模式,但它需要默认构造函数,感觉很奇怪。通常情况下,如果事情变得如此复杂,你应该问问自己,是否可以重新调整你的需求,使其变得不那么复杂,但根据给出的细节,很难知道这是否可能。也就是说,这可能是你所能得到的最接近你所要求的了。

没有办法指定方法返回派生类型,但是可以使用泛型类型指定返回类型。下面是EnumClass,但修改为接受两个泛型类型。第一个类型和前面一样是枚举类型,但第二个类型用于指定派生类型(因此是模板的递归部分)。

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};
}
}

然后,使用此EnumClass的派生类型将看起来像这样,其中第二个泛型类型递归地引用自身,这意味着EnumClass中的静态Parse方法将返回类型AnimalTypes

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

在使用中,它看起来像这样

//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}");

您需要添加另一个类型参数,以便将Parse的返回值类型参数化,并启用创建派生/继承类型。

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));
}
}

最新更新