0

I'm porting a game written in Pascal (compiled in 16 bit) to C# (so it will run on machines newer than XP). From what I've gathered, in Pascal, it's possible to type define in the type section of a unit/program through syntax like this:

type
    BaseArrayPtr = ^BaseArray;
    BaseArray = array [1 .. 5, 1 .. 5] of Integer;

    SubArray = array [0 .. 3] of BaseArray;

I also gathered that, unfortunately, it is impossible to type define in C#. However, I'm trying for a workaround. So far, this is what I have:

BoundedArray.cs:

using System;
using System.Collections;

namespace test
{
    abstract class BoundedArray<T>
    {
        public BoundedArray()
        {
            m_data = null;
        }

        public T this[params int[] index]
        {
            get
            {
                if (index.Length != m_data.Rank)
                    throw new IndexOutOfRangeException();

                return (T) m_data.GetValue(index);
            }
            set
            {
                if (index.Length != m_data.Rank)
                    throw new IndexOutOfRangeException();

                m_data.SetValue(value, index);
            }
        }

        protected void SetAttributes(int[] lowerBounds, int[] lengths)
        {
            if (lengths.Length != lowerBounds.Length)
                throw new ArgumentException();

            m_lowerBounds = lowerBounds;
            m_lengths = lengths;

            m_data = Array.CreateInstance(typeof(T), m_lengths, m_lowerBounds);
            m_data.Initialize(); // Should (but doesn't) initialize every element in m_data
        }

        Array m_data;
        int[] m_lengths;
        int[] m_lowerBounds;
    }
}

test.cs:

using System;

namespace test
{
    class Program
    {
        public static int[] ints(params int[] values)
        {
            return values;
        }

        class BaseArray : BoundedArray<int>
        {
            public BaseArray()
            {
                SetAttributes(ints(2, 2), ints(1, 2));
            }
        }

        class SubArray : BoundedArray<BaseArray>
        {
            public SubArray()
            {
                SetAttributes(ints(4), ints(2));
            }
        }

        static void Main(string[] args)
        {
            SubArray subArray = new SubArray();

            Console.Read();
        }
    }
}

I've checked baseArray, and the default values of m_data are zeroes, since they are ints. However, in subArray, the default values of m_data are null - the BaseArray instances inside the array in subArray haven't been initialized for some reason. How do I get the default constructor to run?

EDIT: The real question at the moment is why doesn't m_data.Initialize(); in the SetAttributes method initialize all elements in m_data? The documentation on MSDN seems to indicate that it should...

EDIT: So I believe that problem is that System.Array.Initialize only works on value-types. Since classes are references types in C#, System.Array.Initialize doesn't do anything. So I have to find a way to initialize a reference-type array of variable dimensions, lengths, and lower bounds.

4

3 回答 3

0

You have a singe-dimensional array SubArray which holds BaseArray objects which are two-dimensional arrays of intergers. In place of Pascal type, you can define a custom C# class which would override the indexer operator to give you exactly the same behavior.

EDITED So, in Pascal you have this:

type
    BaseArrayPtr = ^BaseArray;
    BaseArray = array [1 .. 5, 1 .. 5] of Integer;

    SubArray = array [0 .. 3] of BaseArray;

Maybe I misunderstood the question, but is the below not exactly the same, in C#?

public class BaseArray
{
    int[,] m_array = new int[5, 5];

    static void CheckBounds(int x, int y)
    {
        if (x < 1 || x > 5 || y < 1 || y > 5)
            throw new IndexOutOfRangeException();
    }

    public int this[int x, int y]
    {
        get 
        {
            CheckBounds(x, y);
            return m_array[x-1, y-1]; 
        }
        set 
        {
            CheckBounds(x, y);
            m_array[x-1, y-1] = value; 
        }
    }
}

public class SubArray
{
    BaseArray[] m_array = new BaseArray[4];

    public BaseArray this[int x]
    {
        get { return m_array[x]; }
        set { m_array[x] = value; }
    }
}
于 2013-10-27T05:56:29.027 回答
0

Well I have done some changes that when you want to create an instance of a SubArray you should pass BaseArray as source of data to be initialize.

As i understood you want to set the values from BaseArray to SubArray.

Here is my work:

BoundedArray.cs

 abstract class BoundedArray<T>
{
    public BoundedArray()
    {
        m_data = null;
    }

    public int[] Lengths;
    public int[] LowerBounds;

    public void CreateInstance()
    {
        if (Lengths.Length != LowerBounds.Length)
            throw new Exception("Incorrect number of lengths or lower bounds.");

        m_data = Array.CreateInstance(typeof(T), Lengths, LowerBounds);
    }
    public void CreateInstance(Array source)
    {
        if (Lengths.Length != LowerBounds.Length)
            throw new Exception("Incorrect number of lengths or lower bounds.");

        m_data = Array.CreateInstance(typeof(T), Lengths, LowerBounds);

        /************************************************************************/
        /*    Now you should find the value of BaseArray and set it to m_data                                                                     */
        /************************************************************************/


    }
    public T this[params int[] index]
    {
        get
        {
            if (index.Length != m_data.Rank)
                throw new IndexOutOfRangeException();

            return (T)m_data.GetValue(index);
        }
        set
        {
            if (index.Length != m_data.Rank)
                throw new IndexOutOfRangeException();

            m_data.SetValue(value, index);
        }
    }
    public Array GetData()
    {
        return m_data;
    }
    Array m_data;
}

