6

I'm creating an ASP.NET MVC application to work with data from an API that has a couple of different endpoints for different geographical regions. Each region has a name, an alphanumeric ID and a host URL. e.g.: Western Europe - WE1 - we.api.com.

There are less than a dozen regions and their values won't change, though regions might get added or removed in the future. I would like to store these in a maintainable way so that I can easily pass them as parameters to functions. e.g.: GetUserData("username", Region.WesternEurope, true).

I have used Enums for this before for APIs where endpoints were defined by a single value but I can't decide how to go about this. One option I've considered is creating an Enum with just the names and then mapping those to objects in a dictionary in a static class, but I'm not a very experienced programmer and I don't know if this would be a good idea.

  • Let's not forget that you can do data annotations on Enums on C#, do you can add "extra data" to each enum. That being said, I think ChrisW's answer is the best for you so far. – Machado Dec 19 '16 at 12:33
12

You could do that like this:

class Region
{
    public string name { get; private set; }
    public string id { get; private set; }
    public string url { get; private set; }

    // private constructor
    Region() {}

    // static members
    public static Region WesternEurope = new Region() {
        name = "Western Europe",
        id = "WE1",
        url = "we.api.com"
    };
}

I think that the private constructor implies that instances can only be created from within the class, e.g. as static members, so you control what instances are created.


Eventually you could load the instances from an external source (data file, database, or registry).

Per Doc Brown's comment, below, you could implement this loading in a static constructor of the Region class (e.g. to load all regions), or implement such loading in a static get accessor as shown below (e.g. to load each region just in time, if and when it's accessed).


In a comment below, Rumen Georgiev suggested the following alternative (which is, IMO, more complicated, and unnecessary, and not thread-safe):

// static members
private static Region _westernEurope;
public static Region WesternEurope 
{
    get 
    {
        if (_westernEurope == null)
        {
            _westernEurope = new Region() 
            {
                name = "Western Europe",
                id = "WE1",
                url = "we.api.com"
            };
        }
        return _westernEurope;
    }
}

An alternative version, which is both thread-safe and still enables lazy initialization of WesternEurope, uses the Lazy<> type:

    private static Lazy<Region> _westernEurope = new Lazy<Region>(
        () => new Region() 
        {
            name = "Western Europe",
            id = "WE1",
            url = "we.api.com"
        });

    public static Region WesternEurope => _westernEurope.Value;

It's questionable whether this is worth doing in this case though as the overhead of creating the instances when Region is statically initialised is likely to be small.

  • Good answer, but it could be even better by mentioning static constructors, where loading of the external instances could be implemented. – Doc Brown Dec 19 '16 at 6:01
  • This is a much more elegant solution than I thought was possible! – Lawyerson Dec 19 '16 at 7:15
  • This way every time any of the static properties is accessed new instance of Region will be created. It's better to have a single instance for each property (with lazy initialization) to reduce memory consumption and GC overhead. Also, think about comparing objects by reference (e.g. Region.WesternEurope != Region.WesternEurope) – Rumen Georgiev Dec 19 '16 at 8:17
  • I have changed the code in the answer to illustrate better solution. – Rumen Georgiev Dec 19 '16 at 8:22
  • 1
    @Bobson I wondered that. Maybe yes, because it's intended as a replacement for Enum, and Enum is a value type; and a class instead can be null whereas a struct instance (is a value type and) cannot be null. However you can't prevent the default constructor on a struct, so anyone could call new Region() to create an empty Region instance. – ChrisW Dec 20 '16 at 11:46
0

The answer all depends on your requirements over time.

If your only requirement is clean code that's easy to understand, I would suggest a straight forward class like this:

internal static class regionStrings {

  static public string Ireland { get; private set; }

  static regionStrings()
  {
    Ireland = "eu-west-1";
  }
}
  • This would mean any method that took a region would how have to take a string – Richard Tingle Dec 19 '16 at 7:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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