C数组#

数组是一种特殊类型的数据类型,它可以使用特殊语法 顺序存储固定数量的值。

数组声明:

int [] intArr;

数组初始化:使用new关键字同时声明和初始化数组,下面三种方式等价

int[] intArr = new int[5]; 
int[] intArr = new int[5]{1 2 3 4 5};
int[] intArr = {1 2 3 4 5};

延迟初始化:可以先声明后再初始化数组

string[] strArr strArr;
strArr = new string[5]{ "1st Element""2nd Element" "3rd Element"};
strArr = new string[]{ "1st Element""2nd Element" "3rd Element"};

索引访问数组中元素:

intArr[索引位置];

常用方法和属性:

方法名称描述
GetLength(int维度)返回指定维度中的元素数
GetLowerBound(int维度)返回指定维度的最低索引
GetUpperBound(int维度)返回指定维度的最高索引
GetValue(int index)返回指定索引处的值
属性描述
Length返回数组中元素的总数
[TestMethod]
public void ArrayDefinition()
{
    // 数组可以先声明后赋值,也可以声明同时赋值,下面的方式是等价的,数组中必须存储同一类型数据,这在数组被定义时就已经确定
    // 第一种
    int[] intArr = new int[5];
    intArr[0] = 1;
    intArr[1] = 2;
    intArr[2] = 3;
    intArr[3] = 4;
    intArr[4] = 5;

    // 第二种
    int[] intArr2 = new int[5] { 1, 2, 3, 4, 5 };

    // 第三种
    int[] intArr3 = { 1, 2, 3, 4, 5 };

    // 注意这里是维度不是索引
    Console.WriteLine("GetLength(int维度):返回指定维度中的元素数,值为:{0}", intArr.GetLength(0));
    Console.WriteLine("GetLowerBound(int维度):返回指定维度的最低索引,值为:{0}", intArr.GetLowerBound(0));
    Console.WriteLine("GetUpperBound(int维度):返回指定维度的最高索引,值为:{0}", intArr.GetUpperBound(0));
    Console.WriteLine("GetValue(int index):	返回指定索引处的值,值为:{0}", intArr.GetValue(2));
    Console.WriteLine("属性:Length:返回数组中元素的总数,值为:{0}", intArr.Length);

    // 使用索引来访问数组元素
    Console.WriteLine("使用索引访问数组元素,索引为2的值为:{0}", intArr[2]);


    // 试图访问数组中不存在的索引元素,会发生数组越界
    Assert.ThrowsException<IndexOutOfRangeException>(() =>
    {
        Console.WriteLine("使用索引访问数组元素,索引为10的值为:{0}", intArr[10]);
    });


    for (int i = 0; i < intArr.Length; i++)
    {
        Console.WriteLine(intArr[i]);
    }

    foreach (var item in intArr)
    {
        Console.WriteLine(item);
    }

    Person p1 = new() { Address = "wang" };
    Person p2 = new() { Address = "li" };
    Person[] persons = new Person[2] { p1, p2 };
    Assert.AreEqual("wang", persons[0].Address);
    Assert.AreEqual("li", persons[1].Address);
}

多维数组#

多维数组是行和列的二维系列。多维数组又称为矩形数组;在本质上,是个一维数组的列表。

[TestMethod]
public void MultidimensionalArray()
{
    // 下面的两种创建方式等价
    // 第一种
    int[,] intArray1 = { { 1, 1 }, { 1, 2 }, { 1, 3 } };

    //第二种
    int[,] intArray2 = new int[3, 4]
    {    /*  初始化化一个三行四列的数组 */
           {0, 1, 2, 3} ,                /*  初始化索引号为 0 的行 */
           {4, 5, 6, 7} ,                /*  初始化索引号为 1 的行 */
           {8, 9, 10, 11}                /*  初始化索引号为 2 的行 */
    };
    // 获取数组中第3行第4个元素                                
    Console.WriteLine("二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问,值为:{0}", intArray2[2, 3]);
    Console.WriteLine("属性:Length:返回数组中元素的总数,值为:{0}", intArray1.Length);
    Assert.AreEqual(11, intArray2[2, 3]);
}

可以使用两个索引访问多维数组的值。第一个索引用于行,第二个索引用于列。两个索引都从零开始。

锯齿状数组#

锯齿状数组是数组的数组。Jagged数组直接存储数组而不是任何其他数据类型值。锯齿状数组用两个方括号 [][] 初始化。

