C#集合数组


简单的介绍一下集合,通俗来讲就是用来保存多个数据的方案。比如说我们是一个公司的仓库管理,公司有一堆货物需要管理,有同类的,有不同类的,总而言之就是很多、很乱。我们对照集合的概念对仓库进行管理的话,那么 数组就是将一堆货整整齐齐的码在仓库的某个地方,普通列表也是如此; Set就是在仓库里有这么一个货架,每种货品只能放一个,一旦某种货品超过一个了货架就塌了; Dictionary字典呢,在一个货架上随机摆放,然后再找一个本子把每个货品存放的位置记录下来。

1. 主要集合

C#/.NET Framework 提供了很多很有意思的集合类,数组、列表、链表、Set、字典等一系列的类。其中数组是语言的一部分,个人认为严格意义上不属于集合类这一部分。C#开发中常用的集合有数组、 List类、Set接口、Dictionary类、Queue类、LinkedList类等,其他的出镜率不高。
与其他(java)语言不同的一点是,C#的List是类,而不是接口,接口是IList,但这个接口意义不大,在使用IList的时候更多的倾向于使用IEnumerable,这主要是因为IEnumerableLinq的支持再者两者的方法基本一致,能用IList的地方基本都可以用IEnumerable

1.1 Array 数组

数组,集合的基础部分,主要特点是一经初始化就无法再次对数组本身进行增删元素。C#虽然添加了一些修改数组的扩展方法,但基本都会返回新的数组对象。

1.1.1 初始化

数组的初始化需要指定大小,可以显示指定或者隐式的指定。

  1. // 显示指定类型与大小,具体的元素后续赋值
  2. string[] strArr = new string[10];
  3. //指定类型同时给元素赋值,具体大小由编译器自动推断
  4. string[] strArr1 = new string[]{"1","2","3","4","5","6","7","8","9","10"};
  5. // 类型和大小都由编译器进行推断
  6. string[] strArr2 = new []{"1","2","3","4","5","6","7","8","9","10"};

1.1.2 常用方法

  1. 访问和赋值
    数组可以通过下标访问数组中的元素,下标从0开始,表示0位。代码如下:

    1. string item0 = strArr[0]; //取出 "1"
    2. string item2 = strArr[2]; // 取出 "3"
    3. strArr[0] = "3"; // strArr = {"3","2","3","4","5","6","7","8","9","10"}
  2. 获取长度

    1. int length = strArr.Length;// 获取一个整型的长度
    2. //获取一个长整型的长度,对于一个非常大的数组且长度可能会超过int的最大值
    3. long longLength = strArr.LongLength;
  3. 循环迭代

    1. // 普通for 循环
    2. for(int i = 0;i < strArr.Length;i++)
    3. {
    4. string it = strArr[i];
    5. }
    6. // foreach 循环
    7. foreach(string it in strArr)
    8. {
    9. // 依次循环,不需要下标,操作更快一点
    10. }

    1.1.3 不常用但有用的方法

  4. CopyTo 复制到

    1. public void CopyTo(Array array, int index);
    2. public void CopyTo(Array array, long index);
  5. 参数说明: array 需要复制到的数组,index 目标数组的起始下标
    方法说明:将 源数组的元素依次复制到 array从index下标开始的位置

    1. string[] strArr1 = new string[]{"1","2","3","4","5","6","7","8","9","10"};
    2. string[] strArr3 = new string[10];
    3. strArr1.CopyTo(strArr3, 0); //strArr3 = {"1","2","3","4",'5","6","7","8","9","10"}
  6. 值得注意的是strArr3的长度不能 小于 index + strArr1.Length

  7. Sort 排序
    这个方法不是数组对象的方法,而是 Array 提供的一个静态方法。

    1. int[] arr1 = new[] {1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};
    2. Array.Sort(arr1);//0,1,2,3,4,5,6,9,12,13,18,28,44,81,92,98
  8. 值得注意的是,该方法是直接对数组进行操作,所以不会返回新的数组

  9. ToList 转成 List
    顾名思义,将Array对象转成List对象。这里需要额外注意的是,转换成的List是不可改变长度的
  10. Clone() 获得一个浅拷贝的数组对象
    获取该对象的一个浅拷贝数组对象。

int[] intArray1 = {1, 2};``int [] intArray2 = (int [])intArray1.Clone();这里需要说明的是,需要使用强制类型转换,原因在于Clone()返回的类型为Object
至于其他的Array类和Array对象 还有很多有意思的方法,但是平时开发的时候使用的频率比较低。这里就不一 一介绍了,以后需要会介绍一下的。

