欢迎来真孝善网,为您提供真孝善正能量书籍故事!

深入解析C#基础系列:高效掌握C#集合应用

时间:11-13 神话故事 提交错误

本篇文章给大家谈谈深入解析C#基础系列:高效掌握C#集合应用,以及对应的知识点,文章可能有点长,但是希望大家可以阅读完,增长自己的知识,最重要的是希望对各位有所帮助,可以解决了您的问题,不要忘了收藏本站喔。

公共类List: System.Collections.Generic.ICollection,

System.Collections.Generic.IEnumerable、System.Collections.Generic.IList、

System.Collections.Generic.IReadOnlyCollection、System.Collections.Generic.IReadOnlyList、

System.Collections.IList、System.Collections.IEnumerable、System.Collections.ICollection

创建列表

您可以调用默认构造函数来创建列表对象。

ListintList=new List();使用构造函数创建一个空列表。当向列表添加元素时,列表的容量将扩展至容纳4 个元素。当添加第五个元素时,列表的容量将会增加。被重置为包含8 个元素。如果8个元素不够,列表的容量将设置为16个元素。每次超过现有容量时,列表容量将重置为原来的2倍。

使用Capacity属性获取列表的容量。下面通过一个例子来说明添加元素后Capacity的值如何变化。

ListintList=new List();

//获取初始容量大小

Console.WriteLine("初始容量大小:" + intList.Capacity);

intList.Add(1);

Console.WriteLine($"添加元素后容量为:{intList.Capacity}");

//获取或设置内部数据结构在不调整大小的情况下可以容纳的元素总数

intList.Capacity=5;

Console.WriteLine("设置指定容量大小为5后:" + intList.Capacity);

intList.AddRange(new[] { 2, 3, 4, 5, 6 });

Console.WriteLine($"添加{intList.Count}个元素后,容量为:{intList.Capacity}");以上输出结果为:

初始容量大小:0

添加一个元素后,容量大小为:4

将指定容量大小设置为5:5后

添加6个元素后,容量大小为:10

如果向列表添加元素后存在多余的容量,可以调用TrimExcess()方法删除不需要的容量。

注意:如果未使用的容量小于总容量的10%,则列表不会调整大小。

然后上面的例子执行下面的代码:

Console.WriteLine($"原元素数量为:{intList.Count},容量为:" + intList.Capacity);

intList.TrimExcess();

Console.WriteLine("调用TrimExcess()方法后容量为:" + intList.Capacity);

//重新调整容量,未使用的容量小于总容量的10%

intList.Capacity=7;

intList.TrimExcess();

Console.WriteLine($"最终元素个数为:{intList.Count},容量为:" + intList.Capacity);输出结果为:

原元素数量为:6,容量为:10

调用TrimExcess()方法后容量为:6

最终元素数量为:6,容量为:7

初始化集合并设定值

intList=new List() { 1, 2, 3 };

intList=新列表{ 4, 5, 6 };

添加或插入元素

使用Add() 方法向列表添加一个元素,使用AddRange() 方法一次性向集合添加多个元素。使用Insert() 方法在指定位置插入元素:

intList.Add(7);

intList.AddRange(new int [] { 7, 8, 9 });

intList.Insert(2, 0);

//4 5 0 6 7 7 8 9

访问元素

所有实现IList和IList接口的类都提供了索引器,因此可以使用索引下标来访问指定索引位置的元素。索引下标从0开始。

Console.Write(intList[2]);

0由于List集合类实现了IEnumerate接口,因此可以使用foreach语句来遍历集合中的元素。

删除元素

使用RemoveAt()方法删除指定索引位置的元素,使用Remove()方法删除指定元素。可以使用RemoveRange()方法从集合中删除多个元素。使用RemoveAll()方法删除集合中的所有元素。

注意:建议使用RemoveAt()方法按索引删除元素,因为它比Remove()方法执行得更快。 Remove() 方法将首先搜索集合中的元素。在搜索过程中,会调用Equals() 方法,然后使用IndexOf() 方法获取元素的索引,然后使用该索引删除该元素。

intList.RemoveAt(2);//删除索引2处的元素

intList.Remove(7);//删除元素7

intList.RemoveRange(4, 2);//删除索引4及之后的2个元素

intList.RemoveAll(a=a 5); //删除值大于5的元素

搜索元素