第一个括号指定数组的大小,第二个括号指定将作为值存储的数组的维度。(锯齿状数组总是存储一个数组),二维数组的大小是矩形的。

例如: 3×3个元素。而锯齿数组的大小设置是比较灵活的,在锯齿数组中,每一行都可以有不同的大小

[TestMethod]
public void JaggedArray()
{
    int[][] intJaggedArray = new int[2][];
    intJaggedArray[0] = new int[3] { 1, 2, 3 };
    intJaggedArray[1] = new int[2] { 4, 5 };

    Assert.AreEqual(1, intJaggedArray[0][0]);
    Assert.AreEqual(3, intJaggedArray[0][2]);
    Assert.AreEqual(5, intJaggedArray[1][1]);
}

Array#

.NET提供了一个抽象类 Array ,作为所有数组的基类。它提供了用于创建,操作,搜索和排序数组的静态方法。

属性描述
IsFixedSize获取一个值,该值指示数组是否带有固定大小
IsReadOnly获取一个值,该值指示数组是否只读
Length获取一个 32 位整数,该值表示所有维度的数组中的元素总数
LongLength获取一个 64 位整数,该值表示所有维度的数组中的元素总数
Rank获取数组的秩(维度)
方法描述
Clear根据元素的类型,设置数组中某个范围的元素为零、为 false 或者为 null
Copy(Array,Array,Int32)从数组的第一个元素开始复制某个范围的元素到另一个数组的第一个元素位置。长度由一个 32 位整数指定
CopyTo(Array,Int32)从当前的一维数组中复制所有的元素到一个指定的一维数组的指定索引位置。索引由一个 32 位整数指定
GetLength获取一个 32 位整数,该值表示指定维度的数组中的元素总数
GetLongLength获取一个 64 位整数,该值表示指定维度的数组中的元素总数
GetLowerBound获取数组中指定维度的下界
GetType获取当前实例的类型。从对象(Object)继承
GetUpperBound获取数组中指定维度的上界
GetValue(Int32)获取一维数组中指定位置的值。索引由一个 32 位整数指定
IndexOf(Array,Object)搜索指定的对象,返回整个一维数组中第一次出现的索引
Reverse(Array)逆转整个一维数组中元素的顺序
SetValue(Object, Int32)给一维数组中指定位置的元素设置值。索引由一个 32 位整数指定
Sort(Array)使用数组的每个元素的 IComparable 实现来排序整个一维数组中的元素
ToString返回一个表示当前对象的字符串。从对象(Object)继承
[TestMethod]
public void ArrayClass()
{
    Array arr = Array.CreateInstance(typeof(int), 3);
    for (int i = 0; i < 3; i++)
    {
        // 第一个参数是value,第二个参数是index
        arr.SetValue(i, i);
    }
    Console.WriteLine("IsFixedSize:数组是否带有固定大小,值为:{0}", arr.IsFixedSize);
    Console.WriteLine("IsReadOnly :数组是否只读,值为:{0}", arr.IsReadOnly);
    Console.WriteLine("Length     :32位整数,数组元素总数,值为:{0}", arr.Length);
    Console.WriteLine("LongLength :64位整数,数组元素总数,值为:{0}", arr.LongLength);
    Console.WriteLine("Rank       :数组的维度,值为:{0}", arr.Rank);

    Array.Clear(arr, 0, 3);
    for (int i = 0; i < arr.Length; i++)
    {
        Assert.AreEqual(0, arr.GetValue(i));
    }

    // 显式将arr转换为数组
    int[] arr2 = (int[])arr;

    for (int i = 0; i < arr2.Length; i++)
    {
        Assert.AreEqual(0, arr2[i]);
    }
}

非泛型集合#

每个集合类都实现 IEnumerable接口,因此可以使用 foreach 循环访问集合中的值。System.Collections 命名空间中包括以下非泛型集合:

类型用法
ArrayListArrayList存储任何类型的对象,如数组。但是,当数组自动增长时,无需像数组那样指定ArrayList的大小
SortedListSortedList存储键和值对。它默认按键的升序自动排列元素。C#包括泛型和非泛型SortedList集合
StackStack以LIFO样式存储值(后进先出)。它提供了一个Push()方法来添加一个值,Pop()和Peek()方法来检索值。C#包括通用和非通用堆栈
Queue队列以FIFO样式(先进先出)存储值。它保持添加值的顺序。它提供了一个Enqueue()方法来添加值,还提供了一个Dequeue()方法来从集合中检索值。C#包括通用和非通用队列
HashtableHashtable存储键和值对。它通过比较键的哈希值来检索值
BitArrayBitArray管理一个紧凑的位值数组,表示为布尔值,其中true表示该位为on(1),false表示该位为off(0)

