70

I am trying to obscure the index positions on an edi file... I had a situation where 2 or 3 things could be at an index based on the situation. It'd be cool to use an enum to hide the "magic numbers" and was suprised to see that you could assign multiple enums to the same value like this:

public enum Color
{
    Red = 1,
    Blue = 1,
    Green = 1
}

and the compiler is happy with this. I didn't expect this to work. I don't need to cast back to the enum so I'm not worried about trying to go back, but this smells funky. Why does the CLR allow multiple values for enums and should I use a struct for this? (A struct seemed heavier duty than an enum and this seems to work)

Rikon
  • 2,688
  • 3
  • 22
  • 32
  • 3
    This is invariably best answered with the opposite question: why would it *not* allow this? It is handy when you, say, include a First and Last enum member. – Hans Passant Nov 07 '11 at 21:47
  • 1
    how to you want to use "struct for this"? – wiero Nov 07 '11 at 21:49
  • I could use a struct to get the enum "look" and I wouldn't have to cast. something like "public static int Red { get{ return 1; }}" – Rikon Nov 07 '11 at 21:51

8 Answers8

92

Actually you're already defining a struct... Behind the scenes an enum is just a struct (but which derives from System.Enum) and the values of the enum are defined as constants (you can verify this with ILDASM).

Your enum definition translates into the following pseudo C# code:

public struct Color : System.Enum
{
    public const int Red = 1;
    public const int Blue = 1;
    public const int Green = 1;
}

The above code won't compile in C# because the compiler doesn't allow defining a struct with an explicit base class, but that's what it emits for an enum definition.

Since there is no problem with a type that contains an multiple constants that have the same value, there is no problem with the enum definition.

But since the enum does not have unique values you might have an issue when converting into this enum. For example the following two line of codes will return the enum value Red, because the first value is arbitrarily selected.

Color color1 = (Color)1;
Color color2 = (Color)Enum.Parse(typeof(Color), "1");

Strictly speaking the enum value is not Red, it is 1, but when you print out the value you'll see Red.

Also, the following boolean is true which looks a bit weird...

// true (Red is Green??)
bool b = Color.Red == Color.Green;

At the bottom line this is perfectly legal, but it's up to you to use it when it makes sense...

Here is a direct link to the section of my .NET tutorial that discusses enumerations under the hood: http://motti.me/c1E

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Motti Shaked
  • 1,854
  • 15
  • 8
  • Also see: http://stackoverflow.com/questions/1425777/how-to-prevent-duplicate-values-in-enum – LosManos Oct 30 '12 at 14:44
  • 40
    Some of us are red-green colorblind, so the last line of code there makes perfect sense ;-) – theMayer Mar 17 '13 at 06:37
  • 1
    Note that `Object.Equals` can't tell the difference between `Red` and `Green` either in this case. Also, naturally, `Object.ReferenceEquals` is always false for any pair of `Enum`s because it's not helpful when comparing value types. There really is no way I can see to know the difference between `Red` and `Green`; VS can't even display the right field name so I'd assume that not even going by `FieldName` somehow would work. – jrh Oct 09 '18 at 17:32
  • Switch/cases also won't have full coverage of enums because two cases can't address the same label value. – BLAZORLOVER Jan 22 '22 at 22:27
26

That's perfectly legal C#. From the C# Language specification version 4.0, section 14.3:

Multiple enum members may share the same associated value. The example

enum Color 
{
   Red,
   Green,
   Blue,
   Max = Blue
}

shows an enum in which two enum members—Blue and Max—have the same associated value.

Irvin Dominin
  • 30,819
  • 9
  • 77
  • 111
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • It worth knowing [CA1069](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1069) – Daniel B Aug 07 '23 at 16:46
11

The same numeric value but different name is nothing else as an alias. It could be e.g.

public enum Color
{
   DefaultColor = 1,
   Red = 1,
   Blue = 2
}

It can make sense in some cases but not many. When you parse the values back and call colorValue.ToString() you will get the last value as stringified value back (Red in this case) but you will loose the conept of default colors since it is the same thing. At least in the way you did model your data. If you want to keep it separate use different values for different things.

Alois Kraus
  • 13,229
  • 1
  • 38
  • 64
  • 2
    This is a good "why" example: because you want to enumerate the values, but also name a default (or a min/max). – Jim Mischel Nov 07 '11 at 21:58
6

This would be a perfectly acceptable definition:

public enum AllTheThings
{
    TheMoney = 1,
    TheFreeRides = 1,
    TheLieThatYouDenied = 2,
    TheCallsYouveBeenMaking = 3,
    TheTimesYouveBeenFaking = 4
}
Phil
  • 1,062
  • 1
  • 17
  • 15
5