可以通过索引或元素本身来搜索元素。可以使用的方法有:IndexOf()、LastIndexOf()、FindIndex()、FindLastIndex()、Find()、FindLast()等。要确定元素是否存在,请使用Exists() 方法。除了这些方法之外,实际应用中还包括Linq可以使用的方法。具体使用请查看官方文档。

排序

List 类可以使用Sort() 方法对元素进行排序。 Sort() 方法具有以下重载方法:

公共无效排序(int索引,int计数,IComparercomparer);

public void Sort(比较比较);

公共无效排序();

公共无效排序(IComparercomparer);只有集合中的元素实现了IComparable 接口,才能使用不带参数的Sort() 方法。

如果需要按默认不支持的元素类型进行排序,则需要使用其他重载方法,例如传递实现IComparer 接口的对象。

下面用一个具体的例子来说明:

公开课Racer : IComparable{

公共int Id { 得到; }

公共字符串名字{获取;放; }

公共字符串姓氏{获取;放; }

公共字符串国家{获取;放; }

公共int 胜利{ 得到;放; }

//实现接口中的方法

公共int CompareTo(赛车其他)

{

int Compare=LastName?CompareTo(other?LastName) ? -1;

如果(比较==0)

{

返回名字?CompareTo(其他?名字) ? -1;

}

返回比较;

}

//定义构造函数

public Racer(int id, 字符串firstName, 字符串lastName, 字符串country, int 获胜)

{

这个.Id=id;

this.名字=名字;

this.LastName=姓氏;

this.Country=国家;

this.Wins=胜利;

}

//定义另一个构造函数并调用上面的构造函数

public Racer(int id, 字符串名字, 字符串姓氏, 字符串国家)

: 这个(id,名字,姓氏,国家/地区,wins: 0){}

//重写对象的Tostring()方法

公共覆盖字符串ToString()

{

返回$"{名字} {姓氏}";

}

}上面的类直接实现了IComparable泛型接口,因此可以直接使用不带参数的Sort()方法进行排序。排序基础基于重写的CompareTo() 方法。

var racers=新列表{

new Racer(1,"zhang","bsan","中国"),

new Racer(3,"li","asi","中国"),

new Racer(2,"wang","dwu","中国")

};

赛车手.Sort();执行上面的语句,输出会按照LastName排序,依次为li asi、zhang bsan、wang dwu。

扩展上面的示例以通过传递实现IComparer 接口的对象进行排序。如下:

公共类RacerComparer : IComparer{

//定义一个枚举,可以通过类名.枚举名直接访问

公共枚举比较类型

{

名,

姓,

国家,

胜利

}

//定义枚举变量

私有比较类型_compareType;

//定义构造函数并对外指定枚举类型

公共RacerComparer(CompareType 比较类型)

{

_compareType=比较类型;

}

//重写接口方法

公共int 比较(赛车x,赛车y)

{

if (x==null y==null) 返回0;

如果(x==null)返回-1;

if (y==null) 返回1;

整数结果;

开关(_compareType)

{

案例CompareType.FirstName:

return string.Compare(x.FirstName, y.FirstName);

案例CompareType.LastName:

return string.Compare(x.LastName, y.LastName);

案例CompareType.Country:

结果=string.Compare(x.Country, y.Country);

如果(结果==0)

return string.Compare(x.LastName, y.LastName);

否则返回结果;

案例CompareType.Wins:

返回x.Wins.CompareTo(y.Wins);

默认:

throw new ArgumentException("无效的比较类型");

}

}

}上面的类RacerComparer实现了泛型接口IComparer,其中泛型类型为Racer(IComparer接口中的泛型类型T应该是要排序的元素类型),并重写了Compare()方法,因此可以调用Srot(IComparer) 方法进行排序。

var racers=新列表{

new Racer(1,"zhang","bsan","中国"),

new Racer(3,"li","asi","中国"),

new Racer(2,"wang","dwu","中国")

};

racers.Sort(new RacerComparer(RacerComparer.CompareType.FirstName));

//将按照名字排序。也可以调用Sort(Comparisoncomparison)进行排序。 Comparison是一个泛型委托,其定义如下:

公共委托int Comparison(T x, T y);需要传入两个T类型的参数,返回类型为int。如果参数值相等,该方法返回0;如果第一个参数小于第二个参数,则返回小于0 的值;否则,返回大于0 的值。

