Curiously Recurring Template Pattern, Multiple Layers of Inheritance
在这里完成的工作的基础上,我为枚举定义了一个通用的抽象基类,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public abstract class Enumeration<T> : IEquatable<T> where T : Enumeration<T> { private static IEnumerable<T> enumerateAllValues() { // Obviously use some caching here var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); return fields.Select(f => f.GetValue(null)).OfType<T>(); } internal static IEnumerable<T> AllValues {get { return enumerateAllValues();}} protected Enumeration(int value, string displayName) { if (!typeof(T).IsSealed) throw new NotSupportedException($"Value objects must be sealed, {typeof(T).Name} is not."); this.Value = value; this.DisplayName = displayName; } protected int Value { get; } protected string DisplayName { get; } public override string ToString() { return DisplayName; } // IEquatable implementation based solely on this.Value } |
以及用于分析和列出枚举值的静态非泛型帮助器类:
1 2 3 4 5 6 7 8 | public static class Enumeration { public static IEnumerable<T> GetAllValues<T>() where T : Enumeration<T> { return Enumeration<T>.AllValues; } // Other helper methods, e.g. T Parse(int), bool TryParse(int, out T), etc. } |
现在,我从中派生出另一个抽象类来表示一类具有共同点的枚举:
1 2 3 4 | public abstract class AnimalTrait<T> : Enumeration<AnimalTrait<T>> { protected AnimalTrait(int value, string displayName) : base(value, displayName) { ; } } |
到现在为止,一直都还不错。作为一个例子,从中派生出的具体类可能是dogtrait或fishtrait等。我知道所有动物特征都可以与一个值配对,并且假设动物特征的值总是一个字符串,然后我定义另一个抽象类,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public struct AnimalTraitValuePair<TAnimalTrait> where TAnimalTrait : AnimalTrait<TAnimalTrait> { public TAnimalTrait AnimalTrait { get; } public string Value { get; } // Analogy breaks down here, but lets assume we know that the values of animal traits are always strings. public AnimalTraitValuePair(TAnimalTrait animalTrait, string value) { this.AnimalTrait = animalTrait; this.Value = value; } public override string ToString() { return $"[{AnimalTrait}, {Value}]"; } } |
类似于从
现在,当我去定义持有动物名称的动物类,它的动物列表及其相关值,即EDOCX1[1]的列表时,我遇到了一个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public abstract class Animal<TAnimal, TAnimalTrait> : where TAnimal : Animal<TAnimal, TAnimalTrait> where TAnimalTrait : AnimalTrait<TAnimalTrait> { private readonly IList<AnimalTraitValuePair<TAnimalTrait>> animalTraitValuePairList; // All animals have a name public string Name {get;} protected Animal(string name, IEnumerable<AnimalTraitValuePair<TAnimalTrait>> animalTraitValuePairs) { animalTraitValuePairList = animalTraitValuePairs.ToList(); this.Name = name; } public string this[TAnimalTrait animalTrait] { get { return animalTraitValuePairList.First(atvp => atvp.AnimalTrait == animalTrait).Value; } } public override string ToString() { StringBuilder sb = new StringBuilder(); // !!!! BREAKS HERE !!!! foreach (var animalTrait in Enumeration.GetAllValues<AnimalTrait<TAnimalTrait>>()) // This works... //foreach (var animalTrait in Enumeration.GetAllValues<TAnimalTrait>()) // ...but this doesn't { sb.AppendLine($"{this.Name}'s traits:"); sb.AppendLine($"[{animalTrait}, {animalTrait.Value}]"); } return sb.ToString(); } } |
我得到这个编译器错误:
The type 'TAnimalTrait' cannot be used as type parameter 'T' in the generic type or method 'Enumeration.GetAllValues ()'. There is no implicit reference conversion from 'TAnimalTrait' to 'Maxim.Common.Enums.Enumeration '
为什么我不能直接使用Tanimaltrait?Tanimaltrait是否仅限于
您的代码有很多问题,我对所有必须更改的内容都失去了跟踪,但这里有一个工作片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | void Main() { Console.WriteLine(Dog.Fido.ToString()); } public abstract class Enumeration<T> where T : Enumeration<T> { private static IEnumerable<T> enumerateAllValues() { // Obviously use some caching here var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); return fields.Select(f => f.GetValue(null)).OfType<T>(); } internal static IEnumerable<T> AllValues { get { return enumerateAllValues();}} protected Enumeration(int value, string displayName) { if (!typeof(T).IsSealed) throw new NotSupportedException($"Value objects must be sealed, {typeof(T).Name} is not."); this.Value = value; this.DisplayName = displayName; } protected int Value { get; } protected string DisplayName { get; } public override string ToString() { return DisplayName; } // IEquatable implementation based solely on this.Value } public static class Enumeration { public static IEnumerable<T> GetAllValues<T>() where T : Enumeration<T> { return Enumeration<T>.AllValues; } // Other helper methods, e.g. T Parse(int), bool TryParse(int, out T), etc. } public abstract class AnimalTrait<T> : Enumeration<T> where T : AnimalTrait<T> { protected AnimalTrait(int value, string displayName) : base(value, displayName) {; } } public struct AnimalTraitValuePair<TAnimalTrait> where TAnimalTrait : AnimalTrait<TAnimalTrait> { public TAnimalTrait AnimalTrait { get; } public string Value { get; } // Analogy breaks down here, but lets assume we know that the values of animal traits are always strings. public AnimalTraitValuePair(TAnimalTrait animalTrait, string value) { this.AnimalTrait = animalTrait; this.Value = value; } public override string ToString() { return $"[{AnimalTrait}, {Value}]"; } } public abstract class Animal<TAnimal, TAnimalTrait> : Enumeration<TAnimal> where TAnimal : Animal<TAnimal, TAnimalTrait> where TAnimalTrait : AnimalTrait<TAnimalTrait> { private readonly IList<AnimalTraitValuePair<TAnimalTrait>> animalTraitValuePairList; // All animals have a name public string Name { get; } protected Animal(int i, string name, IEnumerable<AnimalTraitValuePair<TAnimalTrait>> animalTraitValuePairs) : base(i, name) { animalTraitValuePairList = animalTraitValuePairs.ToList(); this.Name = name; } public string this[TAnimalTrait animalTrait] { get { return animalTraitValuePairList.First(atvp => atvp.AnimalTrait == animalTrait).Value; } } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.AppendLine($"{this.Name}'s traits:"); foreach (var animalTrait in Enumeration.GetAllValues<TAnimalTrait>()) { sb.AppendLine($"[{animalTrait}, {this[animalTrait]}]"); } return sb.ToString(); } } public sealed class DogTrait : AnimalTrait<DogTrait> { public DogTrait(int i, string name) : base(i, name) { } public static DogTrait Color = new DogTrait(1,"Color"); public static DogTrait Size = new DogTrait(2,"Size"); } public sealed class Dog : Animal<Dog, DogTrait> { public Dog(int i, string name, IEnumerable<AnimalTraitValuePair<DogTrait>> animalTraitValuePairs) : base(i, name, animalTraitValuePairs) { } public static Dog Fido = new Dog(1,"Fido", new[] { new AnimalTraitValuePair<DogTrait>(DogTrait.Color,"Black"), new AnimalTraitValuePair<DogTrait>(DogTrait.Size,"Medium"), }); } |
输出:
Fido's traits:
[Color, Black]
[Size, Medium]
您对AnimalTraitValuePair有限制
1 2 | public struct AnimalTraitValuePair<TAnimalTrait> where TAnimalTrait : AnimalTrait<TAnimalTrait> |
当你使用它的时候,你是带着动物约束的泰尼玛人。
1 2 3 4 | public abstract class Animal<TAnimal, TAnimalTrait> : IEnumerable<AnimalTraitValuePair<TAnimal>> where TAnimal : Animal<TAnimal, TAnimalTrait> where TAnimalTrait : AnimalTrait<TAnimalTrait> |
如果将其更改为以下内容:
1 2 3 4 | public abstract class Animal<TAnimal, TAnimalTrait> : IEnumerable<AnimalTraitValuePair<TAnimalTrait>> where TAnimal : Animal<TAnimal, TAnimalTrait> where TAnimalTrait : AnimalTrait<TAnimalTrait> |
你会得到一个错误声明
1 |
这是因为您的动物类不是从枚举中派生的
老实说,由于IList是IEnumerable的通用实现,如果您希望实现相同目标的简单实现,我只需执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class Animal { private IList<AnimalTrait> _traits; public Animal(IList<AnimalTrait> traits) { _traits = traits; } public IEnumerable<AnimalTrait> Traits{get{return _traits;}} } public class AnimalTrait { public int Value{get;set;} public string DisplayName{get;set;} } |