4

我正在寻找一种方法来创建具有使用内部属性的类型安全分类的通用基类。为了清楚起见,该类不必使用泛型语言功能,只要它本身是泛型的,并且我正在寻找具有编译时类型安全性的东西。

作为一个例子,这里是一个简单的分类法,我想使用同一类的多个实例来表示

Wood
  Crate
  Box
Metal
  Crate
  Bar

其中的排列是

Wood Crate
Wood Box
Metal Crate
Metal Bar

最初我虽然可以像这样使用枚举来表示不同级别的分类

public enum EFirstLevel
{
    Wood,
    Metal
}

public enum ESecondLevel
{
    Crate,
    Box,
    Bar
}


public class BaseItem
{
    EFirstLevel FirstLevel;
    ESecondLevel SecondLevel;

    public BaseItem(EFirstLevel aFirst, ESecondLevel aSecond)
    {
        FirstLevel = aFirst;
        SecondLevel = aSecond;
    }
}

我可以使用以下方法创建上述项目:

var item1 = new BaseItem(EFirstLevel.Wood,ESecondLevel.Crate)
var item2 = new BaseItem(EFirstLevel.Wood,ESecondLevel.Box)
var item3 = new BaseItem(EFirstLevel.Metal,ESecondLevel.Crate)
var item4 = new BaseItem(EFirstLevel.Metal,ESecondLevel.Bar)

但我也可以创造

var item5 = new BaseItem(EFirstLevel.Wood,ESecondLevel.Bar)

就我的目的而言,这是不正确的。

你们中是否有人知道一种模式,它可以让我创建一个单一的类来以一种类型安全的方式来表示示例分类法,从而禁止创建不正确的组合。

它还需要适用于N个分类级别,上面的2个级别只是一个例子。

谢谢


更新:我确实需要编译时类型安全。我可以使用继承很容易地使用多个类来做到这一点,我试图找到一个使用单个基类实例的解决方案。

如果您需要更多信息,请告诉我


更新:@Maarten 是的,我试图确保保持层次结构,因此如果 EFirstLevel 为 1,则 ESecondLevel 必须是 Crate 或 Box。

只是为了澄清我很高兴有其他支持类,我试图避免的是必须为每个分类值显式创建一个类。

我想要完成的是提供一个类的示例布局,以保持这种分类类型的安全性,以便我可以对其进行反思并置换组合。在保持类型安全的同时,我应该需要一般地实例化所述排列。

我可能反映的课程可能来自第三方,因此我可能事先不知道每个级别的值。我可以将所有可能的组合生成到具有类型安全内部枚举的一组类中,但这将需要在您更改任何级别的项目时重新生成所述类。我只是想知道是否有一种方法可以在无需生成任何类的情况下实现我的目标。


编辑:将此部分移至答案

4

4 回答 4

3

我认为如果不创建类/接口在编译时检查该对象是否符合您的分类法,您将无法逃脱。

我建议如下解决方案:

// Define the taxonomic levels here. Each level (except the first) references its next-higher taxonomic level in a type constraint
interface Material { }
interface Object<TMaterial> where TMaterial : Material { }

// Define the items in the highest taxonomic level (materials)
interface Wood : Material { }
interface Metal : Material { }

// Define the items in the 2nd taxonomic level (objects), implementing the appropriate interfaces to specify what the valid top-level taxonomies it can fall under.
interface Crate : Object<Wood>, Object<Metal> { }
interface Bar : Object<Metal> { }
interface Box : Object<Wood> { }

// Define an item class with type constraints to ensure the taxonomy is correct
abstract class Item<TMaterial, TObject>
    where TMaterial : Material
    where TObject : Object<TMaterial>
{
}

有了上面的定义,我们现在可以定义有效的项目:

class MetalBar : Item<Metal, Bar> { }
class MetalCrate : Item<Metal, Crate> { }
class WoodCrate : Item<Wood, Crate> { }
class WoodBox : Item<Wood, Box> { }

但是,尝试创建无效项目(例如木条)会导致编译时错误:

class WoodBar : Item<Wood, Bar> { }

类型“Taxonomy.Bar”不能用作泛型类型或方法“Taxonomy.Item”中的类型参数“TObject”。没有从“Taxonomy.Bar”到“Taxonomy.Object”的隐式引用转换

于 2013-08-14T09:38:47.707 回答
1

两种方式——

  1. 创建一个枚举,它将包含你的树的叶子,在你的例子中是 Wood_Crate、Wood_box 等等。易于操作且不易维护或阅读。

  2. 为树中的每个元素定义一个名为 Category 的类和一个静态类。例如

    public class Category
      {
        internal Category() {};
        public string Id; // This would be used to understand what you got, can be a list of enums or something
      }
    
      public static Category CrateCategory = new Category {Id = "Wood.Crate"};
    
      public static class Wood
      {
        // either way will work, one will let you directly access CrateCategory (if that is what you wish), the second will remove the need for different Crate categories
        public static Category Crate { get { return CrateCategory; } }
        public static Category Box { get { return new Category { Id = "Wood.Box" }; } }
      }
    

您的 BaseItem 构造函数将只接收一个类别。并且可以写类似的东西

new BaseItem(Box.Crate);

如果您将 Category 类放在另一个程序集中,您将确定没有人能够创建自己的 Categories。

