C#:不可变的类

时间:2014-08-10 21:39:15

标签: c# arrays class properties immutability

我有一个类在本类中应该是不可变的我只有索引器一个私有的set属性所以为什么这不是不可变的我可以在数组中设置一些字段,你可以在主类中看到...

class ImmutableMatice
{       
    public decimal[,] Array { get; private set; } // immutable Property

    public ImmutableMatice(decimal[,] array)
    {
        Array = array;
    }
    public decimal this[int index1, int index2]
    {
        get { return Array[index1, index2]; }
    }

........ 如果我用数据填充此类并更改数据

,则在main方法中
    static void Main(string[] args)
    {
        decimal[,] testData = new[,] {{1m, 2m}, {3m, 4m}};
        ImmutableMatice matrix = new ImmutableMatice(testData);
        Console.WriteLine(matrix[0,0]); // writes 1
        testData[0, 0] = 999;
        Console.WriteLine(matrix[0,0]); // writes 999 but i thought it should 
                                        // write 1 because class should be immutable?
    }
 }

有没有办法让这个类不可变?

啊是的,解决方案是将构造函数中的复制数组复制到新数组中:

    public ImmutableMatice(decimal[,] array)
    {
        decimal[,] _array = new decimal[array.GetLength(0),array.GetLength(1)];
        //var _array = new decimal[,] { };
        for (int i = 0; i < array.GetLength(0); i++)
        {
            for (int j = 0; j < array.GetLength(1); j++)
            {
                _array[i, j] = array[i, j];
            }
        }
        Array = _array;
    }

2 个答案:

答案 0 :(得分:4)

你的类是不可变的,但它里面的对象不是。

拥有public decimal[,] Array { get; private set; }只能保证您不能将属性Array设置为Array的新实例,但它不会阻止您访问现有对象并更改其值(这不是不可变的。

您可能希望查看适当命名的ReadOnlyCollection<T>类。

正如@Mike指出的那样,我第一次看过去了:因为你通过testData对象而不是matrix访问了这个值,所以有一个转折点。虽然原始点仍然存在,但更准确地说,您遇到的问题是您正在更改其引用传递的基础对象中的值。您完全绕过ImmutableMatice对象。

前面提到的使用ReadOnlyCollection<T>的解决方案仍然存在:通过在其周围创建这个只读包装,您之后就无法再进行更改了。只有当你实际按照预期的方式使用它时才会这样:通过ImmutableMatice而不是通过你仍然有引用的底层集合。

解决这个问题的另一个解决方案是将原始数组的内容复制到另一个解决方案,以便&#34;断开&#34;从数组中你仍然可以参考。

为了说明这一点,请考虑以下示例。第一个演示如何仍然可以影响底层引用,而第二个演示如何通过将值复制到新数组来解决它。

void Main()
{
    var arr = new[] { 5 };
    var coll = new ReadOnlyCollection<int>(arr);
    Console.WriteLine (coll[0]); // 5
    arr[0] = 1;
    Console.WriteLine (coll[0]); // 1
}

void Main()
{
    var arr = new[] { 5 };
    var arr2 = new int[] { 0 };
    Array.Copy(arr, arr2, arr.Length);
    var coll = new ReadOnlyCollection<int>(arr2);
    Console.WriteLine (coll[0]); // 5
    arr[0] = 1;
    Console.WriteLine (coll[0]); // 5
}

答案 1 :(得分:4)

这是因为您实际上正在更改ARRAY中的数据,而不是索引器。

static void Main(string[] args)
{
    decimal[,] testData = new[,] {{1m, 2m}, {3m, 4m}};
    ImmutableMatice matrix = new ImmutableMatice(testData);
    Console.WriteLine(matrix[0,0]); // writes 1
    testData[0, 0] = 999; // <--- THATS YOUR PROBLEM
    Console.WriteLine(matrix[0,0]); // writes 999 but i thought it should 
                                    // write 1 because class should be immutable?
}

您可以将数组复制到构造函数中的私有属性中,以避免出现这种情况。

请注意,您确实无法编写matrix[0,0] = 999;,因为索引器没有setter。

修改

正如克里斯指出的那样(我怎么能错过它?) - 你根本不应该将数组暴露为属性(这意味着在大多数情况下它甚至不必是属性)。

请考虑以下代码:

private decimal[,] _myArray; // That's private stuff - can't go wrong there.

public decimal this[int index1, int index2]
{
    // If you only want to allow get data from the array, thats all you ever need
    get { return Array[index1, index2]; }
}