例如,如果示例中按Id排序,则可以使用以下方法调用:

//由于是委托,所以这里可以使用lambda表达式

racers.Sort((r1, r2)=r1.Id.CompareTo(r2.Id));上面会按照Id升序排序。

如果使用Sort()进行排序,可以调用Reverse()方法对整个集合进行反向排序。

只读集合

可以调用List集合的AsReadOnly()方法返回ReadOnlyCollection类型的对象。 ReadOnlyCollection类实现的接口与List集合相同。此外,它还实现了IReadOnlyCollection和IReadOnlyList接口。由于这些接口的成员,集合不能被修改。修改集合的所有方法和属性都会抛出NotSupportedException。

公共类ReadOnlyCollection: IList、ICollection、IEnumerable、

IEnumerable、IList、ICollection、IReadOnlyList、IReadOnlyCollection

队列Queue

队列是一个集合,其元素按先进先出(先进先出、先进先出)方式处理。最先放入队列的元素将首先被读取。该队列是使用System.Collections.Generic 命名空间中的泛型类Queue 实现的。其声明如下:

[System.Runtime.InteropServices.ComVisible(假)]

公共类Queue: System.Collections.Generic.IEnumerable,

System.Collections.Generic.IReadOnlyCollection、System.Collections.ICollection 由于Queue没有实现ICollection泛型接口,因此不能使用该接口中定义的Add()和Remove()方法来操作元素。另外,由于Queue没有实现IList泛型接口,因此无法使用索引下标访问队列。

队列常用方法及属性说明:

Dequeue():删除并返回队列开头的元素。如果队列中没有元素,则调用此方法时将抛出InvalidOperationException 类型的异常。

Enqueue(T):将元素添加到队列末尾。

Peek():返回但不删除队列开头的元素。

TrimExcess():如果队列元素数量小于当前容量的90%,则将容量设置为队列的实际元素数量。

Count:获取队列中元素的数量

您可以使用默认构造函数创建空队列,也可以使用构造函数指定容量。当向队列添加元素时,如果没有指定容量,则会与List类类似,队列的容量总是会根据需要加倍,从而包含4、8、16、32个元素等。

下面将通过一个复杂的例子来说明队列是如何使用的。首先定义一个简单的类:

公开课文档

{

公共字符串标题{获取; }

公共字符串内容{获取; }

公共文档(字符串标题,字符串内容)

{

this.Title=标题;

this.内容=内容;

}

}然后对该类进行队列写入和读取操作:

公共类文档管理器

{

私有只读Queue_documentQueue=new Queue();

//添加元素到队列中

公共无效AddDocument(文档doc)

{

//因为后面会用到多线程,为了避免死锁,所以对lock语句进行了限制。

锁(这个)

{

_documentQueue.Enqueue(doc);

}

}

//获取队列中的元素

公共文档GetDocument()

{

文档doc=null;

锁(这个)

{

doc=_documentQueue.Dequeue();

}

返回文档;

}

//队列中是否还有尚未读取的元素?

公共bool IsDocumentAvailable=_documentQueue.Count 0;

}然后定义一个对该类进行操作的开放类:

公共类流程文档

{

私有文档管理器_documentManager;

protectedProcessDocuments(DocumentManager dm)

{

_documentManager=dm ?抛出新的ArgumentNullException(nameof(dm));

}

受保护的异步任务Run()

{

而(真)

{

如果(_documentManager.IsDocumentAvailable)

{

文档doc=_documentManager.GetDocument();

Console.WriteLine(doc.Title + ":" + doc.Content);

}

//显式指定间隔时间,就会在这里等待执行该方法外的代码。

等待Task.Delay(new Random().Next(1000));

}

}

//调用对外开放的方法

公共静态无效开始(DocumentManager dm)

{

//开始一个新任务

Task.Run(new ProcessDocuments(dm).Run);

}

}调用代码如下:

公共静态无效运行()

{

var dm=new DocumentManager();

ProcessDocuments.Start(dm);

for (int i=0; i 1000; i++)

{

var doc=new Document("Doc_" + i, "Content_" + i);

dm.AddDocument(doc);

Console.WriteLine("添加了document:Doc_" + i);

System.Threading.Thread.Sleep(new Random().Next(1000));

}

}

栈Stack

