Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C#
  4. Should we ever create enums? (C#, maybe valid for C++)

Should we ever create enums? (C#, maybe valid for C++)

Scheduled Pinned Locked Moved C#
csharpc++visual-studiohelptutorial
11 Posts 5 Posters 5 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • P Paulo Zemek

    The title os this message might look like a joke, but it is actually very serious. The other day I wanted to implement my own BigInteger class, and I was using bytes as the backing fields. Yet, I would only use values from 0 to 9 for each digit. Bing AI suggested me to create an enum with values ranging from D0 to D9 (I think their actual values are obvious). Yet, using an enum like that doesn't forbid users from doing things like (DecimalDigit)56 and pass 56 to an enum that was only supposed to support values ranging from 0 to 9. Of course I can validate the values at run-time... but the entire purpose of using an enum was to avoid mistakes like that. So, my solution was to create a class (in fact, a struct, but a class serves the same purpose) that has a private constructor, and has public static readonly fields ranging from D0 to D9. This way, users outside of the class, except if they really want to mess up (like using unsafe reflection) cannot pass values that aren't in the 0-9 range. This also reminded me of a job where we had one enum with like 20 values... and then, many, many, many switches to get the many different traits of those enums. Wouldn't it be better to just have classes, with all the traits, and use the classes? Aside from the use of the enum in switch statements, they work the same in most cases, work even easier in cases where we usually had to use helper methods... and if a new trait is added, we have a single place (where the enum values are declared) to fix... with no chance of "forgetting" a case in a switch somewhere else. What do you guys think? Example:

    public struct DecimalDigit
    {
    public static readonly DecimalDigit D0 = new(0);
    public static readonly DecimalDigit D1 = new(1);
    public static readonly DecimalDigit D2 = new(2);
    public static readonly DecimalDigit D3 = new(3);
    public static readonly DecimalDigit D4 = new(4);
    public static readonly DecimalDigit D5 = new(5);
    public static readonly DecimalDigit D6 = new(6);
    public static readonly DecimalDigit D7 = new(7);
    public static readonly DecimalDigit D8 = new(8);
    public static readonly DecimalDigit D9 = new(9);

    private DecimalDigit(byte value)
    {
    _value = value;
    }

    private readonly byte _value;
    public byte ByteValue
    {
    get => _value;
    }
    }

    // vs

    public enum DecimalDigit:
    b

    L Offline
    L Offline
    Lost User
    wrote on last edited by
    #2

    I use .IsDigit more often than defining what one is. Enums make code more readable. And can save storage.

    "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

    1 Reply Last reply
    0
    • P Paulo Zemek

      The title os this message might look like a joke, but it is actually very serious. The other day I wanted to implement my own BigInteger class, and I was using bytes as the backing fields. Yet, I would only use values from 0 to 9 for each digit. Bing AI suggested me to create an enum with values ranging from D0 to D9 (I think their actual values are obvious). Yet, using an enum like that doesn't forbid users from doing things like (DecimalDigit)56 and pass 56 to an enum that was only supposed to support values ranging from 0 to 9. Of course I can validate the values at run-time... but the entire purpose of using an enum was to avoid mistakes like that. So, my solution was to create a class (in fact, a struct, but a class serves the same purpose) that has a private constructor, and has public static readonly fields ranging from D0 to D9. This way, users outside of the class, except if they really want to mess up (like using unsafe reflection) cannot pass values that aren't in the 0-9 range. This also reminded me of a job where we had one enum with like 20 values... and then, many, many, many switches to get the many different traits of those enums. Wouldn't it be better to just have classes, with all the traits, and use the classes? Aside from the use of the enum in switch statements, they work the same in most cases, work even easier in cases where we usually had to use helper methods... and if a new trait is added, we have a single place (where the enum values are declared) to fix... with no chance of "forgetting" a case in a switch somewhere else. What do you guys think? Example:

      public struct DecimalDigit
      {
      public static readonly DecimalDigit D0 = new(0);
      public static readonly DecimalDigit D1 = new(1);
      public static readonly DecimalDigit D2 = new(2);
      public static readonly DecimalDigit D3 = new(3);
      public static readonly DecimalDigit D4 = new(4);
      public static readonly DecimalDigit D5 = new(5);
      public static readonly DecimalDigit D6 = new(6);
      public static readonly DecimalDigit D7 = new(7);
      public static readonly DecimalDigit D8 = new(8);
      public static readonly DecimalDigit D9 = new(9);

      private DecimalDigit(byte value)
      {
      _value = value;
      }

      private readonly byte _value;
      public byte ByteValue
      {
      get => _value;
      }
      }

      // vs

      public enum DecimalDigit:
      b

      Richard DeemingR Offline
      Richard DeemingR Offline
      Richard Deeming
      wrote on last edited by
      #3

      Your DecimalDigit struct should be marked as readonly, since it will never be mutated. You could also replace the backing field with a read-only auto property. You'll probably want to implement IEquatable<T>, and possibly IComparable<T>. And override ToString. At which point, it might be better to use a readonly record struct. And after all that, without having to resort to reflection or unsafe code, you can still create an invalid instance:

      ReadOnlySpan span = new byte[] { 42 };
      DecimalDigit digit = System.Runtime.InteropServices.MemoryMarshal.Read<DecimalDigit>(span);
      Console.WriteLine(digit.ByteValue); // 42


      "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

      "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

      P 3 Replies Last reply
      0
      • Richard DeemingR Richard Deeming

        Your DecimalDigit struct should be marked as readonly, since it will never be mutated. You could also replace the backing field with a read-only auto property. You'll probably want to implement IEquatable<T>, and possibly IComparable<T>. And override ToString. At which point, it might be better to use a readonly record struct. And after all that, without having to resort to reflection or unsafe code, you can still create an invalid instance:

        ReadOnlySpan span = new byte[] { 42 };
        DecimalDigit digit = System.Runtime.InteropServices.MemoryMarshal.Read<DecimalDigit>(span);
        Console.WriteLine(digit.ByteValue); // 42


        "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

        P Offline
        P Offline
        Paulo Zemek
        wrote on last edited by
        #4

        Thanks, I forgot I can make the entire struct final. Yet, I wouldn't get rid of the backing field... I don't like to make a class/struct use property getters instead of using their fields directly... that's really a personal choice. In any case, I will mark the struct as readonly. Thanks for reminding me of that!

        1 Reply Last reply
        0
        • Richard DeemingR Richard Deeming

          Your DecimalDigit struct should be marked as readonly, since it will never be mutated. You could also replace the backing field with a read-only auto property. You'll probably want to implement IEquatable<T>, and possibly IComparable<T>. And override ToString. At which point, it might be better to use a readonly record struct. And after all that, without having to resort to reflection or unsafe code, you can still create an invalid instance:

          ReadOnlySpan span = new byte[] { 42 };
          DecimalDigit digit = System.Runtime.InteropServices.MemoryMarshal.Read<DecimalDigit>(span);
          Console.WriteLine(digit.ByteValue); // 42


          "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

          P Offline
          P Offline
          Paulo Zemek
          wrote on last edited by
          #5

          Question... if I have an array of my struct... the array itself is readonly... yet I can modify the contents of the array... should I mark the class readonly because I never replace one array by another, or not, as there are mutator methods to change the contents of the inner array?

          Richard DeemingR 1 Reply Last reply
          0
          • P Paulo Zemek

            Question... if I have an array of my struct... the array itself is readonly... yet I can modify the contents of the array... should I mark the class readonly because I never replace one array by another, or not, as there are mutator methods to change the contents of the inner array?

            Richard DeemingR Offline
            Richard DeemingR Offline
            Richard Deeming
            wrote on last edited by
            #6

            You can't mark a class as readonly; only a struct. And a struct containing an array of structs is probably a bad idea - the entire array would need to be stored inline as part of the outer structs data. :)


            "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

            "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

            1 Reply Last reply
            0
            • P Paulo Zemek

              The title os this message might look like a joke, but it is actually very serious. The other day I wanted to implement my own BigInteger class, and I was using bytes as the backing fields. Yet, I would only use values from 0 to 9 for each digit. Bing AI suggested me to create an enum with values ranging from D0 to D9 (I think their actual values are obvious). Yet, using an enum like that doesn't forbid users from doing things like (DecimalDigit)56 and pass 56 to an enum that was only supposed to support values ranging from 0 to 9. Of course I can validate the values at run-time... but the entire purpose of using an enum was to avoid mistakes like that. So, my solution was to create a class (in fact, a struct, but a class serves the same purpose) that has a private constructor, and has public static readonly fields ranging from D0 to D9. This way, users outside of the class, except if they really want to mess up (like using unsafe reflection) cannot pass values that aren't in the 0-9 range. This also reminded me of a job where we had one enum with like 20 values... and then, many, many, many switches to get the many different traits of those enums. Wouldn't it be better to just have classes, with all the traits, and use the classes? Aside from the use of the enum in switch statements, they work the same in most cases, work even easier in cases where we usually had to use helper methods... and if a new trait is added, we have a single place (where the enum values are declared) to fix... with no chance of "forgetting" a case in a switch somewhere else. What do you guys think? Example:

              public struct DecimalDigit
              {
              public static readonly DecimalDigit D0 = new(0);
              public static readonly DecimalDigit D1 = new(1);
              public static readonly DecimalDigit D2 = new(2);
              public static readonly DecimalDigit D3 = new(3);
              public static readonly DecimalDigit D4 = new(4);
              public static readonly DecimalDigit D5 = new(5);
              public static readonly DecimalDigit D6 = new(6);
              public static readonly DecimalDigit D7 = new(7);
              public static readonly DecimalDigit D8 = new(8);
              public static readonly DecimalDigit D9 = new(9);

              private DecimalDigit(byte value)
              {
              _value = value;
              }

              private readonly byte _value;
              public byte ByteValue
              {
              get => _value;
              }
              }

              // vs

              public enum DecimalDigit:
              b

              B Offline
              B Offline
              BillWoodruff
              wrote on last edited by
              #7

              Paulo Zemek wrote:

              to implement my own BigInteger class,

              Idea: add information to your post about why you want/need to do this. What functionality does the MS BigInteger Struct provide that you need extended, modified, etc. [^] ? [^]

              «The mind is not a vessel to be filled but a fire to be kindled» Plutarch

              1 Reply Last reply
              0
              • Richard DeemingR Richard Deeming

                Your DecimalDigit struct should be marked as readonly, since it will never be mutated. You could also replace the backing field with a read-only auto property. You'll probably want to implement IEquatable<T>, and possibly IComparable<T>. And override ToString. At which point, it might be better to use a readonly record struct. And after all that, without having to resort to reflection or unsafe code, you can still create an invalid instance:

                ReadOnlySpan span = new byte[] { 42 };
                DecimalDigit digit = System.Runtime.InteropServices.MemoryMarshal.Read<DecimalDigit>(span);
                Console.WriteLine(digit.ByteValue); // 42


                "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

                P Offline
                P Offline
                Paulo Zemek
                wrote on last edited by
                #8

                The real class implements IEquatable and IComparable. I simply didn't want to make the code to huge for this discussion. And, even though Marshal methods aren't marked as unsafe they are, by definition, unsafe. In fact, reflection itself is not called unsafe, although you can create empty instances of classes that do not define default constructors and the like...

                1 Reply Last reply
                0
                • P Paulo Zemek

                  The title os this message might look like a joke, but it is actually very serious. The other day I wanted to implement my own BigInteger class, and I was using bytes as the backing fields. Yet, I would only use values from 0 to 9 for each digit. Bing AI suggested me to create an enum with values ranging from D0 to D9 (I think their actual values are obvious). Yet, using an enum like that doesn't forbid users from doing things like (DecimalDigit)56 and pass 56 to an enum that was only supposed to support values ranging from 0 to 9. Of course I can validate the values at run-time... but the entire purpose of using an enum was to avoid mistakes like that. So, my solution was to create a class (in fact, a struct, but a class serves the same purpose) that has a private constructor, and has public static readonly fields ranging from D0 to D9. This way, users outside of the class, except if they really want to mess up (like using unsafe reflection) cannot pass values that aren't in the 0-9 range. This also reminded me of a job where we had one enum with like 20 values... and then, many, many, many switches to get the many different traits of those enums. Wouldn't it be better to just have classes, with all the traits, and use the classes? Aside from the use of the enum in switch statements, they work the same in most cases, work even easier in cases where we usually had to use helper methods... and if a new trait is added, we have a single place (where the enum values are declared) to fix... with no chance of "forgetting" a case in a switch somewhere else. What do you guys think? Example:

                  public struct DecimalDigit
                  {
                  public static readonly DecimalDigit D0 = new(0);
                  public static readonly DecimalDigit D1 = new(1);
                  public static readonly DecimalDigit D2 = new(2);
                  public static readonly DecimalDigit D3 = new(3);
                  public static readonly DecimalDigit D4 = new(4);
                  public static readonly DecimalDigit D5 = new(5);
                  public static readonly DecimalDigit D6 = new(6);
                  public static readonly DecimalDigit D7 = new(7);
                  public static readonly DecimalDigit D8 = new(8);
                  public static readonly DecimalDigit D9 = new(9);

                  private DecimalDigit(byte value)
                  {
                  _value = value;
                  }

                  private readonly byte _value;
                  public byte ByteValue
                  {
                  get => _value;
                  }
                  }

                  // vs

                  public enum DecimalDigit:
                  b

                  T Offline
                  T Offline
                  trønderen
                  wrote on last edited by
                  #9

                  Skipping the class/struct/readonly/whatever discussions, and commenting on the subject line topic: Enums. My programming childhood (i.e. as a university freshman) was with Pascal, which provided true enums. Not named integers. In the C language class, replacing integer #define with enum is just syntactical sugar. They are integers in disguise. Or not really disguise - it is just a very thin veil. The very idea behind enums is that they are not integers, no matter what C programmers say. January, February and so on are months, not integers! May is May. It takes a C programmer to get into an argument whether the month of May "really" is 4 or 5. Well, the C programmer would never be in doubt. Also, half of May is March, that is obvious, isn't it? (Half of June is also March.) Noooo! Enums are countable (enumerable, if you prefer). So are apples. That doesn't make an apple an integer. If you are really talking about integers, they are integers. Replacing the digit 0 with 'D0', 1 with 'D1' and so on does not, not in any way whatsoever, 'improve legibility'. Quite to the contrary; it hides the fact that you are in fact talking about integer numbers. The reader has to look up the definition of 'D4' to see what it represents: Is it the binary integer 4, or is is the character code for the '4' digit character, or something quite different, such as 'dictionary entry with key 4'? Enums have no place in this context of yours - not even in the C-style 'enums are named integers' style. You are handling true integers. Declare them as true integers, and nothing else. (And: If Pascal had still been alive, you could have had exactly what you are trying to achieve defining new subrange type such as TYPE SingleDigitInteger = 0 ..9; and the compiler + runtime system would catch all attempts to set a variable/field of this type to a value outside its defined range. It would still be a numeric integer. Unfortunately, Pascal, and a lot of great ideas it represented, died several decades ago.)

                  P 1 Reply Last reply
                  0
                  • T trønderen

                    Skipping the class/struct/readonly/whatever discussions, and commenting on the subject line topic: Enums. My programming childhood (i.e. as a university freshman) was with Pascal, which provided true enums. Not named integers. In the C language class, replacing integer #define with enum is just syntactical sugar. They are integers in disguise. Or not really disguise - it is just a very thin veil. The very idea behind enums is that they are not integers, no matter what C programmers say. January, February and so on are months, not integers! May is May. It takes a C programmer to get into an argument whether the month of May "really" is 4 or 5. Well, the C programmer would never be in doubt. Also, half of May is March, that is obvious, isn't it? (Half of June is also March.) Noooo! Enums are countable (enumerable, if you prefer). So are apples. That doesn't make an apple an integer. If you are really talking about integers, they are integers. Replacing the digit 0 with 'D0', 1 with 'D1' and so on does not, not in any way whatsoever, 'improve legibility'. Quite to the contrary; it hides the fact that you are in fact talking about integer numbers. The reader has to look up the definition of 'D4' to see what it represents: Is it the binary integer 4, or is is the character code for the '4' digit character, or something quite different, such as 'dictionary entry with key 4'? Enums have no place in this context of yours - not even in the C-style 'enums are named integers' style. You are handling true integers. Declare them as true integers, and nothing else. (And: If Pascal had still been alive, you could have had exactly what you are trying to achieve defining new subrange type such as TYPE SingleDigitInteger = 0 ..9; and the compiler + runtime system would catch all attempts to set a variable/field of this type to a value outside its defined range. It would still be a numeric integer. Unfortunately, Pascal, and a lot of great ideas it represented, died several decades ago.)

                    P Offline
                    P Offline
                    Paulo Zemek
                    wrote on last edited by
                    #10

                    You got it... the real purpose was to have a Range<0, 9>. That's something I can easily do in C++ templates... and it would avoid enums altogether... and also classes that act as enums.

                    1 Reply Last reply
                    0
                    • P Paulo Zemek

                      The title os this message might look like a joke, but it is actually very serious. The other day I wanted to implement my own BigInteger class, and I was using bytes as the backing fields. Yet, I would only use values from 0 to 9 for each digit. Bing AI suggested me to create an enum with values ranging from D0 to D9 (I think their actual values are obvious). Yet, using an enum like that doesn't forbid users from doing things like (DecimalDigit)56 and pass 56 to an enum that was only supposed to support values ranging from 0 to 9. Of course I can validate the values at run-time... but the entire purpose of using an enum was to avoid mistakes like that. So, my solution was to create a class (in fact, a struct, but a class serves the same purpose) that has a private constructor, and has public static readonly fields ranging from D0 to D9. This way, users outside of the class, except if they really want to mess up (like using unsafe reflection) cannot pass values that aren't in the 0-9 range. This also reminded me of a job where we had one enum with like 20 values... and then, many, many, many switches to get the many different traits of those enums. Wouldn't it be better to just have classes, with all the traits, and use the classes? Aside from the use of the enum in switch statements, they work the same in most cases, work even easier in cases where we usually had to use helper methods... and if a new trait is added, we have a single place (where the enum values are declared) to fix... with no chance of "forgetting" a case in a switch somewhere else. What do you guys think? Example:

                      public struct DecimalDigit
                      {
                      public static readonly DecimalDigit D0 = new(0);
                      public static readonly DecimalDigit D1 = new(1);
                      public static readonly DecimalDigit D2 = new(2);
                      public static readonly DecimalDigit D3 = new(3);
                      public static readonly DecimalDigit D4 = new(4);
                      public static readonly DecimalDigit D5 = new(5);
                      public static readonly DecimalDigit D6 = new(6);
                      public static readonly DecimalDigit D7 = new(7);
                      public static readonly DecimalDigit D8 = new(8);
                      public static readonly DecimalDigit D9 = new(9);

                      private DecimalDigit(byte value)
                      {
                      _value = value;
                      }

                      private readonly byte _value;
                      public byte ByteValue
                      {
                      get => _value;
                      }
                      }

                      // vs

                      public enum DecimalDigit:
                      b

                      L Offline
                      L Offline
                      Lost User
                      wrote on last edited by
                      #11

                      An enum representing the numbers 0 through 9 is worse than useless, actually harmful even, since you will do arithmetic on them.

                      1 Reply Last reply
                      0
                      Reply
                      • Reply as topic
                      Log in to reply
                      • Oldest to Newest
                      • Newest to Oldest
                      • Most Votes


                      • Login

                      • Don't have an account? Register

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • World
                      • Users
                      • Groups