4
\$\begingroup\$

I have a need to represent a finite set of discrete string values:

  • When these strings are used in code, they must belong to the valid set, or serious errors (or worse) can occur.
  • As "magic strings" are a terrible code smell, I want to encapsulate the strings within a non-string type, so that I can require the use of my type instead of just a string and thus enforce at compile-time that a value is given that I know will work.
  • No constants; if I change the string behind the scenes I should not have to recompile all usages of my type.
  • The identifiers representing the valid values should not have to have any formulaic similarity to the actual string (no Enum.ToString or similar).
  • Lastly, while I want to enforce the use of my type so I can restrict values to the ones I know are valid, the type must be easily convertible to a string so that the piece of code that really needs the string doesn't have to undergo a complicated or esoteric conversion.

What I came up with is a variation of a "multiton"; a class that cannot be instantiated in code, but has a finite number of static instances each representing a valid value of the type. They are implicitly convertible to strings and so the static members just drop in wherever a string is needed.

//abstract base; derive to create your specific set of values
public abstract class StringEnum
{
    protected StringEnum(string name)
    {
        this.name = name;
    }

    private readonly string name;

    public static implicit operator string(StringEnum sp)
    {
        return sp.name;
    }
}

//one possible derivation
public class CommandNames:StringEnum
{
    //unfortunately each derivation has to call back to the base constructor;
    //it would be nice not to have to do this, but meh
    protected CommandNames(string commName) : base(commName) { }

    //obviously the instance names don't have to match the real string value exactly
    public static readonly CommandNames DoSomething = new CommandNames("system638_Command_doSomething");
}

//example usage
public void ExecuteCommand(CommandNames name)
{
    //assume ExecuteByName expects a string
    CommandExecutor.ExecuteByName(name); //the StringEnum just drops in
}

...

//Only one of the static instances, with string values I control,
//can be passed into this function, thus safely encapsulating the inner
//objects' use of "magic strings".
ExecuteCommand(CommandNames.DoSomething);

Thoughts?

\$\endgroup\$
  • \$\begingroup\$ Doesn't this break your requirement for “no constants”? \$\endgroup\$ – svick Mar 15 '12 at 18:53
  • \$\begingroup\$ @KeithS, any feedback on the suggested answers or comments? \$\endgroup\$ – Randy Mar 17 '12 at 16:20
  • \$\begingroup\$ @svick - There is a big difference between readonly and const in .NET. A readonly variable is basically a normal field variable with a few extra compile-time and runtime rules. Objects/assemblies that need it get it the same way they would a mutable field. A const is a value written into the manifest of any assembly that uses it, and all usages of the constant refer to their own assembly's manifest. So, constants require a recompile of any code that uses it, not just the one that declares it in source. That's why I want to avoid constants. \$\endgroup\$ – KeithS Oct 3 '12 at 16:09
2
\$\begingroup\$

A few things don't add up for me regarding your usage. You want compile time absolution, but dynamic values while stating the critical nature of its use.

One option: You can just take care of this by having objects with inheritance structures and loading your "dynamic" values from an application configuration or db. Just have 3 layers of inheritance 1) StringEnum 2) Enum Declaration (like System638) and 3) actual values. Just wire up the inheritance. Mostly likely though, interfaces would be more appropriate for restrictions at the 1 & 2 layers.

Recommended (assuming the distributed smell of the use case): Build your functional class and bind it to a single DB table that ensures the integrity you are looking for. Just cache the values in memory for performance needs.

Recommended (given the critical nature of its use) Find a different way to design the system so that you can fully embed what you need in code. For example, a web service. Even if you need a local presence you can utilize a web service to control the pieces you need.

\$\endgroup\$
  • \$\begingroup\$ Well, just for more specific reference, this is basically a wrapper for SQL stored procedures. You can pass the "command" which represents the SP into the Repository, and the repository can then drop it into the ADO.NET connection. That's the basis of the code smell; having to specify the procedure to use as a string is a necessary evil I'm trying to mitigate by forcing the use of a "safe" list of procedures. \$\endgroup\$ – KeithS Mar 17 '12 at 17:29
  • \$\begingroup\$ Thank you for the clarification and feedback KeithS. What about utilizing an EF entity model and specifying the stored procs straight on it? \$\endgroup\$ – Randy Mar 17 '12 at 19:38
  • \$\begingroup\$ Currently I'm using NHibernate, abstracted behind a basic Repository pattern. While NH does allow for the specification of SPs for create/update/delete, I need a little more control than that for my particular use case. My ideal scenario would be to not use SPs at all, but the data model precedes my development work by some years. \$\endgroup\$ – KeithS Mar 26 '12 at 14:33
0
\$\begingroup\$

KISS! You are over-complicating things. Keep strings as strings. Keep the strings as readonly, not const. Generate the code at build time, possibly using a custom build task, which itself can be compiled in the same build. Here is a quick Python script to give you an idea:

# Python script to generate your code
print('public static class CommandNames')
print('{')
for name, value in open('commands.txt').readlines():
    print('    public static readonly string {0} = "{1}";'.format(name, value))
print('}')

You do not have to stay in Python as I mentioned previously. Powershell or a custom build task will do.

EDIT: There is a more idiomatic way to generate code with Visual Studio -

http://www.hanselman.com/blog/T4TextTemplateTransformationToolkitCodeGenerationBestKeptVisualStudioSecret.aspx

http://msdn.microsoft.com/en-us/library/ee847423.aspx

\$\endgroup\$
  • \$\begingroup\$ -1: what about writing code ExecuteCommand(CommandNames.DoSomething);, does this still allow IDE auto-completion, or is one supposed to "just know" which values to use and ignore both autocomplete and JIT error-detection? Somehow I doubt that the IDE (let's say Visual Studio) would be able to adapt to a dynamic approach such as this; or would it? \$\endgroup\$ – ANeves Mar 15 '12 at 10:10
  • 1
    \$\begingroup\$ @ANeves, auto-generated code is used all the time in large software projects. Visual Studio generates code for accessing embedded Resources, project settings, etc. The question is whether this auto-complete will be fully available before you try to compile it. Well, let's not put a horse before a carriage - add the class in, build once, and then this will become a fully fledged class that you can use auto-complete on. I am not sure what you mean by a JIT-error detection. This class will act as of correctly written by a human. With the right project dependencies it will compile in 1st pass. \$\endgroup\$ – Leonid Mar 15 '12 at 13:48
  • \$\begingroup\$ UGH, what was I thinking?!? Somehow I oversought that the class would of course be included in the project... my apologies; everything works as expected. What I meant by "JIT error detection" is that the IDE shows code errors as one types. \$\endgroup\$ – ANeves Mar 16 '12 at 9:56
  • \$\begingroup\$ You are missing a close parenthesis on the second line, and you have the wrong curly bracket on the last line. It's completely unimportant, but if you corrected these I could remove my stupid -1. :/ \$\endgroup\$ – ANeves Mar 16 '12 at 9:59

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.