具有明确类型安全分类的泛型类

时间:2013-08-14 04:53:17

标签: c# generics taxonomy type-safety

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

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

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 个答案:

答案 0 :(得分: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”

的隐式引用转换

答案 1 :(得分: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" }; } }
      }
    
  3. 您的BaseItem构造函数只会收到一个类别。并且可以写出类似

    的内容
    new BaseItem(Box.Crate);
    

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

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

答案 2 :(得分:0)

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

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

像这样(对不起解释)

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

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

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

答案 3 :(得分:0)

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

在@ Iridium的答案中,它确实需要创建定义分类关系的链接类。

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

查看答案

如果我定义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个属性,其中一个第一级和第二个级别都链接到第一个

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的答案很接近,但不是我想要的,尽管它确实有帮助。