ArrayList#

可以包含任何数据类型的元素。类似于数组,但是在添加元素时不需要指定大小, ArrayList 的大小会自动增长。

要点:

  • 可以存储任何数据类型的项(元素)
  • 添加元素时会自动调整大小
  • 可以包含多个null
  • 可以使用 foreach 或 for 循环或索引器访问
属性描述
Capacity获取或设置ArrayList可以包含的元素数
Count获取ArrayList中实际包含的元素数
IsFixedSize获取一个值,该值指示ArrayList是否具有固定大小
IsReadOnly获取一个值,该值指示ArrayList是否为只读
Item获取或设置指定索引处的元素
方法描述
Add()/AddRange()Add()方法在ArrayList的末尾添加单个元素。 AddRange()方法将指定集合中的所有元素添加到ArrayList中
Insert()/InsertRange()Insert()方法在ArrayList中的指定索引处插入单个元素。 InsertRange()方法从ArrayList中的指定索引开始插入指定collection的所有元素
Remove()/RemoveRange()Remove()方法从ArrayList中删除指定的元素。 RemoveRange()方法从ArrayList中删除一系列元素
RemoveAt()从ArrayList中删除指定索引处的元素
Sort()对ArrayList的整个元素进行排序
Reverse()反转整个ArrayList中元素的顺序
Contains检查ArrayList中是否存在指定的元素。如果存在则返回true,否则返回false
Clear删除ArrayList中的所有元素
CopyTo将所有元素或元素范围复制到compitible Array
GetRange从ArrayList返回指定索引中指定数量的元素
IndexOf搜索指定的元素并返回零基索引(如果找到)。如果找不到元素,则返回-1
ToArray从ArrayList返回compitible数组
[TestMethod]
public void ArrayList()
{
    ArrayList arrayList = new();
    arrayList.Add("wang");
    arrayList.Add(1);
    // ArrayList允许插入null
    arrayList.Add(null);

    foreach (var item in arrayList)
    {
        Console.WriteLine(item);
    }
    Assert.AreEqual("wang", arrayList[0]);
}

SortedList#

SortedList 集合默认按键的升序存储键值对。SortedList 类实现了 IDictionaryICollection 接口,因此可以通过键和索引访问元素。

要点:

  • C#具有泛型和非泛型 SortedList
  • SortedList 按键的升序存储键值对。键必须是唯一的,不能为null,而值可以为null或重复项
  • 非泛型 SortedList 存储任何数据类型的键和值。因此,需要将值转换为适当的数据类型
  • 键值对可以强制转换为 DictionaryEntry
  • 使用索引器访问单个值。SortedList 索引器接受键并返回与之关联的值
属性描述
Capacity获取或设置SortedList实例可以存储的元素数
Count获取SortedList中实际包含的元素数
IsFixedSize获取SortedList中实际包含的元素数
IsReadOnly获取一个值,该值指示SortedList是否为只读
Item获取或设置SortedList中指定键的元素
Keys获取SortedList的键列表
Values获取SortedList中的值列表
方法描述
Add(object key, object value)将键值对添加到SortedList中
Remove(object key)删除具有指定键的元素
RemoveAt(int index)删除指定索引处的元素
Contains(object key)检查SortedList中是否存在指定的键
Clear()从SortedList中删除所有元素
GetByIndex(int index)返回存储在内部数组中的索引值
GetKey(int index)检返回存储在内部数组中指定索引处的键
IndexOfKey(object key)返回存储在内部数组中的指定键的索引
IndexOfValue(object value)返回存储在内部数组中的指定值的索引
[TestMethod]
public void SortedList()
{
    SortedList sortedList = new();
    sortedList.Add(2, "wang");
    sortedList.Add(5, "li");
    sortedList.Add(3, 5);

    // SortedList键可以是任何数据类型,但不能在同一SortedList中添加不同数据类型的键。
    Assert.ThrowsException<InvalidOperationException>(() =>
    {
        sortedList.Add("wang", 32);
    });


    for (int i = 0; i < sortedList.Count; i++)
    {
        Console.WriteLine("key:{0},value:{1}", sortedList.GetKey(i), sortedList.GetByIndex(i));
    }

    foreach (DictionaryEntry item in sortedList)
    {
        Console.WriteLine("key:{0},value:{1}", item.Key, item.Value);
    }
}