这需要更多的工作,但对我来说似乎更优雅和可读。如果 N 非常大,您可以编写一些代码来为您生成类和类别标识符。

于 2013-08-14T06:38:01.290 回答
0

我的建议是为每种类型创建一个类,然后让有效的子类型从该类型继承。

最后将 BaseItem 类型编辑为通用类型并且只接受有效类型。

像这样(抱歉解释不好)

class Wood {}
class Metal {}
class Crate : Wood, Metal {}
class Box : Wood {}
class Bar : Metal {}

class BaseItem<T1, T2> where T2 : T1
{
}

这将为您提供编译时类型安全性(但我认为这不是最好的方法)

于 2013-08-14T07:50:32.763 回答
0

认为我已经找到了我正在寻找的东西 @Iridium 的答案与我认为将成为我的解决方案的答案很接近,但是我认为我不必将每个项目定义为一个类,我认为我已经找到了一种方法来维护类型安全,并且仍然能够将项目创建为单个基类的属性。

正如@Iridium 的回答一样,它确实需要创建定义分类关系的链接类。

我没有使用接口,而是记得很久以前我发现的一个关于使用受保护构造函数的伪枚举继承的 SO 答案 问题在这里,请参阅“七”的答案

如果我定义了 2 个基类,我可以在这些基类上建立分类链接类

public class ChainEnum
{
    public int IntValue { get; protected set; }

    public static readonly ChainEnum None = new ChainEnum(1);

    protected ChainEnum(int internalValue)
    {
        this.IntValue = internalValue;
    }
}

public class ChainLinkEnum<TParent> : ChainEnum where TParent : ChainEnum
{
    public TParent Parent { get; protected set; }

    protected ChainLinkEnum(int internalValue, TParent aParent)
        : base(internalValue)
    {
        Parent = aParent;
    }
}

然后我可以根据需要使用这些链接尽可能多的层次(对于非常深的树,这可能不理想)

第一级继承自没有父级的链枚举

public class HEBaseMaterial : ChainEnum
{
    public static readonly HEBaseMaterial Wood = new HEBaseMaterial(1);
    public static readonly HEBaseMaterial Metal = new HEBaseMaterial(1);

    protected HEBaseMaterial(int internalValue) : base(internalValue) { }
}

后续级别从定义父级的链链接枚举继承

public class HEWoodItemTypes : ChainLinkEnum<HEBaseMaterial>
{
    private static readonly HEBaseMaterial InternalParent = HEBaseMaterial.Wood;

    public static readonly HEWoodItemTypes Box = new HEWoodItemTypes(1);
    public static readonly HEWoodItemTypes Crate = new HEWoodItemTypes(1);

    protected HEWoodItemTypes(int internalValue) : base(internalValue, InternalParent)
    { }
}

public class HEMetalItemTypes : ChainLinkEnum<HEBaseMaterial>
{
    private static readonly HEBaseMaterial InternalParent = HEBaseMaterial.Metal;

    public static readonly HEMetalItemTypes Box = new HEMetalItemTypes(1);
    public static readonly HEMetalItemTypes Bar = new HEMetalItemTypes(1);

    protected HEMetalItemTypes(int internalValue) : base(internalValue, InternalParent) { }
}

第三级将使用类似的签名

public class HEThirdLevelType : ChainLinkEnum<HEWoodItemTypes>

设置完成后,我可以定义我的单个基项类,例如:

public class TwoLevelItem<T1,T2>
    where T1 : ChainEnum
    where T2 : ChainLinkEnum<T1>
{
    public T1 LevelOne { get; set; }
    public T2 LevelTwo { get; set; }
}

或者如果我想要一个具有 5 个分类级别的项目,每个级别都与之前的一个相关联

  public class FiveLevelItem<T1,T2>
        where T1 : ChainEnum
        where T2 : ChainLinkEnum<T1>
        where T3 : ChainLinkEnum<T2>
        where T4 : ChainLinkEnum<T3>
        where T5 : ChainLinkEnum<T4>

    {
        public T1 LevelOne { get; set; }
        public T2 LevelTwo { get; set; }
        public T3 LevelThree { get; set; }
        public T4 LevelFour { get; set; }
        public T5 LevelFive { get; set; }
    }

或 3 个属性,其中一个第一级和 2 个第二级都与第一级相连

public class LinkedItem<T1,T2_1,T2_2>
    where T1 : ChainEnum
    where T2_1 : ChainLinkEnum<T1>
    where T2_2 : ChainLinkEnum<T1>
{
    public T1 LevelOne { get; set; }
    public T2_1 LevelTwoOne { get; set; }
    public T2_2 LevelTwoTwo { get; set; }
}

一旦定义了单个基类,我就可以对其进行反思,并通过链枚举来获得排列。

每个项目都被创建为一个属性

var metalBox = new TwoLevelItem<HEBaseMaterial,HEMetalItemTypes>()
{
    LevelOne = HEBaseMaterial.Metal,
    LevelTwo = HEMetalItemTypes.Box
}

这保持了类型安全并意味着我可以将新属性添加到分类级别,而不必为项目创建类(尽管我必须生成额外的项目作为属性)

这似乎可以满足我的所有需求,但我还没有广泛尝试。

@Iridium 的答案很接近,但不是我想要的,尽管它确实有帮助。

于 2013-08-15T07:19:53.040 回答