关于C#:奇怪的重复模板模式,多个继承层

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

类似于从KeyValuePair where TAnimalTrait : AnimalTrait派生,如果它不是结构,我会这样做。

现在,当我去定义持有动物名称的动物类,它的动物列表及其相关值,即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是否仅限于AnimalTrait的一类,我们知道它是一个枚举,因此可以将两个级别提升到基础Enumeration上?是那个编译"正确"并给我想要的行为的人吗?


您的代码有很多问题,我对所有必须更改的内容都失去了跟踪,但这里有一个工作片段:

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
Enumeration<AnimalTrait<TAnimalTrait>>.Value is inaccessable due to its protection level.

这是因为您的动物类不是从枚举中派生的>

老实说,由于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;}
}