C# 如何在接口或基类中定义具有固定大小的数组

C# 如何在接口或基类中定义具有固定大小的数组,c#,arrays,C#,Arrays,我正在做一个宾果游戏,在宾果游戏中,一张牌是一个5x5的数字网格(减去“空闲”的中心空间)。我正在寻找一种以强类型方式表示5x5网格的方法。它可能是一个由int、bools、某些类等组成的5x5网格。我最初的想法是使用一个二维数组,但问题是我不允许指定大小,因此在传递对象时,其他类无法知道它应该有5行5列 例如,我可以创建接口: public interface ICard { int[,] cells { get; } } 但这里没有明确说明整数数组有5行5列的位置。此外,为了定义要

我正在做一个宾果游戏,在宾果游戏中,一张牌是一个5x5的数字网格(减去“空闲”的中心空间)。我正在寻找一种以强类型方式表示5x5网格的方法。它可能是一个由int、bools、某些类等组成的5x5网格。我最初的想法是使用一个二维数组,但问题是我不允许指定大小,因此在传递对象时,其他类无法知道它应该有5行5列

例如,我可以创建接口:

public interface ICard
{
    int[,] cells { get; }
}
但这里没有明确说明整数数组有5行5列的位置。此外,为了定义要匹配的模式,我可能会使用5x5布尔网格,因此我希望它看起来更像这样:

public interface ICard<T>
{
    T[,] cells { get; }
}
公共接口ICard
{
T[,]细胞{get;}
}
因此,我如何更改它以返回一个强类型的Card对象,该对象强制执行只有5行和5列的规则,并使其变得明显。我想我的问题的答案是创建一个新的Card类,但我不确定如何以一种优雅的方式来执行它,并使它显然是一个5x5网格

任何想法都很感激。谢谢

导出答案 首先,感谢所有快速提供答案的人。基于所有的答案,我提出了一种混合的方法。下面是我最后做的:

创建了新的通用矩阵接口和类:

public interface IMatrix<T>
{
    int NumberOfColumns { get; }
    int NumberOfRows { get; }
    T GetCell(int column, int row);
    void SetCell(int column, int row, T value);
}

public class Matrix<T> : IMatrix<T>
{
    protected readonly T[,] Cells;

    public int NumberOfColumns { get; }
    public int NumberOfRows { get; }

    public Matrix(int numberOfColumns, int numberOfRows)
    {
        NumberOfColumns = numberOfColumns;
        NumberOfRows = numberOfRows;
        Cells = new T[numberOfColumns, numberOfRows];
    }

    public T GetCell(int column, int row)
    {
        ThrowExceptionIfIndexesAreOutOfRange(column, row);
        return Cells[column, row];
    }

    public void SetCell(int column, int row, T value)
    {
        ThrowExceptionIfIndexesAreOutOfRange(column, row);
        Cells[column, row] = value;
    }

    private void ThrowExceptionIfIndexesAreOutOfRange(int column, int row)
    {
        if (column < 0 || column >= NumberOfColumns || row < 0 || row >= NumberOfRows)
        {
            throw new ArgumentException($"The given column index '{column}' or row index '{row}' is outside of the expected range. Max column range is '{NumberOfColumns}' and max row range is '{NumberOfRows}'.");
        }
    }
}
公共接口IMatrix
{
int NumberOfColumns{get;}
int NumberOfRows{get;}
T GetCell(int列,int行);
void SetCell(int列、int行、T值);
}
公共类矩阵:IMatrix
{
受保护的只读T[,]细胞;
公共int NumberOfColumns{get;}
public int NumberOfRows{get;}
公共矩阵(int numberOfColumns,int numberOfRows)
{
NumberOfColumns=NumberOfColumns;
NumberOfRows=NumberOfRows;
单元格=新的T[numberOfColumns,numberOfRows];
}
公共T GetCell(int列,int行)
{
ThroweExceptionIfIndex超出范围(列、行);
返回单元格[列,行];
}
公共void SetCell(int列、int行、T值)
{
ThroweExceptionIfIndex超出范围(列、行);
单元格[列,行]=值;
}
私有void throweExceptioniIndexeAroutofRange(int列,int行)
{
如果(列<0 | |列>=NumberOfColumns | |行<0 | |行>=NumberOfRows)
{
抛出新ArgumentException($“给定的列索引“{column}”或行索引“{row}”超出预期范围。最大列范围为“{NumberOfColumns}”,最大行范围为“{NumberOfRows}”);
}
}
}
然后,我的实际卡片对象接受构造函数中的IMatrix,并验证它是否具有预期的行数和列数:

