一、字典

        在 C# 中,可以通过使用 ReadOnlyDictionary<TKey, TValue> 类或者是通过调用普通字典的 .AsReadOnly() 方法来创建一个只读的字典。ReadOnlyDictionary 不允许修改字典,任何试图改变字典的操作都会抛出 NotSupportedException

        以下是使用 ReadOnlyDictionary<TKey, TValue> 类来返回一个只读字典的例子:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

class Program
{
    static void Main()
    {
        // 创建一个可修改的字典
        Dictionary<int, string> modifiableDict = new Dictionary<int, string>();
        modifiableDict.Add(1, "One");
        modifiableDict.Add(2, "Two");
        modifiableDict.Add(3, "Three");

        // 创建一个只读字典
        ReadOnlyDictionary<int, string> readOnlyDict = new ReadOnlyDictionary<int, string>(modifiableDict);

        // 返回只读字典
        foreach (var kvp in readOnlyDict)
        {
            Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
        }

        // 任何试图修改 readOnlyDict 的操作都会抛出异常
        // readOnlyDict.Add(4, "Four"); // NotSupportedException
    }
}

        如果你想要创建并返回一个只读的字典,但不想引入 ReadOnlyDictionary<TKey, TValue> 的依赖,你可以定义一个返回 IReadOnlyDictionary<TKey, TValue> 的方法,例如:

public IReadOnlyDictionary<TKey, TValue> GetReadOnlyDictionary<TKey, TValue>(IDictionary<TKey, TValue> dictionary)
{
    return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}

        这样做的好处是你的方法签名不会显示具体的 ReadOnlyDictionary 类型,增强了代码的抽象化,只需知道返回的是一个不可变的字典接口。当然,实际返回的依然是 ReadOnlyDictionary 实例。调用者仅能通过 IReadOnlyDictionary 接口暴露的成员来使用这个字典,而不是 ReadOnlyDictionary 类的成员。

二、列表

        在 C# 中,要返回一个只读的列表,可以使用 ReadOnlyCollection<T> 类,或者使用 LINQ 扩展方法 ToList().AsReadOnly() 来将一个 List<T> 转换为 IReadOnlyList<T>。这样的列表对外部是不可变的,即不允许增加、删除或修改元素。

        下面是如何使用 ReadOnlyCollection<T> 来创建只读列表的例子:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class Program
{
    public static void Main()
    {
        // 创建一个可变的列表
        List<int> modifiableList = new List<int> { 1, 2, 3 };

        // 创建一个只读的列表
        ReadOnlyCollection<int> readOnlyList = new ReadOnlyCollection<int>(modifiableList);

        // 使用只读列表
        foreach (var item in readOnlyList)
        {
            Console.WriteLine(item);
        }

        // 任何试图修改 readOnlyList 的操作都会抛出异常
        // readOnlyList[0] = 10;  // NotSupportedException
        // readOnlyList.Add(4);    // NotSupportedException
    }
}

        在上面的代码中,我们首先创建了一个包含三个元素的 List<int> 对象。然后我们将这个列表传递给 ReadOnlyCollection<int> 的构造函数,这样创建了一个只读的列表 readOnlyList。这个只读列表包装了原始的列表,但是不提供任何修改该列表的方法。这意味着,尝试对 readOnlyList 进行任何修改都会导致运行时抛出异常。

        如果需要返回符合 IReadOnlyList<T> 接口的只读列表,可以使用下面的方法:

public IReadOnlyList<T> GetReadOnlyList<T>(IList<T> list)
{
    return new ReadOnlyCollection<T>(list);
}

使用这种方式,你可以保持代码对返回类型的抽象,同时确保在任何情况下列表都是只读的。

三、数组

        在C#中,数组(T[])是一个定长的数据结构,其大小在创建时被确定,并且不能被动态地增长或收缩。要想返回一个只读的数组,你可以返回数组的一个拷贝,客户代码可以自由地修改这个数组的副本,而不会影响原始数组。

        以下是创建一个数组并返回其副本的示例代码:

public class ArrayExample
{
    private int[] _numbers;

    public ArrayExample()
    {
        _numbers = new[] {1, 2, 3, 4, 5}; // 初始化数组
    }

    public int[] GetNumbers()
    {
        // 返回原始数组的一个副本
        return (int[])_numbers.Clone();
    }
}

        在这个例子中,GetNumbers 方法使用 Clone 方法来创建 _numbers 数组的一个完整拷贝,并将其返回。在这种情况下,调用者可以对返回的数组副本进行任何操作,而不会影响原始数组。

        请注意,这种方法只适用于数组包含的是值类型(比如 intdoublechar 等)或不可变的引用类型(比如 string)。如果数组包含可变的引用类型,那么即使返回数组的副本,数组内部的对象仍然是可以被修改的。在这种情况下,你可能需要进行更深层次的拷贝(深拷贝),例如拷贝数组中每个对象的副本而不仅仅是引用。

        如果你想要确保原始数组不能被修改,你还可以使用 Array.AsReadOnly 方法将数组转换为 ReadOnlyCollection<T>,但是请注意这个方法不会返回数组类型,而是返回实现了IReadOnlyList<T> 和 IReadOnlyCollection<T>接口的只读集合。

public ReadOnlyCollection<int> GetReadOnlyNumbers()
{
    // 返回只读集合封装的原始数组
    return Array.AsReadOnly(_numbers);
}

        使用上述方法,用户可以访问原始数组的元素,但不能修改集合。这是一个返回原始数组只读视图的好方法,但请注意调用者得到的返回类型不是数组本身,而是封装数组的 ReadOnlyCollection<T> 对象。

03-27 11:47