How to create methods on an Enum?

Asked

Viewed 2,113 times

8

I have an application in Java and am porting it to C#. I have a question regarding enum that seems to work differently from Java. After asking a question here on the site (Enumerations may contain abstract methods?), whenever possible I choose to centralize methods and properties in a enum. Well, in Java my code is like this:

enum BBcodes {

   BOLD("b"),
   ITALIC("i"),
   //...
   UNDERLINE("u");

   private final String code;
   public BBcodes(String code){
      this.code = code;
   }

   public string wrap(String contents){
      return MessageFormat.format("[{0}]{1}[/{0}]", this.code, contents);
   }
}

In which I can call him that:

String bbcode = BBcodes.BOLD.wrap("StackOverflow"); //[b]StackOverflow[/b]

Vi in that question I can’t create a enum which holds values of the kind string. I don’t know if this is the best way to solve it, but I created a class for it:

public class BBcodes
{
   public static readonly string BOLD = "b";
   public static readonly string ITALIC = "i";
   //...

   public static string Wrap(string code, string contents)
   {
      return string.Format("[{0}]{1}[/{0}]", code, contents);
    }
}

In which I call it:

string bbcode = BBcodes.Wrap(BBcodes.BOLD, "StackOverflow"); //[b]StackOverflow[/b]

It is not a problem to do so, having to pass the respective value of Bbcode as argument to the method Wrap. It’s something you get used to. :)

But if possible, I’d like to do something as close as possible to Java, creating everything (the values and methods) in itself enum. How could I do this in C#? Is it possible?

  • 1

    I love this kind of question, it will certainly be useful for some user in the future.

2 answers

10


In C# the types of enumerations are treated differently from the other types. It’s not a class like it is in Java. In fact you found one of the few things that clearly C# is worse than Java and it is not possible to put methods inside the enumerations. It is worth noting that an enumeration in C# is a type by value and is usually equivalent to an integer (can specify the type) and has even another problem that is the cast implicit of these types, but this is another matter.

The closest solution that is used and some even think it can be more elegant in some scenarios is the use of extension methods (see that it has its advantages and disadvantages). In Jon Skeet’s answer linked above in original author’s question here has similar solution:

public enum BBCodes {
    [Description("b")]
    Bold = 1,
    [Description("i")]
    Italic = 2
}

public static class BBCodesExt {
    public static string Wrap(this BBCodes code, string contents) => $"[{code.ToStringDescription()}]{contents}[/{code.ToStringDescription()}]";
    public static string ToStringDescription(this BBCodes code) {
        var attributes = (DescriptionAttribute[])code.GetType().GetField(code.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
        return attributes.Length > 0 ? attributes[0].Description : "";
    }
}

I gave it an upgrade to stay in c-style#.

Call it that:

string bbcode = BBCodes.Bold.Wrap("StackOverflow");

Behold working in the ideone. And in the .NET Fiddle. Also put on the Github for future reference.

Documentation.

But if the representation string hit the member name in all cases, as shown in the 2 members of the example, nor need it. The enum has a method ToString() for this.

But if what you want is the class reference semantics (I doubt you need it in most cases), then it is better to simulate the enumeration, more or less as you did. Just try to leave more idiomatic for C#, as shown and make the class be sealed (equivalent to final). Or you can do it a little differently:

public sealed class BBCodes {
    public static readonly string Bold = "b";
    public static readonly string Italic = "i";
    //...
    
    private string code;
    
    public BBCodes(string code) => this.code = code;
    
    public string Wrap(string contents) => $"[{code}]{contents}[/{code}]";
}

You can create a property to give read/write access to the instance’s member value if necessary.

Behold working in the ideone. And in the .NET Fiddle. Also put on the Github for future reference.

6

Enumerations (enum) in C# accept some types (byte, sbyte, short, ushort, int, uint, long or ulong), but string is not among them. It is also not possible to create methods within an enumeration, but it is still possible to arrive at the desired result without much suffering.

The output is the creation of a extension method for the desired enumeration type. Next I will build a solution based on the code of your question.

Create the enumeration:

public enum BBCode
{
    [Description("b")]
    Bold,

    [Description("i")]
    Italic,

    [Description("u")]
    Underline
}

In the above enumeration statement, I memorized the constants with the attribute DescriptionAttribute whose purpose is to assign a description in string to any element of an application.

Create an extension method for the desired enumeration type:

public static class MyEnumExtensions
{
    public static string Wrap(this BBCode val, string content)
    {
        var attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);

        if (attributes.Length <= 0)
            throw new Exception($"Not decorated with `{nameof(DescriptionAttribute)}`.");

        var code = attributes[0].Description;
        return $"[{code}]{content}[/{code}]";
    }
}

The method of extension Wrap, defined above, only accessible for enumerations of type BBCode (hence the this BBCode) and its function is to obtain the value contained in the attribute DescriptionAttribute, which was used to decorate a referenced constant, and return a string formatted along with the parameter value content.

To use this extension method just make a simple call to it on top of an enumeration instance BBCode.

BBCode.Bold.Wrap("Foo"); // Retorna: "[b]Foo[/b]"

Note that the class MyEnumExtensions needs to be in an accessible namespace so that the method Wrap may be called.

This way you have a behavior very similar to the original Java code presented in the question.

Another solution would be to use a class that behaves as an enumeration, as presented in its own question in its attempt to implement in C#. But anyway you are free to choose the way you like best for each scenario, as both have their advantages and disadvantages.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.