1.2 List 列表

List列表为一个泛型类,泛型表示,其中T表示列表中存放的元素类型,T代表C#中可实例化的类型。关于泛型的具体描述以后介绍,现在回过头来继续介绍列表。列表内部持有一个数组对象,列表有两个私有变量:一个是列表容量Capacity,即内部数组的大小;另一个是存放的元素数量Count,通过Count获取。
List列表通过元素数量实现了AddRemove 的操作,列表对象操作引发元素数量变动时都会导致对容量的重新计算,如果现有容量不满足后续操作需要的话,将会对现有数组进行扩充。

1.2.1 初始化

  1. List<string> list = new List<string>();// 初始化一个空的列表
  2. List<string> list1 = new List<string>{"12", "2"};//初始化一个包含两个元素的列表
  3. list1 = new List<string>(100);//初始化一个空的列表,并指定list的初始容量为100
  4. list = new List<string>(list1);// 使用一个List/Array 初始化一个列表

1.2.2 常用方法

  1. CountLongCount获取元素的数量
    Count 表示获取一个int类型的的数量值,LongCount表示获取一个long类型的数量值。通常情况下两者返回的结果是一致的,但是如果列表中元素的数量超过了int允许的最大返回直接使用 Count获取将会出现数据溢出的问题,这时候就需要LongCount了。
  2. 访问元素/修改元素
    C#的列表操作单个元素很简单 ,与数组的操作方式完全一样。

    1. string str = list1[0];//获取 list1 的第一个元素,即下标为0的元素
    2. list1[2] = "233"; // 将 list1 的第三个元素设置为“233” ,即下标为2 的元素,这里假设list1有至少三个元素
  3. 需要注意的地方是,如果给定的下标超过了List对象的索引值范围会报ArgumentOutOfRangeException。判断方法就是 下标>= Count,如果满足就会越界。

  4. AddAddRange 添加到列表最后
    将元素添加到List的末尾,Add添加一个,AddRange添加一组,支持数组、列表。

    1. List<string> list = new List<string>();// 初始化一个空的列表
    2. list.Add("12");//list = {"12"}
    3. List<string> list1 = new List<string>{"14", "2"};
    4. list.AddRange(list1);// list = {"12","14","2"}
  5. Insert(int index, T item)InsertRange(int index,IEnumerable<T> items)插入

    • Insert(int index,T item) 在 index 下标处插入一个元素,该下标以及该下标以后的元素依次后移
    • InsertRange(int index,IEnumerable<T> items) 在index下标处插入一组元素,该下标以及之后的元素依次后移
      示例:
      1. List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};
      2. arr1.Insert(3,37);// arr1 = 1,9,28,37,5,3,6,0,12,44,98,4,2,13,18,81,92 下标为3的元素变成了37,之后的元素依次后移了
      1. List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};
      2. List<int> arr2 = new List<int>{2,3,4,5};
      3. arr1.InsertRange(2,arr2);//arr1= 1,9,2,3,4,5,28,5,3,6,0,12,44,98,4,2,13,18,81,92 可以明显发现下标为2的元素发生了变化
  6. Contains(T item) 是否包含
    返回一个Boolean类型的结果,如果包含则返回true,如果不包含则返回false

    1. List<int> arr2 = new List<int>{2,3,4,5};
    2. arr2.Contains(8);//false
    3. arr2.Contains(3);//true
  7. Remove(T item) 删除指定元素

    1. List<int> arr2 = new List<int>{2,3,4,5};
    2. arr2.Remove(3);// arr2 = 2,4,5
    3. arr2.Remove(6);//arr2 = 2,4,5
  8. 值得注意的是,如果删除一个不存在的元素时,不会报错,列表也不会发生任何改变。

  9. RemoveAt(int index) 删除位于下标的元素

    1. List<int> arr2 = new List<int>{2,3,4,5};
    2. arr2.RemoveAt(1);//arr2 = 2,4,5
  10. 如果移除的下标超过了列表的最后一个元素的下标将会抛出异常

  11. RemoveRane(IEnumerable<T> items) 删除一组元素
    Remove(T item)一致,如果要删除的元素不在列表中,则列表元素不会发生变化。

    1. List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};
    2. List<int> arr2 = new List<int>{2,3,4,5};
    3. arr1.RemoveRange(arr2);
  12. GetRange(int index,int count)
    从列表中获取一个子列表,从index开始,获取count个元素,如果源列表中从index开始剩余的元素不足count个将会报错。

    1.2.3 不常用但有用的方法

  13. Clear()删除所有元素
    将列表清空,调用方法之后,列表中将不包含任何元素

  14. Reverse() 调转顺序
    将列表按照从尾到头的顺序进行排列
  15. IndexOf(T item) 查找下标
    查找元素在列表中的下标,如果没找到元素,则返回-1
  16. Sort()排序
    对列表进行排序,调用方法后,会按照默认排序方法返回一个排序结果

    1.3 Set 集合

    C#没有为Set单独设置类,一方面是因为Set出镜率不高,另一方面也因为Set本身的机制所致。Set集合不能包含重复元素,如果尝试存入重复元素集合元素将不会发生任何变化。
    Set集合中元素的顺序与存放顺序不一定相同。因为Set集合中存放对于使用者而言是乱序存放的。
    我们常用的Set集合有 HashSet<T>SortSet<T>,其他的Set相关类则属于更加少见。至少在我5年多的开发经历中没有用过。

    1.3.1 HashSet<T>SortSet<T>

  • HashSet 俗称 哈希集合或者哈希Set,内部使用Hash值作为元素的唯一性验证,即调用对象的HashCode()方法作为Hash值的来源。
  • SortSet 顾名思义,排序集合,它每次在插入的时候都会对元素进行一次排序

    1.3.2 共同点

  1. 初始化
    两者相同的地方就是 都有以下几种初始化方法

    1. Set<T> set = new HashSet<T>();// = new SortSet<T>(); 初始化一个空的集合
    2. //使用一个集合对象初始化
    3. Set<T> set1 = new HashSet<T>(IEnumerable<T> items);// = new SortSet<T>(IEnumerable<T> items);
    4. Set<T> set2 = new HashSet<T>(){T t1, T t2, T t3};// 与上一种一样
  2. 添加元素

    1. set1.Add(item);// 集合只支持添加单个元素,但是可以通过集合运算的方式增加多个元素
  3. 移除元素

    1. set1.Remove(item);//删除集合中与item判断相等的元素
  4. 访问元素
    需要注意的地方是,C#对Set没有支持下标访问方式获取Set里的元素,这是因为索引位置对于集合来说意义不大,没有操作意义。

    1. foreach (var item in set1)
    2. {
    3. // 操作
    4. }
  5. Set 只能通过遍历访问元素,不能通过Get或者下标操作访问元素。关于foreach循环会在下一篇《C#基础知识系列》里进行介绍。

  6. 集合运算C# 基础知识系列- 3 集合数组 - 图1

    • UnionWith 并集

      1. SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17};
      2. set.UnionWith(new []{5,57,8,4,3,1,0,33}); // set = 0,1,3,4,5,8,17,29,33,38,48,57
    • 通过传入一个集合对象,将该集合设置为两个集合的并集,也就是说取上图 A,B,C 三个区域的和

    • ExceptWith

      1. SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17};
      2. set.ExceptWith(new []{5,57,8,4,3,1,0,33}); // set =17,29,38,48
    • 传入一个集合,从set中去掉同属于两个集合的元素,保留只存在于set的元素,也就是取上图中的A部分元素

    • IntersectWith** 交**

      1. SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17};
      2. set.ExceptWith(new []{5,57,8,4,3,1,0,33}); // set =0,1,33
    • 传入一个集合,保留set与传入集合里相同的元素,也就是说取的是上图中的B部分

    • SymmetricExceptWith** 余集**

      1. SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17};
      2. set.SymmetricExceptWith(new []{5,57,8,4,3,1,0,33});//set= 3,4,5,8,17,29,38,48,57
    • 传入一个集合,保留set与传入集合两个集合中不同的元素,也就是取上图的A+C这两部分。

  7. Contains 包含
    判断集合中是否包含目标元素,返回true/false

    1. SortedSet<int> set = new SortedSet<int>{1,0,29,38,33,48,17};
    2. set.Contains(1);// true

    1.3.3 不同点

  8. 初始化

    • HashSet<T> 支持传入一个自定义的相等比较器,该比较器需要返回一个 bool值;可以指定起始容量
    • SortSet<T> 支持传入一个自定义的大小比较器,该比较器返回一个int值;不能指定起始容量
  9. 其他
    Comparer 属性:SortSet 可以获取大小比较器;HashSet 获取一个相等比较器

    1.4 Dictionary 字典

    Dictionary 字典,正如它的名称一样,Dictionary 需要指定两个类型,一个作为索引键,一个作为数据值。就像字典一样,每一个词条内容都只有一个字词索引,但可以出现同义词一样。当然,作为我博大精深的中文会出现同字不同音的词组,但是一旦把音、字组合起来作为索引,那还是只会出现一个词条。
    所以 Dictionary的使用方式也跟字典一样,通过索引访问和操作数据。

    1.4.1 初始化

    Dictionary的初始化有如下几个方法:

    1. Dictionary<string, int> dict = new Dictionary<string, int>();// 键是字符串,值是int类型
    2. Dictionary<string,int> dict1 = new Dictionary<string, int>(10);// 指定初始容量是10
    3. Dictionary<string,int> dict2 = new Dictionary<string, int>()
    4. {
    5. {"1",1},
    6. {"2",2}
    7. };
    8. // 在大括号标记中 通过 {key,value}的写法创建一个字典对象,并包含这些键值对
    9. // 传入一个字典对象,以传入的对象为基础创建一个字典
    10. Dictionary<string,int> dict3 = new Dictionary<string, int>(dict2);

    1.4.2 常用方法

  10. 添加元素

    1. Dictionary<string, int> dict = new Dictionary<string, int>();
    2. // 方法一
    3. dict.Add("1",2);//添加一个 键为“1”,值为2的键值对。
    4. //方法二
    5. //字典可以类似列表的形式通过下标添加或更新键对应的值,
    6. //不过与列表不同的是,字典的下标是字符串
    7. dict["2"] = 4;// 如果 dict中2有值,则更新为4,如果没有,则设置2对应的值为4
  11. 获取元素

    1. Dictionary<string, int> dict = new Dictionary<string, int>();
    2. /*
    3. 省略数据填充阶段
    4. */
    5. int value = dict["2"]; // value = 4
    6. // 如果Dictionary中不存在索引为“2”的数据
    7. // 将会抛出 System.Collections.Generic.KeyNotFoundException 异常
  12. C# 的Dictionary还有一个TryGetValue方法可以用来尝试获取,他的使用方法是这样的:—重要

    1. int obj = 0;
    2. boolean isContains = dict.TryGetValue("3", out obj);

    :::info // 方法会返回 dict是否包含键“3”的结果,如果有 obj 则存放了dict中对应的值,如果没有,则返回false且不改变 obj 的值 :::

  13. Count
    获取Dictionary里键值对的数量。

    1. int count = dict.Count;
  14. Dictionary没有LongCount属性,因为对于Dictionary存放数据需要比对Key的相等性,如果存放巨量数据将会对数据的访问和操作效率有影响。

  15. Keys
    获取Dictionary里所有的键,返回一个KeyCollection对象,不需要关心这是一个什么类型,可以简单的把它当做一个存放了键的HashSet
  16. ContainsKey()
    是否包含键:通常与获取元素一起使用,可以先判断Dictionary里是否有这个键,然后再进行后续操作。
  17. Remove()
    删除**Dictionary**中键对应的元素,删除后再次访问会报错。如果删除一个不存在的元素将返回flase。
    操作示例:

    1. Dictionary<string,int> dict = new Dictionary<string, int>();
    2. //省略赋值操作
    3. bool result = dict.Remove("2");// 如果dict里包含键为“2”的元素,则result为true,否则为false
  18. 另一种方法:

    1. int value = 0;
    2. bool result = dict.Remove("2", out value);
    3. // 如果dict 里包含键为“2”的元素,则result 为 false且value为对应的值
    4. // 并且会删除对应的键值对

    1.4.3 不常用但有用的方法

  19. ContainsValue()
    是否包含值,与ContainsKey的用法一样,只不过遍历的是值;用处不大。

  20. Values
    获取值的集合类似与KeyValues

    2. 传统集合(非泛型)

    C#的传统集合基本都存放在System.Collections命名空间里,详细的可以查看微软官方文档。这个命名空间里的集合类使用都不多,不过C#的集合体系的接口规范都是在这个里面定义的。

    2.1 常见类介绍

  21. ArrayList List的非泛型版,与List操作方法一致,不过返回值是Object类型

  22. SortedList 一个排序的键值对集合,我没用过,不过官方给了如下示例:

    1. using System;
    2. using System.Collections;
    3. public class SamplesSortedList
    4. {
    5. public static void Main()
    6. {
    7. // Creates and initializes a new SortedList.
    8. SortedList mySL = new SortedList();
    9. mySL.Add("Third", "!");
    10. mySL.Add("Second", "World");
    11. mySL.Add("First", "Hello");
    12. // Displays the properties and values of the SortedList.
    13. Console.WriteLine( "mySL" );
    14. Console.WriteLine( " Count: {0}", mySL.Count );
    15. Console.WriteLine( " Capacity: {0}", mySL.Capacity );
    16. Console.WriteLine( " Keys and Values:" );
    17. PrintKeysAndValues( mySL );
    18. }
    19. public static void PrintKeysAndValues( SortedList myList )
    20. {
    21. Console.WriteLine( "\t-KEY-\t-VALUE-" );
    22. for ( int i = 0; i < myList.Count; i++ )
    23. {
    24. Console.WriteLine( "\t{0}:\t{1}", myList.GetKey(i), myList.GetByIndex(i) );
    25. }
    26. Console.WriteLine();
    27. }
    28. }

    image.png

  23. HashTable表示根据键的哈希代码进行组织的键/值对的集合。HashTable的结构类似于Dictionary但又与其不同,它的键值存储用的是Hash值。以下是官方给出的示例代码:

    1. using System;
    2. using System.Collections;
    3. class Example
    4. {
    5. public static void Main()
    6. {
    7. // Create a new hash table.
    8. //
    9. Hashtable openWith = new Hashtable();
    10. // Add some elements to the hash table. There are no
    11. // duplicate keys, but some of the values are duplicates.
    12. openWith.Add("txt", "notepad.exe");
    13. openWith.Add("bmp", "paint.exe");
    14. openWith.Add("dib", "paint.exe");
    15. openWith.Add("rtf", "wordpad.exe");
    16. // The Add method throws an exception if the new key is
    17. // already in the hash table.
    18. try
    19. {
    20. openWith.Add("txt", "winword.exe");
    21. }
    22. catch
    23. {
    24. Console.WriteLine("An element with Key = \"txt\" already exists.");
    25. }
    26. // The Item property is the default property, so you
    27. // can omit its name when accessing elements.
    28. Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]);
    29. // The default Item property can be used to change the value
    30. // associated with a key.
    31. openWith["rtf"] = "winword.exe";
    32. Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]);
    33. // If a key does not exist, setting the default Item property
    34. // for that key adds a new key/value pair.
    35. openWith["doc"] = "winword.exe";
    36. // ContainsKey can be used to test keys before inserting
    37. // them.
    38. if (!openWith.ContainsKey("ht"))
    39. {
    40. openWith.Add("ht", "hypertrm.exe");
    41. Console.WriteLine("Value added for key = \"ht\": {0}", openWith["ht"]);
    42. }
    43. // When you use foreach to enumerate hash table elements,
    44. // the elements are retrieved as KeyValuePair objects.
    45. Console.WriteLine();
    46. foreach( DictionaryEntry de in openWith )
    47. {
    48. Console.WriteLine("Key = {0}, Value = {1}", de.Key, de.Value);
    49. }
    50. // To get the values alone, use the Values property.
    51. ICollection valueColl = openWith.Values;
    52. // The elements of the ValueCollection are strongly typed
    53. // with the type that was specified for hash table values.
    54. Console.WriteLine();
    55. foreach( string s in valueColl )
    56. {
    57. Console.WriteLine("Value = {0}", s);
    58. }
    59. // To get the keys alone, use the Keys property.
    60. ICollection keyColl = openWith.Keys;
    61. // The elements of the KeyCollection are strongly typed
    62. // with the type that was specified for hash table keys.
    63. Console.WriteLine();
    64. foreach( string s in keyColl )
    65. {
    66. Console.WriteLine("Key = {0}", s);
    67. }
    68. // Use the Remove method to remove a key/value pair.
    69. Console.WriteLine("\nRemove(\"doc\")");
    70. openWith.Remove("doc");
    71. if (!openWith.ContainsKey("doc"))
    72. {
    73. Console.WriteLine("Key \"doc\" is not found.");
    74. }
    75. }
    76. }
    77. /* This code example produces the following output:
    78. An element with Key = "txt" already exists.
    79. For key = "rtf", value = wordpad.exe.
    80. For key = "rtf", value = winword.exe.
    81. Value added for key = "ht": hypertrm.exe
    82. Key = dib, Value = paint.exe
    83. Key = txt, Value = notepad.exe
    84. Key = ht, Value = hypertrm.exe
    85. Key = bmp, Value = paint.exe
    86. Key = rtf, Value = winword.exe
    87. Key = doc, Value = winword.exe
    88. Value = paint.exe
    89. Value = notepad.exe
    90. Value = hypertrm.exe
    91. Value = paint.exe
    92. Value = winword.exe
    93. Value = winword.exe
    94. Key = dib
    95. Key = txt
    96. Key = ht
    97. Key = bmp
    98. Key = rtf
    99. Key = doc
    100. Remove("doc")
    101. Key "doc" is not found.
    102. */
  24. 虽然C#框架保留了非泛型集合元素,但不建议使用非泛型集合进行开发。

    3 一些不常用的集合类

    除了之前所说的几个集合类,C#还设置了一些在开发中不常用但在特定场合很有用的集合类。

    3.1 Queue<T>Queue

    这两个类是一对的,一个是泛型类,一个是非泛型类。该类中文名称是队列,如其名,队列讲究一个先进先出,所以队列每次取元素都是从头取,存放是放到队列尾。
    操作代码如下:

  25. 加入队列 Enqueue

    1. Queue queue = new Queue();
    2. queue.Enqueue(1);
    3. queue.Enqueue("2");
    4. Queue<string> queue1 = new Queue<string>();
    5. queue1.Enqueue("stri");//
  26. 读取队首的元素读取有两种:

    • 读取但不移除元素:Peek

      1. object obj= queue.Peek();
      2. string str = queue.Peek();
    • 读取并移除元素:

      1. object obj = queue.Dequeue();
      2. string str = queue.Dequeue();
    1. Count 获取元素数量

      3.2 LinkedList<T>

      LinkedList,链表。与List不同的地方是,LinkedList的元素是LinkedListNode对象,该对象有四个属性,分别是List
      -指向列表对象,Previous指向前一个对象如果有的话,Next指向后一个对象如果有的话。所以根据元素的属性可以发现链表的工作方式,链表就像一条锁链一样,一个元素分三块,一个指向前一个元素,一个用来存放值,一个指向下一个元素,简单如下图所示:
      C# 基础知识系列- 3 集合数组 - 图3
      所以可以明显的发现LinkedList在随机插取上比一般的要快,因为它不用维护一个数组,但是在查找和坐标操作上明显要慢很多。
      LinkedList简单介绍这么多,可以看看它的一些常见操作:
  27. First 第一个元素
    获取第一个元素

  28. Last 最后一个元素
    获取最后一个元素
  29. AddAfter/AddBefore在某个节点后/在某个节点前插入数据支持以下参数列表:
    • (LinkedListNode node, T value)
    • (LinkedListNode node, LinkedListNode newNode)
      第一个参数表示要插入的节点位置,第二个表示要插入的节点/元素。第一个参数会校验是否属于该链表,如果不属于则会抛出一个异常。第二个可以是值,也可以是初始化好的节点对象。如果是节点对象,则判断是否归属其他链表,如果是其他链表抛出异常。
  30. AddFirst/AddLast
    添加元素到头或者尾,可以使用LinkedListNode或者添加值。
  31. Remove
    删除,可以传递某个节点,或者要删除的节点里存放的值。
  32. RemoveFirst/RemoveLast
    删除第一个节点,删除最后一个节点,不含参数