public interface ICard
{
    int NumberOfColumns { get; }
    int NumberOfRows { get; }
    ICell GetCellValue(int column, int row);

    bool Mark(int number);
    bool Unmark(int number);
}

public class Card : ICard
{
    // A standard Bingo card has 5 columns and 5 rows.
    private const int _numberOfColumns = 5;
    private const int _numberOfRows = 5;

    private IMatrix<ICell> Cells { get; } = new Matrix<ICell>(_numberOfColumns, _numberOfRows);

    public Card(IMatrix<ICell> numbers)
    {
        if (numbers.NumberOfColumns != NumberOfColumns || numbers.NumberOfRows != NumberOfRows)
            throw new ArgumentException($"A {numbers.NumberOfColumns}x{numbers.NumberOfRows} matrix of numbers was provided for the Card with ID '{id}' instead of the expected {NumberOfColumns}x{NumberOfRows} matrix of numbers.", nameof(provider));

        for (int column = 0; column < NumberOfColumns; column++)
        {
            for (int row = 0; row < NumberOfRows; row++)
            {
                var number = numbers.GetCell(column, row);
                var value = (column == 2 && row == 2) ? new Cell(-1, true) : new Cell(number);
                Cells.SetCell(column, row, value);
            }
        }
    }

    public int NumberOfColumns => _numberOfColumns;
    public int NumberOfRows => _numberOfRows;

    public ICell GetCellValue(int column, int row) => Cells.GetCell(column, row).Clone();

    public bool Mark(int number)
    {
        var cell = GetCell(number);
        if (cell != null)
        { 
            cell.Called = true;
        }
        return cell != null;
    }

    public bool Unmark(int number)
    {
        var cell = GetCell(number);
        if (cell != null)
        {
            cell.Called = false;
        }
        return cell != null;
    }

