Search code examples
c#genericstaxonomytype-safety

Generic class with explicitly type-safe taxonomy


I'm looking for a way to create a generic base class that has a typesafe taxonomy using internal properties. Just to be clear, the class doesn't have to use the generics language feature as long as it is generic itself and I'm looking for something that has compile-time type safety.

As an example here is a simple taxonomy I want to represent using multiple instances of the same class

Wood
  Crate
  Box
Metal
  Crate
  Bar

The permutations of which are

Wood Crate
Wood Box
Metal Crate
Metal Bar

initially I though I could use enums to represent the different levels of taxonomy like so

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

I could create the items above using:

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)

but I could also create

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

which for my purposes is incorrect.

Do any of you know of a pattern that would let me create a single class to represent the example taxonomy in a type-safe way that prohibits the creation of incorrect combinations.

It also needs to be applicable to N levels of taxonomy, the 2 levels above are just an example.

Thank you


Update: I do require compile-time type safety. I could do this with multiple classes quite easily using inheritance and such, I'm trying to find a solution using instances of just a single base class.

let me know if you need any more info


Update: @Maarten Yes, i'm trying to sure that the hierarchy is maintained so if EFirstLevel is 1 then ESecondLevel must be either Crate or Box.

Just to clairify i'm happy to have other supporting classes, what i'm trying to avoid is having to explicitly create a class for each taxanomic value.

What I'm trying to accomplish is providing an example layout of class that that maintains this taxanomic type safety so I can reflect over it and permute combinations. While maintaining the type safety should I need to generically instantiate said permutations.

The class upon which I might reflect could come form a third party and as such I might not know beforehand the values for each level. I could generate all the possible combinations into a set of classes with type safe internal enums but this would require regeneration of said classes any time you changed the items in any level. I was just wondering if there was a was to achieve my goals without having to generate any classes.


EDIT: Moved this section to an answer


Solution

  • Think I've found what I'm looking for @Iridium had an answer close to what I think is going to be my solution, however rather than having to define each item as a class I think I've found a way to maintain the type safety and still be able to create the items as properties of a single base class.

    As in @Iridium's answer it does require the creation of linked classes defining the taxonomic relationships.

    Instead of using interfaces I remembered an SO answer I found a long time ago about pseudo enum inheritance with a protected constructor Question is Here see the answer by "Seven"

    If I define 2 base classes on which I can base the taxonomic chaining classes

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

    I can then use these to chain as many levels deep as needed (for very deep trees this may not be ideal)

    The first level inherits from the chain enum with no parent

    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) { }
    }
    

    Subsequent levels inherit from the chain link enum which defines a parent

    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) { }
    }
    

    A third level would use a signature like

    public class HEThirdLevelType : ChainLinkEnum<HEWoodItemTypes>
    

    After that set-up I can then define my single base item class like:

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

    or if I wanted an item with 5 levels of taxonomy where each is linked to the one before

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

    or 3 properties with one first level and 2 second levels both linked to the first

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

    Once the single base class is defined, i can reflect over it and the chain enums to get the permutations.

    each item is created as a property

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

    This maintains the type safety and means that I can new properties to a taxonomy level and not have to create classes for items(although I do have to generate the extra items as properties)

    This seems to do all i wanted but i've yet to try it extensively.

    @Iridium's answer was close but not quite what I was looking for, although it did help.