下面是微软官方的一些示例

  1. using System;
  2. using System.Text;
  3. using System.Collections.Generic;
  4. public class Example
  5. {
  6. public static void Main()
  7. {
  8. // Create the link list.
  9. string[] words =
  10. { "the", "fox", "jumps", "over", "the", "dog" };
  11. LinkedList<string> sentence = new LinkedList<string>(words);
  12. Display(sentence, "The linked list values:");
  13. Console.WriteLine("sentence.Contains(\"jumps\") = {0}",
  14. sentence.Contains("jumps"));
  15. // Add the word 'today' to the beginning of the linked list.
  16. sentence.AddFirst("today");
  17. Display(sentence, "Test 1: Add 'today' to beginning of the list:");
  18. // Move the first node to be the last node.
  19. LinkedListNode<string> mark1 = sentence.First;
  20. sentence.RemoveFirst();
  21. sentence.AddLast(mark1);
  22. Display(sentence, "Test 2: Move first node to be last node:");
  23. // Change the last node to 'yesterday'.
  24. sentence.RemoveLast();
  25. sentence.AddLast("yesterday");
  26. Display(sentence, "Test 3: Change the last node to 'yesterday':");
  27. // Move the last node to be the first node.
  28. mark1 = sentence.Last;
  29. sentence.RemoveLast();
  30. sentence.AddFirst(mark1);
  31. Display(sentence, "Test 4: Move last node to be first node:");
  32. // Indicate the last occurence of 'the'.
  33. sentence.RemoveFirst();
  34. LinkedListNode<string> current = sentence.FindLast("the");
  35. IndicateNode(current, "Test 5: Indicate last occurence of 'the':");
  36. // Add 'lazy' and 'old' after 'the' (the LinkedListNode named current).
  37. sentence.AddAfter(current, "old");
  38. sentence.AddAfter(current, "lazy");
  39. IndicateNode(current, "Test 6: Add 'lazy' and 'old' after 'the':");
  40. // Indicate 'fox' node.
  41. current = sentence.Find("fox");
  42. IndicateNode(current, "Test 7: Indicate the 'fox' node:");
  43. // Add 'quick' and 'brown' before 'fox':
  44. sentence.AddBefore(current, "quick");
  45. sentence.AddBefore(current, "brown");
  46. IndicateNode(current, "Test 8: Add 'quick' and 'brown' before 'fox':");
  47. // Keep a reference to the current node, 'fox',
  48. // and to the previous node in the list. Indicate the 'dog' node.
  49. mark1 = current;
  50. LinkedListNode<string> mark2 = current.Previous;
  51. current = sentence.Find("dog");
  52. IndicateNode(current, "Test 9: Indicate the 'dog' node:");
  53. // The AddBefore method throws an InvalidOperationException
  54. // if you try to add a node that already belongs to a list.
  55. Console.WriteLine("Test 10: Throw exception by adding node (fox) already in the list:");
  56. try
  57. {
  58. sentence.AddBefore(current, mark1);
  59. }
  60. catch (InvalidOperationException ex)
  61. {
  62. Console.WriteLine("Exception message: {0}", ex.Message);
  63. }
  64. Console.WriteLine();
  65. // Remove the node referred to by mark1, and then add it
  66. // before the node referred to by current.
  67. // Indicate the node referred to by current.
  68. sentence.Remove(mark1);
  69. sentence.AddBefore(current, mark1);
  70. IndicateNode(current, "Test 11: Move a referenced node (fox) before the current node (dog):");
  71. // Remove the node referred to by current.
  72. sentence.Remove(current);
  73. IndicateNode(current, "Test 12: Remove current node (dog) and attempt to indicate it:");
  74. // Add the node after the node referred to by mark2.
  75. sentence.AddAfter(mark2, current);
  76. IndicateNode(current, "Test 13: Add node removed in test 11 after a referenced node (brown):");
  77. // The Remove method finds and removes the
  78. // first node that that has the specified value.
  79. sentence.Remove("old");
  80. Display(sentence, "Test 14: Remove node that has the value 'old':");
  81. // When the linked list is cast to ICollection(Of String),
  82. // the Add method adds a node to the end of the list.
  83. sentence.RemoveLast();
  84. ICollection<string> icoll = sentence;
  85. icoll.Add("rhinoceros");
  86. Display(sentence, "Test 15: Remove last node, cast to ICollection, and add 'rhinoceros':");
  87. Console.WriteLine("Test 16: Copy the list to an array:");
  88. // Create an array with the same number of
  89. // elements as the inked list.
  90. string[] sArray = new string[sentence.Count];
  91. sentence.CopyTo(sArray, 0);
  92. foreach (string s in sArray)
  93. {
  94. Console.WriteLine(s);
  95. }
  96. // Release all the nodes.
  97. sentence.Clear();
  98. Console.WriteLine();
  99. Console.WriteLine("Test 17: Clear linked list. Contains 'jumps' = {0}",
  100. sentence.Contains("jumps"));
  101. Console.ReadLine();
  102. }
  103. private static void Display(LinkedList<string> words, string test)
  104. {
  105. Console.WriteLine(test);
  106. foreach (string word in words)
  107. {
  108. Console.Write(word + " ");
  109. }
  110. Console.WriteLine();
  111. Console.WriteLine();
  112. }
  113. private static void IndicateNode(LinkedListNode<string> node, string test)
  114. {
  115. Console.WriteLine(test);
  116. if (node.List == null)
  117. {
  118. Console.WriteLine("Node '{0}' is not in the list.\n",
  119. node.Value);
  120. return;
  121. }
  122. StringBuilder result = new StringBuilder("(" + node.Value + ")");
  123. LinkedListNode<string> nodeP = node.Previous;
  124. while (nodeP != null)
  125. {
  126. result.Insert(0, nodeP.Value + " ");
  127. nodeP = nodeP.Previous;
  128. }
  129. node = node.Next;
  130. while (node != null)
  131. {
  132. result.Append(" " + node.Value);
  133. node = node.Next;
  134. }
  135. Console.WriteLine(result);
  136. Console.WriteLine();
  137. }
  138. }
  139. //This code example produces the following output:
  140. //
  141. //The linked list values:
  142. //the fox jumps over the dog
  143. //Test 1: Add 'today' to beginning of the list:
  144. //today the fox jumps over the dog
  145. //Test 2: Move first node to be last node:
  146. //the fox jumps over the dog today
  147. //Test 3: Change the last node to 'yesterday':
  148. //the fox jumps over the dog yesterday
  149. //Test 4: Move last node to be first node:
  150. //yesterday the fox jumps over the dog
  151. //Test 5: Indicate last occurence of 'the':
  152. //the fox jumps over (the) dog
  153. //Test 6: Add 'lazy' and 'old' after 'the':
  154. //the fox jumps over (the) lazy old dog
  155. //Test 7: Indicate the 'fox' node:
  156. //the (fox) jumps over the lazy old dog
  157. //Test 8: Add 'quick' and 'brown' before 'fox':
  158. //the quick brown (fox) jumps over the lazy old dog
  159. //Test 9: Indicate the 'dog' node:
  160. //the quick brown fox jumps over the lazy old (dog)
  161. //Test 10: Throw exception by adding node (fox) already in the list:
  162. //Exception message: The LinkedList node belongs a LinkedList.
  163. //Test 11: Move a referenced node (fox) before the current node (dog):
  164. //the quick brown jumps over the lazy old fox (dog)
  165. //Test 12: Remove current node (dog) and attempt to indicate it:
  166. //Node 'dog' is not in the list.
  167. //Test 13: Add node removed in test 11 after a referenced node (brown):
  168. //the quick brown (dog) jumps over the lazy old fox
  169. //Test 14: Remove node that has the value 'old':
  170. //the quick brown dog jumps over the lazy fox
  171. //Test 15: Remove last node, cast to ICollection, and add 'rhinoceros':
  172. //the quick brown dog jumps over the lazy rhinoceros
  173. //Test 16: Copy the list to an array:
  174. //the
  175. //quick
  176. //brown
  177. //dog
  178. //jumps
  179. //over
  180. //the
  181. //lazy
  182. //rhinoceros
  183. //Test 17: Clear linked list. Contains 'jumps' = False
  184. //

