试图创建不可变类 - 私有集被忽略

时间:2014-10-07 15:23:24

标签: c# design-patterns immutability

我正在尝试创建一个表示矩阵的不​​可变类。但是,当行和列中的私有setter正在工作时,我仍然可以修改Elements的内容。如何正确创建不可变类?

    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);
        }
    }

4 个答案:

答案 0 :(得分: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 :(得分:3)

我不会公开数组,而是创建一个索引器:

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

然后你会像这样使用索引器:

matrix[1,1]

答案 2 :(得分: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中的元素作为构建块。拥有不可变的集合有助于实现更复杂的场景。

答案 3 :(得分: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];
        }
    }

将数组设为私有,并公开一个方法来检索值。