views:

456

answers:

5

I have what amounts to a multi-dimensional array.

int[][][] MyValues;

What I want is to access the indexes via a strongly typed equivelent, such as an enumeration. I'm aware that you can get the enumeration values from the Enum type, but it's a bit long winded for my tastes.

I'd rather have a way to Strongly type the indexes.

For example:

int CarNumber = MyValues[Racetrack.Daytona][Race.Daytona500][Driver.JGordon];

This would, by virtue of it being enum-like, prevent any out of bounds exceptions from being thrown, plus it gives all the indexes a nice human readable meaning.

I've implemented this using a dictionary approach, but it seems kind of heavy handed:

Dictionary<Racetrack,Dictionary<Race,<Dictionary<Driver,int>>> =
    new Dictionary<Racetrack,Dictionary<Race,<Dictionary<Driver,int>>>();

which I can then access via enums, but I don't really like this approach. It seems "ugly".

I'm looking for some alternate methods to represent what is essentially a multi-dimensional array while using human readable indexers, while maintaining type safety (can't accidently use a Driver for a Race, for instance, so simply using consts is not a good approach).

Any suggestions?

This will be a compile time array (example above is not real, just an illustration) so I don't have to worry about inserts or deletes or other manipulations of the array. It will stay immutable, both in values, size and layout.

Using a static class with const values is not a good approach either, since it doesn't enforce that only the set of values defined can be passed as indexers.

A: 

Obvious question.. Will List<T> not work for you?

Ian P
List<T> does not allow you to use an enum as an indexer without casts or writing a lot of accessors to get the actual value, and is not type safe. So no.
Mystere Man
I thought List<T> was type safe? I guess I'm confused.
Ian P
It's safe for it's value, not the indexer.
Mystere Man
A: 

Are the enums reasonably small, with values 0...n? If so, you could use a multi-dimensional array but expose an indexer. Note that the code below uses a rectangular array rather than a jagged array, but you could fairly easily adapt it.

// In a static class somewhere. Just a convenience method to check
// whether a value is defined or not. See comment in indexer.
public static void CheckDefined<T>(this T value, String name)
    where T : struct
{
    if (!Enum.IsDefined(typeof(T), value))
    {
        throw new ArgumentOutOfRangeException(name);
    }
}


// Somewhere else...
private static int GetLength<T>() where T : struct
{
    return Enum.GetValues(typeof(T)).Length;
}

private int[,,] array = new int[GetLength<Racetrack>(), 
                                GetLength<Race>(),
                                GetLength<Driver>()];

public int this Car[Racetrack racetrack, Race race, Driver driver]
{
  get
  {
    // If you don't care about just getting an
    // IndexOutOfRangeException, you could skip these three lines.
    racetrack.CheckDefined("racetrack");
    race.CheckDefined("race");
    driver.CheckDefined("driver");
    return array[(int) racetrack, (int) race, (int) driver);
  }
}
Jon Skeet
can you explain what the "public int this Car" bit is about? what is the "this" for?
Mystere Man
It's declaring an indexer. See http://msdn.microsoft.com/en-us/library/6x16t2tx.aspx
Jon Skeet
Huh, learn something new every day. Thanks.
Mystere Man
+2  A: 

How about using a triple <Racetrack,Race,Driver> as the key (define your own class) in the Dictionary?

If you really need to use an array, I don't think you can do better than wrapping it in a custom class that allows access only using Racetrack, Race, Driver enums.

Zach Scrivena
+3  A: 

It sounds to me that you want to use indexers rather than an array. Assuming the following enums (Formula 1 based!):

public enum Track
{
    Spielberg,
    Adelaide,
    Casablanca,
    Liverpool,
    Melbourne,
    Berlin,
    Sakhir,
}

public enum Constructor
{
    BMW,
    Ferrari,
    McLaren,
    Toyota,
    Williams
}

public enum Driver
{
    Hamilton,
    Kovalainen,
    Raikkonen,
    Nakajima,
    Glock
}

the basic structure is as follows:

public class Race
{
    int Year { get; set; }
    Track Track { get; set; }
    Driver[] Placings { get; set; }
    public int this[Driver driver] { } // placing by driver
}

public class Results
{
    YearResults this[int index] { }
    DriverResults this[Driver index] { }
    TrackResults this[Track index] { }
    ConstructorResults this[Constructor index] { }
}

public class YearResults
{
    YearDriverResults this[Driver index] { }
}

This of course is a partial implementation but you can do some pretty cool things with indexers this way. Like you can access your information with any combination of values in any order (assuming you set up all the intermediate classes).

Its wordier than a multidimensional array or a tuple-keyed Dictionary but I think will give you far more elegant code.

cletus
This is a good point. Sometimes we get caught up in thinking about the problem as as Hammer solution that we forget it might not be a nail. Maybe it's a screw or glue.
Mystere Man
Well, no, I don't need Racetrack or Race objects with all sorts of properties or methods. It's just a collection of numbers with constraints. But like I said, I hadn't really though about defining them as thir own classes.
Mystere Man
A: 

I don't think the dictionary approach is bad, but it isn't elegant. If you created an alias for your dictionary of dictionary things would look better:

using RaceSetup = Dictionary<Racetrack,Dictionary<Race,<Dictionary<Driver,int>>>;

Or you could create a class that derived from the dictionary:

class RaceSetup : Dictionary<Racetrack,Dictionary<Race,<Dictionary<Driver,int>>>
{}
Rune Grimstad
The larger problem with the dictionary approach is that it creates mess initialization code. Not a huge problem, but still ugly.
Mystere Man