3.3 Stack<T>Stack

Stack广泛的翻译是栈,是一种后进先出的集合。在一些特殊场景里,使用十分广泛。
Stack有两个很重要的方法PopPush,出/进。Pop 获取最后一个元素,并退出栈,Push 向栈推入一个元素。
具体可以参照官方文档

4 集合相关命名空间

C# 的集合还有其他的一些命名空间里藏着宝贝,不过在实际开发中使用频率并不大,可以按需查看。

4.1 System.Collections.Concurrent 线程安全

这个命名空间,提供了一系列线程安全的集合类,当出现多线程操作集合的时候,应当使用这个命名空间的集合。名称和常用的类是一一对应的,不过只提供了ConcurrentDictionary<TKey,TValue>ConcurrentQueue<T>ConcurrentStack<T>等几个集合类。具体可以查看官方文档

4.2 System.Collections.Immutable 不可变集合

命名空间包含用于定义不可变集合的接口和类,如果需要使用这个命名空间,则需要使用NuGet下载。

  • 共享集合,使其使用者可以确保集合永远不会发生更改。
  • 提供多线程应用程序中的隐式线程安全(无需锁来访问集合)。
  • 遵循函数编程做法。
  • 在枚举过程中修改集合,同时确保该原始集合不会更改。

  • 本文作者:GeekPower - Felix Sun
  • 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!