Stack#

LIFO 样式存储元素(后进先出)。C#包括通用和非通用堆栈,非泛型堆栈。Stack允许空值以及重复值。它提供了一个 Push() 方法来添加一个值, Pop() 或 Peek() 方法来检索值。

要点:

  • 最后添加的元素将是首先出现的元素LIFO(后进先出)
  • 使用 Push() 方法添加元素
  • Pop() 方法返回并从堆栈顶部删除元素。在空 Stack 上调用 Pop() 方法将引发异常
  • Peek() 方法返回 Stack 中最顶层的元素

属性方法
Count返回Stack中元素的总数
方法描述
Push在堆栈顶部插入一个项目
Peek返回堆栈中的顶部项
Pop从堆栈顶部删除并返回项目
Contains检查堆栈中是否存在项目
Clear从堆栈中删除所有项目
[TestMethod]
public void Stack()
{
    Stack stack = new();
    stack.Push("1");
    stack.Push(1);
    stack.Push(false);

    foreach (var item in stack)
    {
        Console.WriteLine(item);
    }
}

Queue#

FIFO样式(先进先出)存储元素。与Stack集合完全相反,它按照添加顺序包含元素。队列集合允许多个空值和重复值。使用 Enqueue() 方法添加值,使用Dequeue()方法从队列中检索值。

要点:

  • 首先添加的元素将首先出现FIFO(先进先出)
  • 使用Enqueue()方法添加元素
  • Dequeue()方法返回并从队列的开头删除元素。在空队列上调用Dequeue()方法将引发异常
  • Peek()方法总是返回最顶层的元素

属性描述
Count返回Stack中元素的总数
方法描述
Enqueue将项添加到队列中
Dequeue从队列的开头删除并返回一个项目
Peek返回队列中的第一个项目
Contains检查项目是否在队列中
Clear从队列中删除所有项目
TrimToSize将队列的容量设置为队列中的实际项目数
 [TestMethod]
 public void Queue()
 {
     Queue queue = new();
     queue.Enqueue("1");
     queue.Enqueue(1);
     queue.Enqueue(false);

     foreach (var item in queue)
     {
         Console.WriteLine(item);
     }
 }

Hashtable#

类似于通用字典集合。Hashtable集合存储键值对。通过计算每个密钥的哈希码来优化查找,并在内部将其存储在不同的存储桶中,然后在访问值时匹配指定密钥的哈希码。

要点:

  • 存储Key必须唯一的任何数据类型的键值对
  • 键不能为null,而值可以为null
  • 通过比较键的哈希码来检索项目。因此性能比 Dictionary 集合慢
  • 使用默认的哈希码提供程序,即 object.GetHash()。还可以使用自定义哈希码提供程序
  • DictionaryEntryforeach 语句一起使用以迭代 Hashtable
属性描述
Count获取Hashtable中键/值对的总数
IsReadOnly获取布尔值,指示Hashtable是否为只读
Item获取或设置与指定键关联的值
Keys获取Hashtable中的键的ICollection
Values获取Hashtable中值的ICollection
方法描述
Add将具有键和值的项添加到哈希表中
Remove从散列表中删除具有指定键的项
Clear从哈希表中删除所有项目
Contains检查哈希表是否包含特定密钥
ContainsKey检查哈希表是否包含特定密钥
ContainsValue检查哈希表是否包含特定值
GetHash返回指定键的哈希码
[TestMethod]
public void Hashtable()
{
    Hashtable hashtable = new()
    {
        { 1, "wang" },
        { 3, false },
        { 2, "li" }
    };

    foreach (DictionaryEntry item in hashtable)
    {
        Console.WriteLine("key:{0}, value:{1}", item.Key, item.Value);
    }
}

BitArray#

BitArray 类管理一个紧凑型的位值数组,它使用布尔值来表示,其中true表示位是开启的(1),false 表示位是关闭的(0)。当需要存储位但是事先不知道位数时,则使用点阵列。可以使用整型索引从点阵列集合中访问各项,索引从零开始。