    ...
}
公共接口ICard
{
int NumberOfColumns{get;}
int NumberOfRows{get;}
ICell GetCellValue(int列,int行);
布尔标记(整数);
布尔取消标记(整数);
}
公共类卡:ICard
{
//标准宾果卡有5列5行。
private const int_numberOfColumns=5;
私有常量int_numberOfRows=5;
私有IMatrix单元格{get;}=新矩阵(_numberOfColumns,_numberOfRows);
公共卡(IMatrix号码)
{
if(numbers.NumberOfColumns!=NumberOfColumns | | numbers.NumberOfRows!=NumberOfRows)
抛出新ArgumentException($“为ID为{ID}的卡提供了{numbers.NumberOfColumns}x{numbers.NumberOfRows}数字矩阵,而不是预期的{NumberOfColumns}x{NumberOfRows}数字矩阵。”,nameof(提供者));
for(int column=0;column\u NumberOfColumns;
public int NumberOfRows=>\u NumberOfRows;
public ICell GetCellValue(int列,int行)=>Cells.GetCell(列,行).Clone();
公共布尔标志(整数)
{
var cell=GetCell(编号);
如果(单元格!=null)
{ 
cell.Called=true;
}
返回单元格!=null;
}
公共布尔取消标记(整数)
{
var cell=GetCell(编号);
如果(单元格!=null)
{
cell.Called=false;
}
返回单元格!=null;
}
...
}

我喜欢这种方法,因为它通过IMatrix属性使行和列的数量变得明显,并允许我轻松添加另一个更大的卡片类,它可以采用10x10矩阵或我需要的任何东西。因为他们都在使用接口,这意味着所需的代码更改应该是最小的。此外,如果我决定在内部使用列表而不是多维数组(可能是出于性能原因),我需要做的就是更新矩阵类实现。

无论您选择什么实现,都不应该让这些信息公开。您已经有了卡的接口。但是你需要的是一些能够以安全的方式提供细胞的方法。例如索引器:

public Cell this[int row, int column]
{
   // check for boundaries
   return _cells[row, column];
}
或其他一些辅助方法,如:

public IEnumerable<Cell> GetRow(int row)
{
    // return all cells of specified row
}
public IEnumerable GetRow(int行)
{
//返回指定行的所有单元格
}
属性CardSize表示一张卡片上有多少列和行。CardSize应该是构造函数中初始化的只读信息


正如我所想的,您需要另一种卡片方法,如“SetCell”、“MoveCell”、“ClearCell”。(很抱歉,我不知道bingo是如何工作的。)

无法指定方法或属性返回的数组的实际大小。A.
bool[,] cells = obj.cells;
for(int i = 0; i <= cells.GetUpperBound(0); i++) {
    for(int j = 0; j <= cells.GetUpperBound(1); j++) {
        // Do something with cells[i,j] 
    }
}
public interface ICard<T>
{
    T[,] GetCells();
    T GetCell(int row, column);
}
public class Card : ICard
{    
    ...
    public const int MaxRows = 5;
    public const int MaxColumns = 5;

    private readonly int _rows;
    private readonly int _columns;

    public Card(int rows, int columns)
    {
        if(columns > MaxColumns || rows > MaxRows) 
        {
            throw new ArgumentExcetion(...);
        }
    }
    ...
    public int Rows { get { return _rows; } }
    public int Columns { get { return _columns; } } 
}
public Card()
    : this(MaxRows, MaxColumns)
{
}
static void Main()
{
    var card = new Card();

    card.cells[3, 2] = true;

    Console.WriteLine(card.cells[2, 4]); // False
    Console.WriteLine(card.cells[3, 2]); // True
    Console.WriteLine(card.cells[8, 9]); // Exception
}

public interface ICard
{
    Cells cells { get; set; }
}

public class Card : ICard
{
    Cells _cells = new Cells();

    public Cells cells { get { return _cells; } set { _cells = value; } }
}

public class Cells : List<bool>
{
    public Cells()
    {
        for (int i = 0; i < 25; i++)
        {
            this.Add(false);
        }
    }

    public virtual bool this[int row, int col]
    {
        get
        {
            if (row < 0 || row >= 5 || col < 0 || col >= 5) throw new IndexOutOfRangeException("Something");
            return this[row * 5 + col];
        }
        set
        {
            if (row < 0 || row >= 5 || col < 0 || col >= 5) throw new IndexOutOfRangeException("Something");
            this[row * 5 + col] = value;
        }
    }
}
public class Cell
{
  public int Value { get; set; }
  public bool Flagged { get; set; }  // terrible name imho
}

public class Board
{
  private List<List<Cell>> Rows;
  private List<List<Cell>> Columns;      

  public Board(IEnumerable<Cell> cells)
  {
    if (cells == null)
      throw new ArgumentNullException();

    if (cells.Count() != 25)
      throw new ArgumentException("cells must contain exactly 25 cells");

    // additional logic to make sure you don't have same values
    // or to many numbers in a specific range etc

    var orderedCells = cells.OrderBy(c => c.Value);

    Columns = new List<List<Cell>>()
    {
      orderedCells.Take(5).ToList(),
      orderedCells.Skip(5).Take(5).ToList(),  
      orderedCells.Skip(10).Take(5).ToList(),  
      orderedCells.Skip(15).Take(5).ToList(), 
      orderedCells.Skip(20).Take(5).ToList()  
    }

    Rows = Enumerable.Range(0,5)
      .Select(i => new List<Int>()
        {
          Columns.ElementAt(0).Skip(i).First(),
          Columns.ElementAt(1).Skip(i).First(),
          Columns.ElementAt(2).Skip(i).First(),
          Columns.ElementAt(3).Skip(i).First(),
          Columns.ElementAt(4).Skip(i).First(),
        })
      .ToList();
  }

  public IEnumerable<Cell> GetRow(int index)
  {
    if (index < 0 || index >= Rows.Count())
      throw new ArgumentOutOfRangeException();

    return Rows.ElementAt(index);
  }


  public IEnumerable<Cell> GetColumn(int index)
  {
    if (index < 0 || index >= Rows.Count())
      throw new ArgumentOutOfRangeException();

    return Rows.ElementAt(index);
  }


  public Cell GetCell(int column, int row)
  {
    if (column < 0 || column >= Columns.Count())
      throw new ArgumentOutOfRangeException();

    if (row < 0 || row >= Rows.Count())
      throw new ArgumentOutOfRangeException();


    return Rows.ElementAt(row).ElementAt(column);
  }
}
class ListOfFives<T> : List<T>
{
  public ListOfFives<T>(): base(capacity:5)
  { 
  }
}
public interface ICard
{
    ListOfFives<ListOfFives<T>> Cells { get; }
}