Test.cs

class Program
{
    public static int[] ints(params int[] values)
    {
        return values;
    }

    class BaseArray : BoundedArray<int>
    {
        public BaseArray()
        {
            Lengths = ints(1, 2);
            LowerBounds = ints(2, 2);

            CreateInstance();
        }
    }

    class SubArray : BoundedArray<BaseArray>
    {
        public SubArray(BaseArray arr)
        {
            Lengths = ints(2);
            LowerBounds = ints(4);

            CreateInstance(arr.GetData());
        }
    }

    static void Main(string[] args)
    {
        BaseArray baseArray = new BaseArray();
        SubArray subArray = new SubArray(baseArray);

        Console.Read();
    }
}
于 2013-10-27T06:20:27.107 回答
0

I've already answered my own question once, but I came up with a much better implementation of my answer.

Here's what this solution consists of:

  1. SetAttributes must be run once, in the default constructor of a class based off of BoundedArray
  2. During SetAttributes, I gather a jagged, two-dimensional array of all of the indices in the current BoundedArray subclass
  3. I create instances of the template type by calling Activator.CreateInstance and assigning one per index

Other things to note:

  • Set attributes now takes a variable length array of int[]s instead of two int[]s. Previously, it was taking the lowerbounds and the lengths, but I realized it makes more sense to just take int[]s which are lower and upper bounds, and then use a LINQ query to check that there aren't any which aren't pairs
  • I created a static class called IntArray, which is used extensively by SetAttributes and in test.cs
  • I tried to throw as many useful errors as possible, since I'll probably end up using this code a lot
  • I have a feeling that Combinations(int[][] list1, int[] list2) is probably where the most improvement on my solution could be found. I'm open to suggestions on how to improve all of my code

So, without further ado, my complete solution:

BoundedArray.cs

using System;
using System.Linq;
using System.Collections.Generic;

namespace test
{
    static class IntArray
    {
        public static int[] FromValues(params int[] values)
        {
            return values;
        }

        public static int[] Sequence(int from, int length)
        {
            if (from < 0 || length < 1)
                throw new ArgumentException();

            return Enumerable.Range(from, length).ToArray();
        }

        public static int[][] Combinations(int[] list1, int[] list2)
        {
            return Combinations(list1.Select(i => new int[] { i }).ToArray(), list2);
        }

        public static int[][] Combinations(int[][] list1, int[] list2)
        {
            List<List<int>> result = new List<List<int>>();

            for (int i = 0; i < list1.Length; i++)
            {
                for (int j = 0; j < list2.Length; j++)
                    result.Add(((int[]) list1.GetValue(i)).Concat(new int[] { list2[j] }).ToList());
            }

            return result.Select(i => i.ToArray()).ToArray();
        }
    }

    abstract class BoundedArray<T>
    {
        public BoundedArray()
        {
            m_data = null;
        }

        public Array Value
        {
            get { return m_data; }
        }

        public T this[params int[] index]
        {
            get
            {
                if (index.Length != m_data.Rank)
                    throw new IndexOutOfRangeException();

                return (T) m_data.GetValue(index);
            }
            set
            {
                if (index.Length != m_data.Rank)
                    throw new IndexOutOfRangeException();

                m_data.SetValue(value, index);
            }
        }

        protected void SetAttributes(params int[][] values)
        {
            // Make sure all of the values are pairs
            if (values.Where(i => i.Length != 2).ToArray().Length > 0)
                throw new ArgumentException("Input arrays must be of length 2.");

            int[] lowerBounds = values.Select(i => i[0]).ToArray();
            int[] lengths = values.Select(i => i[1] - i[0] + 1).ToArray();

            m_data = Array.CreateInstance(typeof(T), lengths, lowerBounds);

            int[][] indices = (lowerBounds.Length != 1) ?
                IntArray.Combinations(IntArray.Sequence(lowerBounds[0], lengths[0]), IntArray.Sequence(lowerBounds[1], lengths[1]))
                : IntArray.Sequence(lowerBounds[0], lengths[0]).Select(i => new int[] { i }).ToArray();

            for (int i = 2; i < lowerBounds.Length; i++)
                indices = IntArray.Combinations(indices, IntArray.Sequence(lowerBounds[i], lengths[i]));

            for (int i = 0; i < indices.Length; i++)
                m_data.SetValue(Activator.CreateInstance(typeof(T)), indices[i]);
        }

        Array m_data;
    }
}

test.cs

using System;

namespace test
{
    class Program
    {
        // *** Examples of what you can do with BoundedArray ***

        // Multi-dimensional, bounded base array
        class BaseArray : BoundedArray<int>
        {
            public BaseArray()
            {
                SetAttributes(IntArray.FromValues(2, 3), IntArray.FromValues(2, 4));
            }
        }

        // One-dimensional, bounded subclass array
        class SubArray : BoundedArray<BaseArray>
        {
            public SubArray()
            {
                SetAttributes(IntArray.FromValues(4, 6));
            }
        }

        static void Main(string[] args)
        {
            // Initializations used for testing purposes
            BaseArray baseArray = new BaseArray();
            SubArray subArray = new SubArray();

            // Example of assignment
            baseArray[3, 4] = 3;
            subArray[4][2, 3] = 4;

            subArray[4][2] = 3; // Weakness: compiles, but causes IndexOutOfRangeException

            Console.Read();
        }
    }
}

Thougts?

于 2013-10-28T01:58:16.303 回答