属性描述
Count获取 BitArray 中包含的元素个数。
IsReadOnly获取一个值,表示 BitArray 是否只读。
Item获取或设置 BitArray 中指定位置的位的值。
Length获取或设置 BitArray 中的元素个数。
方法描述
public BitArray And( BitArray value );对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位与操作。
public bool Get( int index );获取 BitArray 中指定位置的位的值。
public BitArray Not();把当前的 BitArray 中的位值反转,以便设置为 true 的元素变为 false,设置为 false 的元素变为 true。
public BitArray Or( BitArray value );对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位或操作。
public void Set( int index, bool value );把 BitArray 中指定位置的位设置为指定的值。
public void SetAll( bool value );把 BitArray 中的所有位设置为指定的值。
public BitArray Xor( BitArray value );对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位异或操作。

泛型集合#

同传统的集合相比,泛型集合是一种强类型的集合,它解决了类型安全问题,同时避免了集合中每次的装箱与拆箱的操作,提升了性能。

List<T>#

List<T> 在C#应用程序中是一种快捷、易于使用的泛型集合类型,使用泛型编程为编写面向对象程序增加了极大的效率和灵活性,不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换。

名称描述
Add将对象添加到 List的结尾处
AddRange将指定集合的元素添加到 List的末尾
AsReadOnly返回当前集合的只读 IList包装
BinarySearch(T)使用默认的比较器在整个已排序的 List中搜索元素,并返回该元素从零开始的索引
BinarySearch(T, IComparer)使用指定的比较器在整个已排序的 List中搜索元素,并返回该元素从零开始的索引
BinarySearch(Int32, Int32, T, IComparer)使用指定的比较器在已排序 List的某个元素范围中搜索元素,并返回该元素从零开始的索引
Clear从 List中移除所有元素
Contains确定某元素是否在 List中
ConvertAll将当前 List<T中的元素转换为另一种类型,并返回包含转换后的元素的列表
CopyTo(T[])将整个 List<T复制到兼容的一维数组中,从目标数组的开头开始放置
Exists确定 List<T是否包含与指定谓词所定义的条件相匹配的元素
Find搜索与指定谓词所定义的条件相匹配的元素,并返回整个 List中的第一个匹配 元素
FindIndex(Predicate)搜索与指定谓词所定义的条件相匹配的元素,并返回整个List 中第一个匹配元素的从零开始的索引
ForEach对 List的每个元素执行指定操作。  GetEnumerator  返回循环访问 List的枚举器
IndexOf(T)搜索指定的对象,并返回整个 List中第一个匹配项的从零开始的索引
Insert将元素插入 List的指定索引处
InsertRange将集合中的某个元素插入 List的指定索引处
LastIndexOf(T)搜索指定的对象,并返回整个 List中最后一个匹配项的从零开始的索引
Remove从 List中移除特定对象的第一个匹配项
Reverse()将整个 List中元素的顺序反转
Sort()使用默认比较器对整个 List中的元素进行排序

Stack<T>#

后进先出 的方式维护数据的集合,包含 pop()push() 从栈内压入或移除数据。

Quenue<T>#

先进先出 的方式访问数据,使用 Enqueue()Dequeue()添加数据和移除数据。

SortedSet<T>#

这个类中的数据是排序的,在插入和移除数据之后仍然能自动排序,需要向其构造函数中传递一个实现了IComparer<T>,该接口定义了Compare方法。

ObservableCollection<T>#

表示能在添加、移除或者刷新整个列表时提供通知的动态数据集合, ReadOnlyObservableCollection<T> 的操作与之类似,不过是只读的ObservableCollection<T> 实现了一个名为CollectionChanged事件,该事件在插入新的数据或者移除数据时触发。

Dictionary<k,v>#

提供快速的基于键值的元素查找。结构是:Dictionary <[key],[value]>,当有很多元素的时候可以用它。在使用前,必须声明它的键类型和值类型。

数组池#

如果需要多次创建/销毁数组,为了减少GC操作,可以通过ArrayPool类使用数组池

[TestMethod]
public void ArrayPool()
{
    // maxArrayLengthDefaultValue: 1024 * 1024
    // maxArraysPerBucketDefaultValue: 50
    ArrayPool<int> arrayPool = ArrayPool<int>
        .Create(maxArrayLength: 4000, maxArraysPerBucket: 10);

    // 使用预定义的共享池
    ArrayPool<int> sharePool = ArrayPool<int>.Shared;
    Console.WriteLine($"{arrayPool},{sharePool}");
    Assert.IsTrue(sharePool != null);
}

数组/ArrayList/List区别#

  • 数组:针对特定类型固定长度,值不可为null,在声明数组的时候必须指定数组的长度,可有多个维度
  • Array:数组的另一种创建方式抽象类,作为所有数组的基类,针对任意类型固定长度,值可以为null,输出会被替换为0
  • ArrayList:针对任意类型、任意长度的,值可以为null,存储或检索值类型时通常发生装箱和拆箱操作,带来很大的性能耗损
  • List:强类型的集合,固定类型、任意长度,值不可为null,是类型安全的

枚举器#

迭代器#

索引器#

Indexer 是一种特殊类型的属性,允许以访问数组相同的方式访问类或结构。除了使用带有方括号和参数的此关键字定义的属性外,它与属性相同。

  • 索引器与属性相同,除了它使用带有方括号的此关键字定义,该参数具有参数
  • 可以通过具有不同类型的参数来覆盖索引器
  • 不支持使用索引器的Refout参数
  • 索引器可以作为接口成员包含在内
public <return typethis[<parameter typeindex]
{
    Get{
    // return the value from the specified index
    }
    Set{
    // set values at the specified index
    }
}
public class Indexer
{
        private int[] indexs = new int[10];

        public int this[int index]
        {
            get { return indexs[index]; }
            set
            {
                indexs[index] = value;
            }
        }
}

/*
Indexer是一种特殊类型的属性,允许以与其内部集合的数组相同的方式访问类或结构。
除了使用带有方括号和参数的此关键字定义的属性外,它与属性相同。
*/
Console.WriteLine("********************索引器*********************");

Indexer indexer = new Indexer();
indexer[0] = 1;
indexer[1] = 2;
Console.WriteLine(indexer[1]);

可观察的集合#

如果需要知道集合中的元素何时删除或添加的信息,就可以使用 ObservableCollection<T> 类。.Net Core中要使用,需要引用Nuget包System.ObjectModel。这个类的名称空间是System.Collections.ObjectModel。

ObservableCollection<T> 类派生自 Collection<T> 基类。该基类用于创建自定义集合。并在内部使用List类。重写基类中的虚方法SetItem()和RemoveItem(),以触发 CollectionChanged 事件。这个类的用户就可以使用 INotifyCollectionChanged 接口注册这个事件。

        /// <summary>
        /// 可观察的集合
        /// </summary>
        [TestMethod]
        public void ObservableArray()
        {
            var arr = new ObservableCollection<string>();
            arr.CollectionChanged += (object sender, NotifyCollectionChangedEventArgs e) =>
            {
                if (e.OldItems != null)
                {
                    Console.WriteLine($"index:{e.OldStartingIndex}");
                    Console.WriteLine("old items:");
                    foreach (var item in e.OldItems)
                    {
                        Console.WriteLine(item);
                    }
                }

                if (e.NewItems != null)
                {
                    Console.WriteLine($"index:{e.NewStartingIndex}");
                    Console.WriteLine("new items:");
                    foreach (var item in e.NewItems)
                    {
                        Console.WriteLine(item);
                    }
                }
            };
            arr.Add("One");
            arr.Add("Two");
            arr.Insert(1, "Three");
            arr.Remove("One");
        }

不可变集合#

如果对象可以改变其状态,就很难在多个同时运行的任务中使用。这些集合必须同步。

如果对象不能改变其状态,就很容易在多个线程中使用。不能改变的对象称为不变的对象。

不能改变的集合称为不变的集合。

为了使用不可变的集合。可以添加Nuget包 System.Collections.Immutable。命名空间 System.Collections.Immutable 中的集合类。

在使用不变数组的每个阶段,都没有复制完整的集合。相反,不变类型使用了共享状态,仅在需要时复制集合。

注意:先填充集合,再将它变成不变的数组会更高效。

        public void ImmutableArray()
        {
            // 先正常创建个集合
            var accounts = new List<string>()
            {
                "hello",
                "world",
                "a",
                "b"
            };

            // 使用ToImmutableList扩展方法创建一个不变的集合。
            // 也可以像其他集合那样枚举,只是不能改变。
            ImmutableList<string> immutableList = accounts.ToImmutableList();
            foreach (var item in immutableList)
            {
                Console.WriteLine(item);
            }
        }