2

I'm trying to make a number of data classes for my C#/XNA game, which would essentially be a wrapper around T[,]:

public interface IGrid<T>
    where T : struct
{
    Point Size { get; }
    T this[int x, int y] { get; set; }
}

For example, I might have a Heightmap object which is essentially an IGrid<float>, or I might have a Level.TileData object which is an IGrid<byte> or IGrid<int>. These could be entirely separate classes with specific helper methods, etc.

I also made a small extension library that operates specifically on any 2-dimensional array:

public static class Transformations
{
    static T[,] Transpose(this T[,] value);
    static T[,] FlipHorizontal(this T[,] value);
    static T[,] FlipVertical(this T[,] value);
    static T[,] RotateClockwise(this T[,] value);
    static T[,] RotateCounterClockwise(this T[,] value);
}

However, when I actually implement IGrid<T>, I encounter a small design issue.

Because the backing array would be private/protected, any client code that actually uses IGrid<T> would be unable to use my extension library.

According to CA1819 and Eric's blog, publicly exposing an array (even via properties!) is a bad idea, as it would be fully mutable. The suggested workaround is to clone the array, then expose it as an immutable type (which means I still don't have an array to use the library on).

Am I overthinking things, or is there an elegant solution here? I really like the idea of using indexers to read/write data on the object, but in some situations I might want to work with the entire array at once.

1 Answer 1

0

There is no guarantee that a client implementing IGrid<T> does actually use an 2D array T[,] for the implementation. So without changing something, you cannot expect to make your extension library work "out of the box" on IGrid<T>. IMHO you have the following options:

  • change (or extend) the interface of your Transformation class to use IGrid<T> as input instead of T[,]. This is probably the cleanest alternative, but I guess if you want to keep and maintain the T[,] variant, too, it will mean some code duplication in your extension library, since T[,] and IGrid<T> don't have a common interface.

  • to avoid the code duplication, you could write a conversion method from IGrid<T> to T[,], but this will have the drawback of making additional copies of the data. You could use this with my first suggestion to create adapter methods to avoid the code-duplication:

     T[,] Transpose(this IGrid<T> value)
     {
          return ConvertIGridToArray(value).Transpose();
     }
    
  • extend IGrid<T> to provide access to underlying T[,], with the drawback of loosing immutability (which forces any client to use a T[,] internally)

Currently, I don't see a way to avoid the code duplication and the additional copying and the loss of immutability all three - pick your choice.

Note that for big arrays, immutability maybe not the optimal design for your transformation operations, without immutability they could be implemented in-place, which is sometimes the better alternative, since it is less memory consuming. Note further that if you want your transformation methods work on something like an Heightmap class, they cannot deliver a new Heightmap object directly, you will have to do some type conversion either. An in-place transformation would not have that problem, it could operate on the internal T[,] of the Heightmap object directly.

1
  • Well, having out laid out my options for me, I think the easiest solution would be to bump my Transformation class over to IGrid<>, and if I really need to pass the data around, I can still iterate it via Size and indexers.
    – Kyle Baran
    Apr 21, 2014 at 20:12

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.