Попытка создать неизменный класс - закрытый набор игнорируется

1

Я пытаюсь создать неизменяемый класс для представления матрицы. Однако, хотя частные сеттеры в рядах и столбцах работают, я все еще могу изменять содержимое элементов. Как правильно создать неизменяемый класс?

    var matrix = new Matrix(new double[,] {
                                          {1,1,1,1},
                                          {1,2,3,4},
                                          {4,3,2,1},
                                          {10,4,12, 6}});

    matrix.Elements[1,1] = 30; // This still works!

Класс матрицы:

   class Matrix
    {
        public uint Rows { get; private set; }
        public uint Columns { get; private set; }
        public double[,] Elements { get; private set; }

        public Matrix(double[,] elements)
        {
            this.Elements = elements;
            this.Columns = (uint)elements.GetLength(1);
            this.Rows = (uint)elements.GetLength(0);
        }
    }
  • 0
    Не делись своим массивом с внешним миром. Это не является неизменным. Вместо этого используйте индексатор для предоставления доступа на чтение. Я приведу пример через несколько минут.
  • 0
    Я полагаю, что вместо использования 2d-массива вам придется создать собственный класс, который обернет 2d-массив индексатором private set
Показать ещё 4 комментария
Теги:
design-patterns
immutability

4 ответа

5

Массив не является неизменным. Вам нужно использовать индекс, чтобы иметь неизменяемый тип:

class Matrix
{
    public uint Rows { get; private set; }
    public uint Columns { get; private set; }
    private readonly double[,] elements;

    public Matrix(double[,] elements)
    {
        // this will leave you open to mutations of the array from whoever passed it to you
        this.elements = elements;

        // this would be perfectly immutable, for the price of an additional block of memory:
        // this.elements = (double[,])elements.Clone();

        this.Columns = (uint)elements.GetLength(1);
        this.Rows = (uint)elements.GetLength(0);
    }

    public double this[int x, int y]
    {
      get
      {
        return elements[x, y];
      }

      private set
      {
        elements[x, y] = value;
      }
    }
}

Вы можете использовать это с помощью индексатора в своем экземпляре:

var matrix = new Matrix(new double[,] {
                                      {1,1,1,1},
                                      {1,2,3,4},
                                      {4,3,2,1},
                                      {10,4,12, 6}});

double d = matrix[1,1]; // This works, public getter

matrix[1,1] = d; // This does not compile, private setter
  • 1
    @ user9993 Я обновил пост с примером.
  • 1
    Обязательно проверьте ответ Фаланве о необходимости в неизменяемом массиве ... Примечание: set , даже личное выглядит очень странно в классе "неизменяемого".
Показать ещё 2 комментария
3

Я бы не стал раскрывать массив, а вместо этого создал индекс:

public double this[int x, int y]
{
    get
    {
        return elements[x,y];
    }
}

Затем вы использовали бы индексатор следующим образом:

matrix[1,1]
  • 1
    Это лучшее, нет необходимости в установщике, потому что другие функции-члены могут напрямую записывать elements[x,y]
3

Как указывали другие, вы можете почти достичь неизменности, не подвергая свой внутренний массив и вместо этого подвергая индексатора внешнему миру.

public class Matrix
{
    private readonly double[,] _elements;

    public uint Rows { get { return (uint)elements.GetLength(0);} }
    public uint Columns { get { return (uint)elements.GetLength(1);} }

    public Matrix(double[,] elements)
    {
        this._elements = elements;
    }

    public double this[int x, int y]
    {
      get
      {
        return elements[x, y];
      }
   }
}

Теперь вы можете легко получить доступ к своим элементам:

matrix[1,2]

Это почти делает ваш класс неизменным. Почему почти? Поскольку внутренний массив все еще изменен, и он был передан вам вызывающим абонентом, который может впоследствии его изменить (по ошибке или по назначению), побеждая вашу неизменность.

Как предотвратить этот недостаток?

Самый простой способ - сделать копию исходного массива (с помощью метода Clone() или иначе, вашего выбора) и сохранить эту копию. Таким образом, ваш внутренний массив никогда не подвергается чему-либо вне класса, обеспечивая истинную внешнюю неизменность (конечно, запрещая любое отражение или небезопасный код).

Другим приятным вариантом для достижения неизменности является использование элементов из пространства имен System.Collections.Immutable как ваши строительные блоки. Наличие уже неизменяемых коллекций позволяет очень просто реализовать более сложные сценарии.

0

Это еще один способ...

class Matrix
    {
        private uint Rows { get; private set; }
        private uint Columns { get; private set; }
        private double[,] Elements { get; private set; }

        public Matrix(double[,] elements)
        {
            this.Elements = elements;
            this.Columns = (uint)elements.GetLength(1);
            this.Rows = (uint)elements.GetLength(0);
        }

        public double GetElement(int row, int column)
        {
            return this.elements[row, column];
        }
    }

Сделайте массив приватным и выведите метод для извлечения значения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню