5

Consider the case of a base assembly base having an enum type like

public enum ItemState = { red, green, blue };

I use this base assembly within other assemblies Project_1, Project_2 and so on.

Each of them does some specific stuff, and requires project-specific states, like {grey, black, white, ...} in Project_1 and {brown, transparent, ...} in Project_2.

Project_1 isn't allowed to use (and if possible even see) {brown, transparent, ...}. Similarly, Project_2 cannot use {grey, black, white, ...}.

I know "partial enum" doesn't exist - so what is the suggested design pattern for such a task?

NinjaDeveloper
  • 1,620
  • 3
  • 19
  • 51
neggenbe
  • 1,697
  • 2
  • 24
  • 62
  • 1
    [Related, maybe dupe?](http://stackoverflow.com/questions/757684/enum-inheritance) (look beyond the accepted answer for alternatives) – James Thorpe Aug 09 '16 at 16:28
  • Can you use 2 enums? If not, why is it required to have access to only some of the values? I don't think there is design pattern regarding this and it seems more like bad choice in your model. – CrudaLilium Aug 09 '16 at 16:31

3 Answers3

8

Since enums cannot be inherited from, one solution might be to use a class with static constant members like this:

public class ItemState
{
    protected ItemState() { }

    public static ItemState red { get; } = new ItemState();
    public static ItemState green { get; } = new ItemState();
    public static ItemState blue { get; } = new ItemState();
}

Then in your Project_1 you can derive an own class:

public class ItemState_1 : ItemState
{
    public static ItemState grey { get; } = new ItemState_1();
    public static ItemState black white { get; } = new ItemState_1();
}

And in Project_2

public class ItemState_2 : ItemState
{
    public static ItemState brown { get; } = new ItemState_2();
    public static ItemState transparent white { get; } = new ItemState_2();
}

It's probably not the most comfortable way, but the best I can think of right now.

And you can use them like that:

ItemState project1State = ItemState_1.grey;

if (project1State == ItemState_1.grey)
   // do something

This all compiles fine, but unfortunatly those value cannot be used in a switch/case statement. This could be worked around with proper ToString() implementation, string literals can be used in switch/case. But that would of course add more code to those class/property definitions.

René Vogt
  • 43,056
  • 14
  • 77
  • 99
  • One of the advantages of this method is that you never have more than one object of each value. This gives you enum-like performance, as you only look at the pointer value rather than the object itself. – Jonathan Allen Aug 09 '16 at 16:53
  • I love that answer. One more thing is to make the class abstract to force assemblies using it to actually implement it! A big +1!!!! – neggenbe Aug 10 '16 at 12:44
6

I am a bit late, but here is "buffed" version of Rene`s answer (now with implicit casting!):

public class ColorEnum
{
    protected readonly string Name;
    protected readonly Color Value;

    public static readonly ColorEnum Red = new ColorEnum(Color.Red, "Red");
    public static readonly ColorEnum Green = new ColorEnum(Color.Green, "Green");
    public static readonly ColorEnum Blue = new ColorEnum(Color.Blue, "Blue");

    protected ColorEnum(Color value, string name)
    {
        Name = name;
        Value = value;
    }

    public override string ToString()
    {
        return Name;
    }

    public static implicit operator Color(ColorEnum @enum)
    {
        return @enum.Value;
    }

    public static implicit operator string(ColorEnum @enum)
    {
        return @enum.Name;
    }
}

public class AnotherColorEnum : ColorEnum
{
    public static readonly ColorEnum Grey = new AnotherColorEnum(Color.Gray, "Grey");
    public static readonly ColorEnum Black = new AnotherColorEnum(Color.Black, "Black");
    public static readonly ColorEnum White = new AnotherColorEnum(Color.White, "White");

    protected AnotherColorEnum(Color value, string name) : base(value, name)
    {
    }
}

This way you may use your "enum" like this:

    var color = ColorEnum.Red;
    var anothercolor = Color.Red;
    if (color == anothercolor)
    {
            //DoSomething
    }

Or like this:

    var color = ColorEnum.Red;
    var anothercolor = "Red";
    if (color == anothercolor)
    {
            //DoSomething
    }
Szer
  • 3,426
  • 3
  • 16
  • 36
  • Nice, and now if you have a `ColorEnum ce` you can do a `switch(ce.ToString()) case "Grey":....` which still doesn't seem perfect, but will work (`case ColorEnum.Grey.ToString()` will not compile). – René Vogt Aug 09 '16 at 16:44
  • I ended up using your answer René. Note that, as with enum, on can use a constant "int" value in the class, which one can then switch/case on...! – neggenbe Aug 11 '16 at 12:13
  • I *really* want to use this as an Attribute argument but get "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type" - Any way to cajole this into those requirements? See this [SO](https://stackoverflow.com/questions/25859094/an-attribute-argument-must-be-a-constant-expression-create-a-attribute-of) – ttugates Jul 18 '18 at 22:12
1

Depending on your use case, you may wish to use a master/subset pattern.

For example, I have one enum that contains every possible value:

/// <summary>
/// Types of limits that can be applied to a table, view, or table-value function query.
/// </summary>
/// <remarks>Databases are expected to provide their own enumeration that represents a subset of these options.</remarks>
[Flags]
public enum LimitOptions
{
    /// <summary>
    /// No limits were applied.
    /// </summary>
    None = 0,

    /// <summary>
    /// Returns the indicated number of rows with optional offset
    /// </summary>
    Rows = 1,

    /// <summary>
    /// Returns the indicated percentage of rows. May be applied to TableSample
    /// </summary>
    Percentage = 2,


    /// <summary>
    /// Adds WithTies behavior to Rows or Percentage
    /// </summary>
    WithTies = 4,


    /// <summary>
    /// Returns the top N rows. When there is a tie for the Nth record, this will cause it to be returned. 
    /// </summary>
    RowsWithTies = Rows | WithTies,

    /// <summary>
    /// Returns the top N rpercentage of ows. When there is a tie for the Nth record, this will cause it to be returned. 
    /// </summary>
    PercentageWithTies = Percentage | WithTies,

Then each project has its own subset of values:

/// <summary>
/// Limit options supported by Access.
/// </summary>
/// <remarks>This is a strict subset of LimitOptions</remarks>
public enum AccessLimitOption
{
    /// <summary>
    /// No limits were applied.
    /// </summary>
    None = LimitOptions.None,

    /// <summary>
    /// Uses TOP
    /// </summary>
    RowsWithTies = LimitOptions.RowsWithTies,

}

Rather than making the enumeration extensible, the subprojects always use a strict subset. This allows me to keep the core fairly generic, while offering database specific APIs where appropriate.

Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447