堆栈是后进先出(Lastin、Firstout、LIFO)的集合。最后添加到堆栈的元素将首先被读取。它与队列非常相似,只是读取元素的方法不同。NET中栈的声明如下:

[System.Runtime.InteropServices.ComVisible(假)]

公共类Stack: System.Collections.Generic.IEnumerable,

System.Collections.Generic.IReadOnlyCollection、System.Collections.ICollection常用的方法和属性有:

Pop():移除并返回栈顶元素。如果堆栈上没有元素,调用此方法将抛出InvalidOperationException。

Push(T): 在堆栈顶部插入一个元素。

Peek():返回但不删除栈顶元素。

Contains(T):判断一个元素是否在栈上。

计数:返回堆栈中元素的数量。

下面用一个简单的例子来说明栈的相关操作:

var mystacks=new Stack();

mystacks.Push(1);

mystacks.Push(2);

mystacks.Push(3);

foreach(mystacks 中的var num)

{

Console.Write(num+"t"); //将输出:3 2 1

}

Console.WriteLine();

while (mystacks.Count 0)

{

Console.Write(mystacks.Pop() + "t");

}

链表LinkedList

LinkedList是一个双向链表,其元素指向前一个

面和后面的元素。 链表的优点是,如果将元素插入列表的中间位置,使用链表就会非常快。在往链表插入一个新的元素时,只需要修改上一个元素的Next引用和下一个元素的Previous引用,使它们的引用指向新插入的元素。【删除原因:表述不准确,Next和Previous都是获取值不能设置值】 链表的缺点是,链表的元素只能一个接一个地访问,这需要较长的时间来查找位于链表中间或尾部的元素。 LinkedList在.NET中的定义: [System.Runtime.InteropServices.ComVisible(false)] public class LinkedList: System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.ICollection, System.Runtime.Serialization.IDeserializationCallback, System.Runtime.Serialization.ISerializable操作链表时,离不开泛型类LinkedListNode,它表示LinkedList中的节点。LinkedListNode是一个独立定义的类,并不继承自LinkedList,但是链表LinkedList包含的元素节点均来自于LinkedListNode,以下为链表LinkedList部分常用方法和属性: public LinkedListNodeLast { get; } public LinkedListNodeFirst { get; } public LinkedListNodeAddAfter(LinkedListNodenode, T value); public void AddAfter(LinkedListNodenode, LinkedListNodenewNode); public LinkedListNodeAddBefore(LinkedListNodenode, T value); public void AddBefore(LinkedListNodenode, LinkedListNodenewNode); public LinkedListNodeAddFirst(T value); public void AddFirst(LinkedListNodenode); public LinkedListNodeAddLast(T value); public void AddLast(LinkedListNodenode); public LinkedListNodeFind(T value); public LinkedListNodeFindLast(T value);上述大多数都与LinkedListNode紧密相关: [System.Runtime.InteropServices.ComVisible(false)] public sealed class LinkedListNode{ public LinkedListNode(T value); public LinkedListList { get; } public LinkedListNodeNext { get; } public LinkedListNodePrevious { get; } public T Value { get; set; } }通过定义可以知道,使用LinkedListNode类,可以获得列表中的下一个元素和上一个元素。LinkedListNode定义了属性List(返回对应的LinkedList对象)、Next、Previous和Value(返回与节点相关的元素,其类型是T)。并且它提供的属性大多数都是可读不可写。 注:LinkedListNode的成员很少,几乎提供的全是读取的操作,因此实际操作元素比如添加、删除等,还是通过LinkedList的成员方法(如上述展示的方法)进行调用。 下面将使用一个完整的示例说明如何使用链表进行操作,该示例使用链表让文档按照优先级进行排序显示,并且在链表中添加新文档时,新添加的文档应该放在优先级相同的最后一个文档的后面。 首先定义一个简单的类Document_V2,它包括基本的文档信息已经优先级: public class Document_V2 { public string Title { get; private set; } public string Content { get; private set; } public byte Priority { get; private set; } public Document_V2(string title,string content,byte priority) { this.Title = title; this.Content = content; this.Priority = priority; } }接着定义一个操作该类链表的类: public class PriorityDocumentManager {

//集合LinkedList包含多个LinkedListNode类型的元素 private readonly LinkedList_documentList; //定义包含LinkedListNode类型的List集合,便于后续使用Next和Previous属性进行遍历 private readonly List>_priorityNodes; public PriorityDocumentManager() { _documentList = new LinkedList(); _priorityNodes = new List>(10); for (int i = 0; i< 10; i++) { //添加10个类型为Document_V2的空节点(节点的Value及其他属性均为null) _priorityNodes.Add(new LinkedListNode(null)); } } public void AddDocument(Document_V2 d) { if (d == null) throw new ArgumentNullException("d"); AddDocumentToPriorityNode(d, d.Priority); } private void AddDocumentToPriorityNode(Document_V2 doc, int priority) { if (priority >9 || priority< 0) { throw new ArgumentException("等级必须为0~9"); } if (_priorityNodes[priority].Value == null) { --priority; if (priority >= 0) { //递归调用该方法 AddDocumentToPriorityNode(doc, priority); } else { _documentList.AddLast(doc); _priorityNodes[doc.Priority] = _documentList.Last; } return; } else { LinkedListNodeprioNode = _priorityNodes[priority]; //判断优先级是否相同 if (priority == doc.Priority) { _documentList.AddAfter(prioNode, doc); _priorityNodes[doc.Priority] = prioNode.Next; } else { LinkedListNodefirstPrioNode = prioNode; //循环遍历所有链接节点 while (firstPrioNode.Previous != null && firstPrioNode.Previous.Value.Priority == prioNode.Value.Priority) { firstPrioNode = prioNode.Previous; prioNode = firstPrioNode; } _documentList.AddBefore(firstPrioNode, doc); _priorityNodes[doc.Priority] = firstPrioNode.Previous; } } } public void DisplayAllNodes() { foreach (Document_V2 doc in _documentList) { Console.WriteLine($"priority:{doc.Priority},tilte:{doc.Title}"); } } public Document_V2 GetDocument() { Document_V2 doc = _documentList.First.Value; _documentList.RemoveFirst(); return doc; } }调用上述执行: public static void Run() { var pdm = new PriorityDocumentManager(); pdm.AddDocument(new Document_V2("one", "示例一", 8)); pdm.AddDocument(new Document_V2("two", "示例二", 3)); pdm.AddDocument(new Document_V2("three", "示例三", 4)); pdm.AddDocument(new Document_V2("for", "示例四", 8)); pdm.AddDocument(new Document_V2("five", "示例五", 1)); pdm.AddDocument(new Document_V2("six", "示例六", 9)); pdm.AddDocument(new Document_V2("seven", "示例七", 1)); pdm.AddDocument(new Document_V2("eight", "示例八", 1)); pdm.DisplayAllNodes(); }输出结果: priority:9,title:six priority:8,title:one priority:8,title:for priority:4,title:three priority:3,title:two priority:1,title:five priority:1,title:seven priority:1,title:eight

有序列表SortedList

使用SortedList类可以基于键对集合排序。 使用一个简单的示例对其进行操作说明: var mysortedlist = new SortedList(); mysortedlist.Add("one", "一"); mysortedlist.Add("two", "二"); mysortedlist.Add("three", "三"); mysortedlist.Add("four", "四"); //还可以使用索引的形式添加元素,索引参数是键 mysortedlist["five"] = "五"; //修改值 mysortedlist["three"] = "3"; foreach (var item in mysortedlist) { Console.WriteLine($"{item.Key}:{item.Value}"); }上述将会按照键自动的进行排序显示,显示结果: five:五 four:四 one:一 three:3 two:二

字典Dictionary

字典表示一种非常复杂的数据结构,由键和值组成,这种数据结构允许按照某个键来访问元素。字典也被称为映射或散列表。

字典初始化

之前只能先实例一个字典对象,然后使用Add()方法添加元素,在C#6定义了一个新的语法,可以在声明的同时初始化字典,例如: var dic = new Dictionary() { //第一元素的键是100 [100] = "第一个元素", [200] = "第二个元素" };

键的类型

字典类要确定元素的位置,它就要调用GetHashCode()方法,GetHashCode()方法返回的int由字典用于计算在对应位置放置元素的索引,因此用作字典中的键的类型必须重写Object类的GetHashCode()方法。GetHashCode()方法的实现代码必须满足如下要求: 相同的对象应该总是返回相同的值。不同的对象可以返回相同的值。不能抛出异常。至少使用一个实例字段。散列代码(调用GetHashCode方法得到的值)在对象的生存期中不发生变化。除了必须要满足的要求外,最好还满足如下要求: 它应该执行的比较快,计算开销不大。散列代码值应平均分布在int可以存储的整个数字范围内。注意:字典的性能 取决于GetHashCode()方法的实现代码。 通过GetHashCode得到的散列代码值的范围应该尽可能的分布在int可以存储的整个数字范围内,避免两个键返回的散列代码值得到相同的索引(字典中的索引包含一个到值的链接,一个索引项可以关联多个值,此处的索引不是指索引下标),这会降低性能,因为字典类需要寻找最近的可用空闲位置来存储第二个数据项,这需要进行一定的搜索,如果在排序时许多键都有相同的索引,这类冲突就更可能出现,所以,当计算出来的散列代码值平均分布在int.MinValue和int.MaxValue之间时,这种风险会降低到最小。 除了实现GetHashCode()方法之外,键类型还必须实现IEquatable.EQuals()方法,或重写Object类的Equals()方法。因为不同的键对象可能返回相同的散列代码,所以字典使用Equals()方法来比较键。字典检查两个键A和B是否相等,并调用A.Equals(B)方法。这说明必须确保下述条件总是成立: 如果A.Equals(B)方法返回true,则A.GetHashCode()和B.GetHashCode()方法必须总是返回相同的散列代码。 注意:如果为Equals()方法提供了重写版本,但没有提供GetHashCode()方法的重写版本,C#编译器就会显示一个编译警告。 综上所述,应用在字典中的键,必须实现或重写GetHashCode()和IEquatable.EQuals()方法。如果这两个方法都没有实现, 可以创建一个实现IEqualityComparer接口的比较器,IEqualityComparer接口定义了GetHashCode()和Equals()方法,并将传递的对象作为参数,因此可以提供与对象类型不同的实现方式。Dictionary构造函数的一个重载版本允许传递一个实现了IEqualityComparer接口的对象。如果把这个对象赋予字典,该类就用于生成散列代码并比较键。 下面通过一个示例进行说明。首先创建字典中的键将要使用到的类型: public struct EmployeeId : IEquatable{ private readonly char prefix; private readonly int number; public EmployeeId(string id) { //System.Diagnostics.Contracts.Contract.Requires(id != null); prefix = (id.ToUpper())[0]; int numLength = id.Length - 1; try { number = int.Parse(id.Substring(1, numLength >6 ? 6 : numLength)); } catch (Exception) { throw new Exception("EmployeeId格式错误"); } } public override string ToString() { return prefix.ToString() + $"{number,6:000000}"; } //重写GetHashCode()方法 public override int GetHashCode() { //此条语句只是为了使得到的值能够尽可能的平均到int范围 //将数字向左移动16位,再与原数字进行异或操作,得到的结果乘以16进制数15051505 return (number ^ number<< 16) * 0x15051505; } //必须实现Equals()方法 public bool Equals(EmployeeId other) { //return (_prefix == other?._prefix && _number == other?._number); return (prefix == other.prefix && number == other.number); } public override bool Equals(object obj) { return Equals((EmployeeId)obj); } //使用 operator 关键字重载内置运算符== public static bool operator ==(EmployeeId left, EmployeeId right) { return left.Equals(right); } //使用 operator 关键字重载内置运算符!= public static bool operator !=(EmployeeId left, EmployeeId right) =>!(left == right); }接着创建字典中的值对应的类型: public class Employee { private string name; private decimal salary; private readonly EmployeeId id; public Employee(EmployeeId id, string name, decimal salary) { this.id = id; this.name = name; this.salary = salary; } public override string ToString() { return $"{id.ToString()}:{name,-20} {salary:C}"; } }定义字典,并调用: public static void Run() { var idTony = new EmployeeId("C3755"); var tony = new Employee(idTony, "Tony Stewart", 379025.00m); var idCarl = new EmployeeId("F3547"); var carl = new Employee(idCarl, "Carl Edwards", 403466.00m); var idKevin = new EmployeeId("C3386"); var kevin = new Employee(idKevin, "kevin Harwick", 415261.00m); //字典使用EmployeeId对象来索引 var employees = new Dictionary(5) { [idTony] = tony, [idCarl] = carl, [idKevin] = kevin }; foreach (var employee in employees.Values) { Console.WriteLine(employee); } }

Lookup

Lookup类非常类似于Dictionary类,但是Lookup表示每个映射到一个或多个值的键集合,也就是它的键可以映射到一个或多个值,Lookup中的TElement表示的是Lookup中每个IEnumerable值的元素类型。所以要获取其中的每个元素,可以使用循环进行遍历: var racers = new List(); racers.Add(new Racer(1, "zhang", "san", "zhongguo")); racers.Add(new Racer(2, "li", "si", "riben")); racers.Add(new Racer(3, "wang", "wu", "zhongguo")); racers.Add(new Racer(4, "zhao", "liu", "meiguo")); var lookupRacers= racers.ToLookup(r =>r.Country); foreach (var item in lookupRacers) { foreach(Racer r in lookupRacers[item.Key]) { Console.WriteLine($"{item.Key}:{r.ToString()}"); } }

有序字典SortedDictionary

SortedDictionary和SortedList功能类似,但因为SortedList实现为一个基于数组的列表,而SortedDictionary类实现为一个字典,所以它们有不同的特征。 SortedList使用的内存比SortedDictionary少。SortedDictionary的元素插入和删除操作比较快。在用已排好序的数据填充集合时,若不需要修改容量,SortedList就比较快。注意:SortedList使用的内存比SortedDictionary少,但SortedDictionary在插入和删除未排序的数据时比较快。 SortedDictionary是一个二叉搜索树,其中的元素根据键来排序。该键类型必须实现IComparable接口。如果键的类型不能排序,则可以创建一个实现了IComparer接口的比较器,将比较器用作有序字典的构造函数的一个参数。

集Set

集(set)是包含不重复的元素的集合。主要有两个集,HashSet和SortedSet,它们都实现ISet接口,其中,HashSet集包含不重复元素的无序列表,SortedSet集包含不重复元素的有序列表。 ISet常用方法: bool Add(T item):向当前集添加元素并返回一个值以指示元素是否已成功添加。void ExceptWith(IEnumerableother):从当前集中删除指定集合中的所有元素。bool IsSubsetOf(IEnumerableother):确定集合是否是指定集合的子集。bool IsSupersetOf(IEnumerableother):确定当前集是否是指定集合的超集。bool Overlaps(IEnumerableother):确定当前集是否与指定集合重叠。void UnionWith(IEnumerableother):修改当前集,使其包含当前集,指定集合或两者中存在的所有元素。var hsA = new HashSet() { "one", "two", "three" }; var hsB = new HashSet() { "two", "three", "four" }; if (hsA.Add("five")) { Console.WriteLine("添加了five"); } if (!hsA.Add("two")) { Console.WriteLine("已经存在了two"); } var hsM = new HashSet() { "one", "two", "three", "four", "five", "six" }; //hsA的每个元素是否都包含在hsB中 if (hsA.IsSubsetOf(hsB))//false { Console.WriteLine("hsA是hsB的子集"); } if (hsA.IsSubsetOf(hsM))//true { Console.WriteLine("hsA是hsM的子集"); } //hsA是否是hsB的超集 if (hsA.IsSupersetOf(hsB))//false { Console.WriteLine("hsA是hsB的超集"); } if (hsM.IsSupersetOf(hsB))//true { Console.WriteLine("hsM是hsB的超集"); } //判断hsA是否与hsB有公共元素 if (hsA.Overlaps(hsB))//true { Console.WriteLine("hsA与hsB包含共同元素"); } var allhs = new SortedSet(hsA); allhs.UnionWith(hsB); allhs.UnionWith(hsM); foreach (var n in allhs) { Console.Write(n + "t"); } var ex = new HashSet() { "five", "three" }; //删除ex包含的元素 allhs.ExceptWith(ex); Console.WriteLine(); Console.WriteLine("删除后:"); foreach (var n in allhs) { Console.Write(n + "t"); }

性能

集合的性能决定了操作时应该选择哪种集合。在MSDN文档中,集合的方法常常有性能提示,给出了以大写O记号表示的操作时间: O(1):表示无论集合中有多少数据项,这个操作需要的时间都不变。O(n):表示对于集合执行一个操作需要的时间在最坏情况下是N。O(log n):表示操作需要的时间随着集合中元素的增加而增加,但每个元素需要增加的时间不是线性的,而是成对数曲线。下表列出了集合类执行不同操作的性能,如果单元格中有多个大O值,表示若集合需要重置大小,该操作就需要一定的时间。一般重置大小出现在集合的容量不足以满足需要添加的元素总个数,因此最好避免重置集合的大小,而应把集合的容量设置为一个可以包含所有元素的值。 如果表单元格的内容是n/a(代表not applicable),就表示这个操作不能应用于这个集合类型。 集合AddInsertRemoveItemSortFindList如果集合必须重置大小,就是O(1)或O(n)O(n)O(n)O(1)O(n log n),最坏的情况是O(n^2)O(n)StackPush(),如果栈必须重置大小,就是O(1)或O(n)n/aPop,O(1)n/an/an/aQueueEnqueue(),如果队列必须重置大小,就是O(1)或O(n)n/aDequeue, O(1)n/an/an/aHashSet如果集必须重置大小,就是O(1)或O(n)Add,O(1)或O(n)O(1)n/an/an/aSortedSet如果集必须重置大小,就是O(1)或O(n)Add,O(1)或O(n)O(1)n/an/an/aLinkedListAddLast,O(1)AddAfter, O(1)O(1)n/an/aO(n)DictionaryO(1)或O(n)n/aO(1)O(1)n/an/aSortedDictionaryO(log n)n/aO(log n)O(log n)n/an/aSortedList无序数据为O(n);如果必须重置大小,就是O(n);到列表的尾部,就是O(log n)n/aO(n)读/写是O(log n);如果键在列表中,就是O(log n);如果键不在列表中,就是O(n)n/an/a

参考资源

《C#高级编程(第10版)》C#集合和数据结构本文后续会随着知识的积累不断补充和更新,内容如有错误,欢迎指正。

关于深入解析C#基础系列:高效掌握C#集合应用,的介绍到此结束,希望对大家有所帮助。

用户评论

枫无痕

想学习C#,了解集合的概念很重要啊!

    有16位网友表示赞同!

剑已封鞘

这篇文章能帮我更深入地掌握C#集合的使用吗?

    有17位网友表示赞同!

雨后彩虹

我已经会一些简单的编程语言,准备入门C#了,这篇文章刚好合适!

    有17位网友表示赞同!

清羽墨安

C#集合应用很广,学习起来确实有点难度,希望这篇文章能够详细讲解。

    有5位网友表示赞同!

西瓜贩子

收藏起来了,等空闲时间好好学习一下。

    有16位网友表示赞同!

Hello爱情风

感觉C#集合的API还是挺复杂的...

    有9位网友表示赞同!

人心叵测i

对于想提升C#编程能力的人来说,这个系列文章太实用了!

    有12位网友表示赞同!

自繩自縛

我的项目里需要用到C#集合,希望这篇文章能给我一些启发和指导。

    有6位网友表示赞同!

命里缺他

学习新的编程语言和知识点总让人兴奋!

    有17位网友表示赞同!

空谷幽兰

看标题就知道很棒了,我正在提升C#水平,这个系列一定不能错过。

    有6位网友表示赞同!

旧爱剩女

之前只听过集合的概念,现在终于着手学习了,希望能学到真本事!

    有6位网友表示赞同!

何必锁我心

文章介绍很详细的吗?可以让我直接上手实现一些案例吗?

    有14位网友表示赞同!

无所谓

我已经开始学习C#基础知识了,这个系列正好可以帮助我巩固和提升。

    有14位网友表示赞同!

初阳

学习C#集合应该比学习其他数据结构更容易吧?

    有11位网友表示赞同!

采姑娘的小蘑菇

期待这篇文章能把我从入门到精通 C# 集合的道路上指引方向。

    有9位网友表示赞同!

逃避

最近打算深入学习C#,这个系列文章简直太合适了!

    有16位网友表示赞同!

发型不乱一切好办

学习完这个系列之后,我可以写出更复杂的C#程序了吗?

    有15位网友表示赞同!

最怕挣扎

这篇文章适合哪些水平的C#学习者参加呢?

    有11位网友表示赞同!

孤城暮雨

C#集合在实际项目中的应用场景很多吧?希望文章能分享一些实际案例。

    有13位网友表示赞同!

【深入解析C#基础系列:高效掌握C#集合应用】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活