奇怪的重复模板模式,多层继承
Curiously Recurring Template Pattern, Multiple Layers of Inheritance
基于已完成的工作 here,我为枚举定义了一个通用的抽象基础 class,如下所示:
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
}
还有一个静态的、非通用的助手class来解析和列出枚举的值:
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.
}
现在,我从这个派生出另一个抽象 class 来表示具有共同点的某些 class 枚举:
public abstract class AnimalTrait<T> : Enumeration<AnimalTrait<T>>
{
protected AnimalTrait(int value, string displayName) : base(value, displayName) { ; }
}
到目前为止一切顺利。例如,由此派生的具体 class 可能是 DogTrait 或 FishTrait 等。知道所有动物特征都可以与一个值配对,并假设动物特征的值始终是一个字符串,然后我像这样定义另一个抽象 class:
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<TAnimalTrait, string> where TAnimalTrait : AnimalTrait<TAnimalTrait>
派生,如果它不是结构,我会这样做。
现在,当我去定义包含动物名称的 Animal class 时,它是 AnimalTrait 及其相关值的列表,即 AnimalTraitValuePair<TAnimal>
的列表,我 运行 成问题:
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<T>()'. There is no implicit reference conversion from 'TAnimalTrait' to 'Maxim.Common.Enums.Enumeration<TAnimalTrait>'
为什么我不能直接使用TAnimalTrait? TAnimalTrait 不是限制为 AnimalTrait<TAnimalTrait>
的 class,我们知道它是一个枚举,因此可以向上转换两个级别到基础 Enumeration<T>
吗?是编译 "correct" 并提供我想要的行为的那个吗?
您对 AnimalTraitValuePair 有约束
public struct AnimalTraitValuePair<TAnimalTrait>
where TAnimalTrait : AnimalTrait<TAnimalTrait>
当你使用它时,你传递的是一个带有 Animal 约束的 TAnimal
public abstract class Animal<TAnimal, TAnimalTrait>
: IEnumerable<AnimalTraitValuePair<TAnimal>>
where TAnimal : Animal<TAnimal, TAnimalTrait>
where TAnimalTrait : AnimalTrait<TAnimalTrait>
如果改成如下:
public abstract class Animal<TAnimal, TAnimalTrait>
: IEnumerable<AnimalTraitValuePair<TAnimalTrait>>
where TAnimal : Animal<TAnimal, TAnimalTrait>
where TAnimalTrait : AnimalTrait<TAnimalTrait>
您将收到一条错误消息,指出
Enumeration<AnimalTrait<TAnimalTrait>>.Value is inaccessable due to its protection level.
发生这种情况是因为您的 Animal class 不是从 Enumeration>
派生的
老实说,由于 IList 是 IEnumerable 的通用实现,如果您想要一个实现相同目标的简单实现,我会执行以下操作:
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;}
}
你的代码有很多问题,我忘记了我必须改变的所有事情,但这里有一个有效的片段:
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]
基于已完成的工作 here,我为枚举定义了一个通用的抽象基础 class,如下所示:
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
}
还有一个静态的、非通用的助手class来解析和列出枚举的值:
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.
}
现在,我从这个派生出另一个抽象 class 来表示具有共同点的某些 class 枚举:
public abstract class AnimalTrait<T> : Enumeration<AnimalTrait<T>>
{
protected AnimalTrait(int value, string displayName) : base(value, displayName) { ; }
}
到目前为止一切顺利。例如,由此派生的具体 class 可能是 DogTrait 或 FishTrait 等。知道所有动物特征都可以与一个值配对,并假设动物特征的值始终是一个字符串,然后我像这样定义另一个抽象 class:
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<TAnimalTrait, string> where TAnimalTrait : AnimalTrait<TAnimalTrait>
派生,如果它不是结构,我会这样做。
现在,当我去定义包含动物名称的 Animal class 时,它是 AnimalTrait 及其相关值的列表,即 AnimalTraitValuePair<TAnimal>
的列表,我 运行 成问题:
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<T>()'. There is no implicit reference conversion from 'TAnimalTrait' to 'Maxim.Common.Enums.Enumeration<TAnimalTrait>'
为什么我不能直接使用TAnimalTrait? TAnimalTrait 不是限制为 AnimalTrait<TAnimalTrait>
的 class,我们知道它是一个枚举,因此可以向上转换两个级别到基础 Enumeration<T>
吗?是编译 "correct" 并提供我想要的行为的那个吗?
您对 AnimalTraitValuePair 有约束
public struct AnimalTraitValuePair<TAnimalTrait>
where TAnimalTrait : AnimalTrait<TAnimalTrait>
当你使用它时,你传递的是一个带有 Animal 约束的 TAnimal
public abstract class Animal<TAnimal, TAnimalTrait>
: IEnumerable<AnimalTraitValuePair<TAnimal>>
where TAnimal : Animal<TAnimal, TAnimalTrait>
where TAnimalTrait : AnimalTrait<TAnimalTrait>
如果改成如下:
public abstract class Animal<TAnimal, TAnimalTrait>
: IEnumerable<AnimalTraitValuePair<TAnimalTrait>>
where TAnimal : Animal<TAnimal, TAnimalTrait>
where TAnimalTrait : AnimalTrait<TAnimalTrait>
您将收到一条错误消息,指出
Enumeration<AnimalTrait<TAnimalTrait>>.Value is inaccessable due to its protection level.
发生这种情况是因为您的 Animal class 不是从 Enumeration
老实说,由于 IList
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;}
}
你的代码有很多问题,我忘记了我必须改变的所有事情,但这里有一个有效的片段:
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]