One thing to be aware of is that if you are relying on C# to automatically assign the enum values, then the order of any aliased members becomes important. Consider the following:

public enum Foo
{
    Alpha,  // 0
    Bravo,  // 1
    Charlie,  // 2
    Delta,  // 3
}

If you add an alias in there, it will reset the auto-numbering at that position:

public enum Foo
{
    Alpha,  // 0
    Bravo,  // 1
    Charlie,  // 2
    AlsoBravo = Bravo,  // AlsoBravo assigned 1, same as Bravo
    Delta,  // Delta is now 2, not 3 as you might expect
}

Usually this is not an issue because the aliased members come directly after the members that they are aliasing. This is fine and works as expected:

public enum Foo
{
    Alpha,  // 0
    Bravo,  // 1
    AlsoBravo = Bravo,  // AlsoBravo assigned 1, same as Bravo
    Charlie,  // Continues with 2, as expected
    Delta,  // 3
}

This issue bit me today because I had an enum whose members had attributes that I didn't want to duplicate, something similar to this:

public enum AppIcon
{
    [IconMapping(Icon.Cogs)] MenuItem_AppSettingsTab,  // 0
    [IconMapping(Icon.TabRemove)] MenuItem_CloseTab,  // 1

    RootTab_AppSettings = MenuItem_AppSettingsTab,  // 0
    [IconMapping(Icon.Cube)] RootTab_Package,  // 1 not 3, oops!
}
Steven Rands
  • 5,160
  • 3
  • 27
  • 56
3

If you think of each enum value as a constant, it makes sense. There's no reason why you shouldn't be able to have two constants with the same value:

public enum MyColor 
{ 
    Blue = 2,         
    Yellow = 3,
    Green = 4
    BlueAndYellow = 4,        
} 

Is the same as:

public enum MyColor 
{ 
    Blue = 2,         
    Yellow = 3,
    Green = 4,
    BlueAndYellow = Green,        
} 

Essentially you just have the same constant with two different names. BlueAndYellow is an alias for Green.

Igby Largeman
  • 16,495
  • 3
  • 60
  • 86
2

One thing to note here, is that non unique values result in missing and duplicated values in Visual Studio designer.

public enum MyColor
{
Red= 1,
Green= 1,
Blue= 2
}

if you use this enum in a browable property you will see Green,Green,Blue in designer rather than Red, Green, Blue.

Zero
  • 21
  • 1
2

Having multiple members of the enum pointing to the same value may cause confusion. I added a code fix via a simple extension for this on Visual Studio Marketplace.

UniqueEnumValueFixer

The source code is available here: https://github.com/toreaurstadboss/UniqueEnumValuesAnalyzer

The part where we detect if the enum got multiple members with the same value is shown below. The code is built upon the Analyzer with Code Fix (.NET Standard) project type after installing the .NET Compiler SDK (Roslyn).

   public override void Initialize(AnalysisContext context)
    {
        // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols
        // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
        context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

    }

    private static void AnalyzeSymbol(SymbolAnalysisContext context)
    {
        try
        {
            var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
            if (namedTypeSymbol.EnumUnderlyingType != null)
            {
                var valueListForEnum = new List<Tuple<string, int>>();
                //Debugger.Launch();
                //Debugger.Break();
                var typeResolved = context.Compilation.GetTypeByMetadataName(namedTypeSymbol.MetadataName) ?? context.Compilation.GetTypeByMetadataName(namedTypeSymbol.ToString());
                if (typeResolved != null)
                {
                    foreach (var member in typeResolved.GetMembers())
                    {
                        var c = member.GetType().GetRuntimeProperty("ConstantValue");
                        if (c == null)
                        {
                            c = member.GetType().GetRuntimeProperties().FirstOrDefault(prop =>
                                prop != null && prop.Name != null &&
                                prop.Name.Contains("IFieldSymbol.ConstantValue"));
                            if (c == null)
                            {
                                continue;
                            }
                        }

                        var v = c.GetValue(member) as int?;
                        if (v.HasValue)
                        {
                            valueListForEnum.Add(new Tuple<string, int>(member.Name, v.Value));
                        }
                    }
                    if (valueListForEnum.GroupBy(v => v.Item2).Any(g => g.Count() > 1))
                    {
                        var diagnostic = Diagnostic.Create(Rule, namedTypeSymbol.Locations[0],
                            namedTypeSymbol.Name);
                        context.ReportDiagnostic(diagnostic);
                    }
                }
            }
        }
        catch (Exception err)
        {
            Console.WriteLine(err);
        }

    }

The enum IceCream looks like this:

enum IceCream { Vanilla = 0, Chocolate = 2, Strawberry = Vanilla, Peach = 2 }

Code fixer in use in VS

Tore Aurstad
  • 3,189
  • 1
  • 27
  • 22