C#/.NET framework 提供了很多很有意思的集合类,数组、列表、链表、Set、字典等一系列的类。其中数组是语言的一部分,个人认为严格意义上不属于集合类这一部分。C#开发中常用的集合有数组、 List类、Set接口、Dictionary类、Queue类、linkedList类等,其他的出镜率不高。 与其他(java)语言不同的一点是,C#的List是类,而不是接口,接口是IList,但这个接口意义不大,在使用IList的时候更多的倾向于使用IEnumerable,这主要是因为IEnumerable 有 Linq的支持再者两者的方法基本一致,能用IList的地方基本都可以用IEnumerable。
1.1 Array 数组
数组的初始化需要指定大小,可以显示指定或者隐式的指定。
// 显示指定类型与大小,具体的元素后续赋值string[] strArr = new string[10]; //指定类型同时给元素赋值,具体大小由编译器自动推断string[] strArr1 = new string[]{"1","2","3","4","5","6","7","8","9","10"};// 类型和大小都由编译器进行推断string[] strArr2 = new []{"1","2","3","4","5","6","7","8","9","10"};1.1.2 常用方法
- 访问和赋值 数组可以通过下标访问数组中的元素,下标从0开始,表示0位。代码如下:
string item0 = strArr[0]; //取出 "1"string item2 = strArr[2]; // 取出 "3"strArr[0] = "3"; // strArr = {"3","2","3","4","5","6","7","8","9","10"}
- 获取长度
int length = strArr.Length;// 获取一个整型的长度//获取一个长整型的长度,对于一个非常大的数组且长度可能会超过int的最大值long longLength = strArr.LongLength;
- 循环迭代
// 普通for 循环for(int i = 0;i < strArr.Length;i++){ string it = strArr[i];}// foreach 循环foreach(string it in strArr){ // 依次循环,不需要下标,操作更快一点}1.1.3 不常用但有用的方法
- CopyTo 复制到 public void CopyTo(Array array, int index);
public void CopyTo(Array array, long index);参数说明: array 需要复制到的数组,index 目标数组的起始下标方法说明:将 源数组的元素依次复制到 array从index下标开始的位置 string[] strArr1 = new string[]{"1","2","3","4","5","6","7","8","9","10"};
string[] strArr3 = new string[10];
strArr1.CopyTo(strArr3, 0); //strArr3 = {"1","2","3","4",'5","6","7","8","9","10"}值得注意的是strArr3的长度不能 小于 index + strArr1.Length- Sort 排序这个方法不是数组对象的方法,而是 Array 提供的一个静态方法。 int[] arr1 = new[] {1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};
Array.Sort(arr1);//0,1,2,3,4,5,6,9,12,13,18,28,44,81,92,98值得注意的是,该方法是直接对数组进行操作,所以不会返回新的数组。- ToList 转成 List顾名思义,将Array对象转成List对象。这里需要额外注意的是,转换成的List是不可改变长度的。
- Clone() 获得一个浅拷贝的数组对象获取该对象的一个浅拷贝数组对象。
List列表为一个泛型类,泛型表示<T>,其中T表示列表中存放的元素类型,T代表C#中可实例化的类型。关于泛型的具体描述以后介绍,现在回过头来继续介绍列表。列表内部持有一个数组对象,列表有两个私有变量:一个是列表容量,即内部数组的大小;另一个是存放的元素数量,通过Count获取。 List列表通过元素数量实现了Add和Remove 的操作,列表对象操作引发元素数量变动时都会导致对容量的重新计算,如果现有容量不满足后续操作需要的话,将会对现有数组进行扩充。
1.2.1 初始化
List<string> list = new List<string>();// 初始化一个空的列表List<string> list1 = new List<string>{"12", "2"};//初始化一个包含两个元素的列表list1 = new List<string>(100);//初始化一个空的列表,并指定list的初始容量为100list = new List<string>(list1);// 使用一个List/Array 初始化一个列表1.2.2 常用方法
- Count 或LongCount获取元素的数量Count 表示获取一个int类型的的数量值,LongCount表示获取一个long类型的数量值。通常情况下两者返回的结果是一致的,但是如果列表中元素的数量超过了int允许的最大返回直接使用 Count获取将会出现数据溢出的问题,这时候就需要LongCount了。
- 访问元素/修改元素C#的列表操作单个元素很简单 ,与数组的操作方式完全一样。 string str = list1[0];//获取 list1 的第一个元素,即下标为0的元素
list1[2] = "233"; // 将 list1 的第三个元素设置为“233” ,即下标为2 的元素,这里假设list1有至少三个元素需要注意的地方是,如果给定的下标超过了List对象的索引值范围会报ArgumentOutOfRangeException。判断方法就是 下标>= Count,如果满足就会越界。- Add或AddRange 添加到列表最后将元素添加到List的末尾,Add添加一个,AddRange添加一组,支持数组、列表。 List<string> list = new List<string>();// 初始化一个空的列表
list.Add("12");//list = {"12"}
List<string> list1 = new List<string>{"14", "2"};
list.AddRange(list1);// list = {"12","14","2"}- Insert(int index, T item)或InsertRange(int index,IEnumerable<T> items) 插入
- Insert(int index,T item) 在 index 下标处插入一个元素,该下标以及该下标以后的元素依次后移
- InsertRange(int index,IEnumerable<T> items) 在index下标处插入一组元素,该下标以及之后的元素依次后移示例:List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};
arr1.Insert(3,37);// arr1 = 1,9,28,37,5,3,6,0,12,44,98,4,2,13,18,81,92 下标为3的元素变成了37,之后的元素依次后移了List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};
List<int> arr2 = new List<int>{2,3,4,5};
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的元素发生了变化- Contains(T item) 是否包含 返回一个Boolean类型的结果,如果包含则返回true,如果不包含则返回false List<int> arr2 = new List<int>{2,3,4,5};
arr2.Contains(8);//false
arr2.Contains(3);//true- Remove(T item) 删除指定元素 List<int> arr2 = new List<int>{2,3,4,5};
arr2.Remove(3);// arr2 = 2,4,5
arr2.Remove(6);//arr2 = 2,4,5值得注意的是,如果删除一个不存在的元素时,不会报错,列表也不会发生任何改变。- RemoveAt(int index) 删除位于下标的元素 List<int> arr2 = new List<int>{2,3,4,5};
arr2.RemoveAt(1);//arr2 = 2,4,5如果移除的下标超过了列表的最后一个元素的下标将会抛出异常- RemoveRane(IEnumerable<T> items) 删除一组元素与Remove(T item)一致,如果要删除的元素不在列表中,则列表元素不会发生变化。 List<int> arr1 = new List<int>{1, 9, 28, 5, 3, 6, 0, 12, 44, 98, 4, 2, 13, 18, 81, 92};
List<int> arr2 = new List<int>{2,3,4,5};
arr1.RemoveRange(arr2);- GetRange(int index,int count)从列表中获取一个子列表,从index开始,获取count个元素,如果源列表中从index开始剩余的元素不足count个将会报错。
1.2.3 不常用但有用的方法
- Clear()删除所有元素将列表清空,调用方法之后,列表中将不包含任何元素
- Reverse() 调转顺序将列表按照从尾到头的顺序进行排列
- IndexOf(T item) 查找下标查找元素在列表中的下标,如果没找到元素,则返回-1
- Sort()排序对列表进行排序,调用方法后,会按照默认排序方法返回一个排序结果
1.3 Set 集合
Dictionary 字典,正如它的名称一样,Dictionary 需要指定两个类型,一个作为索引键,一个作为数据值。就像字典一样,每一个词条内容都只有一个字词索引,但可以出现同义词一样。当然,作为我博大精深的中文会出现同字不同音的词组,但是一旦把音、字组合起来作为索引,那还是只会出现一个词条。 所以 Dictionary的使用方式也跟字典一样,通过索引访问和操作数据。
1.4.1 初始化
C#的传统集合基本都存放在System.Collections命名空间里,详细的可以查看微软官方文档。这个命名空间里的集合类使用都不多,不过C#的集合体系的接口规范都是在这个里面定义的。
2.1 常见类介绍
- ArrayList List的非泛型版,与List操作方法一致,不过返回值是Object类型
- SortedList 一个排序的键值对集合,我没用过,不过官方给了如下示例: using System;
using System.Collections;
public class SamplesSortedList {
public static void Main() {
// Creates and initializes a new SortedList.
SortedList mySL = new SortedList();
mySL.Add("Third", "!");
mySL.Add("Second", "World");
mySL.Add("First", "Hello");
// Displays the properties and values of the SortedList.
Console.WriteLine( "mySL" );
Console.WriteLine( " Count: {0}", mySL.Count );
Console.WriteLine( " Capacity: {0}", mySL.Capacity );
Console.WriteLine( " Keys and Values:" );
PrintKeysAndValues( mySL );
}
public static void PrintKeysAndValues( SortedList myList ) {
Console.WriteLine( "\t-KEY-\t-VALUE-" );
for ( int i = 0; i < myList.Count; i++ ) {
Console.WriteLine( "\t{0}:\t{1}", myList.GetKey(i), myList.GetByIndex(i) );
}
Console.WriteLine();
}
}- HashTable表示根据键的哈希代码进行组织的键/值对的集合。HashTable的结构类似于Dictionary但又与其不同,它的键值存储用的是Hash值。以下是官方给出的示例代码: using System;
using System.Collections;
class Example {
public static void Main() {
// Create a new hash table.
//
Hashtable openWith = new Hashtable();
// Add some elements to the hash table. There are no
// duplicate keys, but some of the values are duplicates.
openWith.Add("txt", "notepad.exe");
openWith.Add("bmp", "paint.exe");
openWith.Add("dib", "paint.exe");
openWith.Add("rtf", "wordpad.exe");
// The Add method throws an exception if the new key is
// already in the hash table.
try
{
openWith.Add("txt", "winword.exe");
}
catch
{
Console.WriteLine("An element with Key = \"txt\" already exists.");
}
// The Item property is the default property, so you
// can omit its name when accessing elements.
Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]);
// The default Item property can be used to change the value
// associated with a key.
openWith["rtf"] = "winword.exe";
Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]);
// If a key does not exist, setting the default Item property
// for that key adds a new key/value pair.
openWith["doc"] = "winword.exe";
// ContainsKey can be used to test keys before inserting
// them.
if (!openWith.ContainsKey("ht"))
{
openWith.Add("ht", "hypertrm.exe");
Console.WriteLine("Value added for key = \"ht\": {0}", openWith["ht"]);
}
// When you use foreach to enumerate hash table elements,
// the elements are retrieved as KeyValuePair objects.
Console.WriteLine();
foreach( DictionaryEntry de in openWith )
{
Console.WriteLine("Key = {0}, Value = {1}", de.Key, de.Value);
}
// To get the values alone, use the Values property.
ICollection valueColl = openWith.Values;
// The elements of the ValueCollection are strongly typed
// with the type that was specified for hash table values.
Console.WriteLine();
foreach( string s in valueColl )
{
Console.WriteLine("Value = {0}", s);
}
// To get the keys alone, use the Keys property.
ICollection keyColl = openWith.Keys;
// The elements of the KeyCollection are strongly typed
// with the type that was specified for hash table keys.
Console.WriteLine();
foreach( string s in keyColl )
{
Console.WriteLine("Key = {0}", s);
}
// Use the Remove method to remove a key/value pair.
Console.WriteLine("\nRemove(\"doc\")");
openWith.Remove("doc");
if (!openWith.ContainsKey("doc"))
{
Console.WriteLine("Key \"doc\" is not found.");
}
}
}
虽然C#框架保留了非泛型集合元素,但不建议使用非泛型集合进行开发。3 一些不常用的集合类
这两个类是一对的,一个是泛型类,一个是非泛型类。该类中文名称是队列,如其名,队列讲究一个先进先出,所以队列每次取元素都是从头取,存放是放到队列尾。 操作代码如下:
- 加入队列 Queue queue = new Queue();
queue.Enqueue(1);
queue.Enqueue("2");
Queue<string> queue1 = new Queue<string>();
queue1.Enqueue("stri");//- 读取队首的元素 读取有两种:
- 读取但不移除元素: object obj= queue.Peek();
string str = queue.Peek();- 读取并移除元素: object obj = queue.Dequeue();
string str = queue.Dequeue();- Count 获取元素数量
3.2 linkedList<T>
所以可以明显的发现linkedList在随机插取上比一般的要快,因为它不用维护一个数组,但是在查找和坐标操作上明显要慢很多。 linkedList简单介绍这么多,可以看看它的一些常见操作:
- First 第一个元素获取第一个元素
- Last 最后一个元素获取最后一个元素
- AddAfter/AddBefore 在某个节点后/在某个节点前插入数据 支持以下参数列表:
- (linkedListNode node, T value)
- (linkedListNode node, linkedListNode newNode)第一个参数表示要插入的节点位置,第二个表示要插入的节点/元素。第一个参数会校验是否属于该链表,如果不属于则会抛出一个异常。第二个可以是值,也可以是初始化好的节点对象。如果是节点对象,则判断是否归属其他链表,如果是其他链表抛出异常。
- AddFirst/AddLast添加元素到头或者尾,可以使用linkedListNode或者添加值。
- Remove删除,可以传递某个节点,或者要删除的节点里存放的值。
- RemoveFirst/RemoveLast 删除第一个节点,删除最后一个节点,不含参数
Stack广泛的翻译是栈,是一种后进先出的集合。在一些特殊场景里,使用十分广泛。 Stack有两个很重要的方法Pop 和Push,出/进。Pop 获取最后一个元素,并退出栈,Push 向栈推入一个元素。 具体可以参照官方文档
4 集合相关命名空间
这个命名空间,提供了一系列线程安全的集合类,当出现多线程操作集合的时候,应当使用这个命名空间的集合。名称和常用的类是一一对应的,不过只提供了ConcurrentDictionary<TKey,TValue>、ConcurrentQueue<T>、ConcurrentStack<T>等几个集合类。具体可以查看官方文档
4.2 System.Collections.Immutable 不可变集合
argumentoutofrangeexception(C# 基础知识系列- 3 集合数组)
