将枚举映射到“子枚举”
本文关键字:枚举 子枚举 映射 | 更新日期: 2023-09-27 18:27:08
我有一个包含产品的数据库。这些产品已分类,具有类别和子类别。
例如:
Product p1 = new Product()
{
Category = Category.Fruit,
Subcategory = Subcategory.Apple
};
我的问题是我想根据类别限制子类别。
以下示例应该是不可能的:
Product p2 = new Product()
{
Category = Category.Fruit,
Subcategory = Subcategory.Cheese
};
此外,我希望能够返回一个字符串数组(匹配每个类别枚举(,每个字符串都有一个相应子类别的数组。
我已经思考了一段时间,但一无所获,也没有在网上找到任何解决方案。
会有什么建议?
我喜欢地图规则。您还可以在枚举值上放置自定义属性。
例如:
public enum Subcategory {
[SubcategoryOf(Category.Fruit)]
Apple,
[SubcategoryOf(Category.Dairy)]
Emmenthaler
}
这要求您编写一个SubcategoryOfAttribute
类(有关 MS 指南,请参阅此处(。然后,您可以编写一个验证程序,该验证程序可以查看任何子类别并从中获取合法的父类别。
与地图相比,这样做的好处是关系在声明中得到了很好的阐述。
缺点是每个子类别最多可以有一个父类别。
我发现这很有趣,所以我把它存了下来。 首先是属性:
[AttributeUsage(AttributeTargets.Field)]
public class SubcategoryOf : Attribute {
public SubcategoryOf(Category cat) {
Category = cat;
}
public Category Category { get; private set; }
}
然后我们做一些模拟枚举
public enum Category {
Fruit,
Dairy,
Vegetable,
Electronics
}
public enum Subcategory {
[SubcategoryOf(Category.Fruit)]
Apple,
[SubcategoryOf(Category.Dairy)]
Buttermilk,
[SubcategoryOf(Category.Dairy)]
Emmenthaler,
[SubcategoryOf(Category.Fruit)]
Orange,
[SubcategoryOf(Category.Electronics)]
Mp3Player
}
现在我们需要一个谓词来确定子类别是否与类别匹配(注意:如果需要,您可以有多个父类别 - 您需要修改属性和此谓词以获取所有属性并检查每个属性。
public static class Extensions {
public static bool IsSubcategoryOf(this Subcategory sub, Category cat) {
Type t = typeof(Subcategory);
MemberInfo mi = t.GetMember(sub.ToString()).FirstOrDefault(m => m.GetCustomAttribute(typeof(SubcategoryOf)) != null);
if (mi == null) throw new ArgumentException("Subcategory " + sub + " has no category.");
SubcategoryOf subAttr = (SubcategoryOf)mi.GetCustomAttribute(typeof(SubcategoryOf));
return subAttr.Category == cat;
}
}
然后,您输入您的产品类型进行测试:
public class Product {
public Product(Category cat, Subcategory sub) {
if (!sub.IsSubcategoryOf(cat)) throw new ArgumentException(
String.Format("{0} is not a sub category of {1}.", sub, cat), "sub");
Category = cat;
Subcategory = sub;
}
public Category Category { get; private set; }
public Subcategory Subcategory { get; private set; }
}
测试代码:
Product p = new Product(Category.Electronics, Subcategory.Mp3Player); // succeeds
Product q = new Product(Category.Dairy, Subcategory.Apple); // throws an exception
您要做的是使用枚举表示一阶逻辑(http://en.wikipedia.org/wiki/First-order_logic(。并与数据库同步。在代码中对其进行硬编码时,这不是一件容易的事。已经提出了许多好的解决方案。
就我而言,我只会对类别和子类别使用字符串(或唯一 ID(,并使用数据库中定义的规则强制执行完整性。但是,如果您最终在代码中使用它,则它不会是编译时。
Enum 的问题在于它必须与外部源代码和代码匹配。此外,很难附加更多信息,例如价格或国家,或者即使您有不同种类的苹果。
我的建议是有一个Dictionary<SubCategory, Category>
,将您的SubCategory
映射到您的Category
。
之后,您可以一起摆脱产品上的Category
,也可以使用辅助方法
public class Product
{
static Dictionary<SubCategory, Category> _categoriesMap;
public static Product()
{
_categoriesMap = new Dictionary<SubCategory, Category>();
_categoriesMap.Add(SubCategory.Apple, Category.Fruit);
}
public SubCategory SubCategory { get; set; }
public Category Category
{
get { return _categoriesMap[this.SubCategory]; }
}
}
我能够修改@plinth发布的代码
[AttributeUsage(AttributeTargets.Field,
AllowMultiple = true)]
public class SubcategoryOf : Attribute
{
public Enum? Category { get; }
public SubcategoryOf(object category)
{
if (category is Enum categoryEnum) Category = categoryEnum;
}
}
public static class CategoryExtension
{
public static bool IsSubcategoryOf(this Enum subcategory,
Enum category)
{
var t = subcategory.GetType();
/*This section is for returning true if enum is
of the same category (eg. Product.Fruit is Product.Fruit) */
if (Equals(subcategory,
category))
return true;
var memberInfo = t.GetMember(subcategory.ToString());
/*This section loops through all attributes for a match of
declared category and returns its result true/false */
foreach (var member in memberInfo)
if (member.IsDefined(typeof(SubcategoryOf)))
return member.GetCustomAttributes(typeof(SubcategoryOf))
.Cast<SubcategoryOf?>()
.Any(subCatOf => Equals(subCatOf!.Category,
category));
/*If a category is not assigned a warning is posted to the
debug console that an attempt to compare an enum without a
category was made and returns false, this does not stop the
program with a throw exception as this should not break the
intended use of comparing.*/
Debug.WriteLine($"the enum {subcategory} does not contain a category and was being compared.",
"Warning(CategoryExtension.IsSubcategoryOf)");
return false;
}
}
以下是使用上述类的示例:
public enum Proficiency
{
ArmorProficiency,
WeaponProficiency,
ToolProficiency,
LanguageProficiency
}
public enum ArmorProficiency
{
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
Light,
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
Medium,
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
Heavy,
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
Shield,
}
public enum LightArmorProficiency
{
[IsSubcategoryOf(ArmorProficiency.Light)]
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
PaddedArmor,
[IsSubcategoryOf(ArmorProficiency.Light)]
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
LeatherArmor,
[IsSubcategoryOf(ArmorProficiency.Light)]
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
StuddedLeatherArmor,
}
public enum HeavyArmorProficiency
{
[IsSubcategoryOf(ArmorProficiency.Heavy)]
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
RingMail,
[IsSubcategoryOf(ArmorProficiency.Heavy)]
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
ChainMail,
[IsSubcategoryOf(ArmorProficiency.Heavy)]
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
SplintMail,
[IsSubcategoryOf(ArmorProficiency.Heavy)]
[IsSubcategoryOf(Proficiency.ArmorProficiency)]
PlateMail,
}
下面是如何将类别类与枚举一起使用的示例。
public class Proficiencies
{
private readonly Dictionary<Enum, ProficiencyType> _armorProficiencies;
private readonly Dictionary<Enum, ProficiencyType> _weaponProficiencies;
private readonly Dictionary<Enum, ProficiencyType> _toolProficiencies;
private readonly Dictionary<Enum, LanguageComprehension> _languageProficiencies;
public IReadOnlyDictionary<Enum, ProficiencyType> ArmorProficiencies =>
_armorProficiencies;
public IReadOnlyDictionary<Enum, ProficiencyType> WeaponProficiencies =>
_weaponProficiencies;
public IReadOnlyDictionary<Enum, ProficiencyType> ToolProficiencies =>
_toolProficiencies;
public IReadOnlyDictionary<Enum, LanguageComprehension> LanguageProficiencies =>
_languageProficiencies;
public Proficiencies(bool startsWithCommon,
LanguageComprehension comprehensionLevel = LanguageComprehension.ReadAndWrite)
{
_armorProficiencies = new Dictionary<Enum, ProficiencyType>();
_weaponProficiencies = new Dictionary<Enum, ProficiencyType>();
_toolProficiencies = new Dictionary<Enum, ProficiencyType>();
_languageProficiencies = new Dictionary<Enum, LanguageComprehension>();
if (startsWithCommon)
_languageProficiencies.Add(StandardLanguageProficiency.Common,
comprehensionLevel);
}
public void AddNonLanguageProficiency(Enum proficiency,
ProficiencyType proficiencyType)
{
if (proficiency.IsSubcategoryOf(Proficiency.ArmorProficiency))
{
if (_armorProficiencies.ContainsKey(proficiency))
_armorProficiencies[proficiency] = proficiencyType;
else
_armorProficiencies.Add(proficiency,
proficiencyType);
}
else if (proficiency.IsSubcategoryOf(Proficiency.WeaponProficiency))
{
if (_weaponProficiencies.ContainsKey(proficiency))
_weaponProficiencies[proficiency] = proficiencyType;
else
_weaponProficiencies.Add(proficiency,
proficiencyType);
}
else if (proficiency.IsSubcategoryOf(Proficiency.ToolProficiency))
{
if (_toolProficiencies.ContainsKey(proficiency))
_toolProficiencies[proficiency] = proficiencyType;
else
_toolProficiencies.Add(proficiency,
proficiencyType);
}
else
{
Debug.WriteLine($"The enum {proficiency} is not a valid proficiency and was being added.",
"Warning(Proficiencies.AddProficiency)");
}
}
public void RemoveProficiency(Enum proficiency)
{
if ( _armorProficiencies.ContainsKey(proficiency) )
_armorProficiencies.Remove(proficiency);
else if ( _weaponProficiencies.ContainsKey(proficiency) )
_weaponProficiencies.Remove(proficiency);
else if ( _toolProficiencies.ContainsKey(proficiency) )
_toolProficiencies.Remove(proficiency);
else if ( _languageProficiencies.ContainsKey(proficiency))
_languageProficiencies.Remove(proficiency);
}
public void AddLanguageProficiency(Enum language,
LanguageComprehension comprehension)
{
if (!language.IsSubcategoryOf(Proficiency.LanguageProficiency)) return;
if (_languageProficiencies.ContainsKey(language))
_languageProficiencies[language] = comprehension;
else
_languageProficiencies.Add(language,
comprehension);
}
}
总结:在这种情况下,主类别现在由提供的枚举定义,而不是硬编码,子类别类不关心给出的内容,只要它是对象类型枚举。
扩展类现在仅按属性检查子类别是否属于类别或其他类别,并且不对枚举类型进行硬编码。这反过来又允许使用多个枚举集合,而无需将所有内容强制到单个枚举中,如果子类别不属于,它将返回 false。