【问题标题】:C#: Immutable classC#:不可变类
【发布时间】:2014-10-03 16:33:33
【问题描述】:

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

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

【问题讨论】:

    标签: c# arrays class properties immutability


    【解决方案1】:

    那是因为您实际上是在更改 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;,因为索引器没有设置器。

    编辑

    正如 Chris 所指出的(我自己怎么会错过它?) - 您根本不应该将数组公开为属性(这意味着在大多数情况下它甚至不必是属性)。

    请考虑以下代码:

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

    【讨论】:

    • 此外,Seda 可能应该让public decimal[,] Array { get; private set; } 完全私有/不可访问。
    • @Chris 是的,确实如此。索引器应该是私有数组唯一对外暴露的地方。
    【解决方案2】:

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

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

    您可能想查看适当命名的 ReadOnlyCollection&lt;T&gt; 类。

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

    前面提到的使用ReadOnlyCollection&lt;T&gt; 的解决方案仍然有效:通过围绕它创建这个只读包装器,之后您将无法再更改它。但是,只有当您实际按照预期方式使用它时才会出现这种情况:通过ImmutableMatice 而不是通过您仍然引用的基础集合。

    解决此问题的另一种解决方案是将原始数组的内容复制到另一个数组中,以将其与您仍然引用的数组“断开”。

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

    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
    }
    

    【讨论】:

    • 这实际上只是它不是不可变的部分原因,实际上也不是示例无法按预期工作的原因。如果需要真正的不变性,则需要在构造函数中复制提供的数组。
    猜你喜欢
    • 1970-01-01
    • 2016-11-29
    • 1970-01-01
    • 2010-11-20
    • 2016-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多