集合
集合(Collection)类是专门用于数据存储和检索的类。
这些类提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。
大多数集合类实现了相同的接口(IEnumerable)。
集合(Collection)类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等等,在 C# 中,Object 类是所有数据类型的基类。
集合--ArrayList
ArrayList:不定长度的,连续分配的。
示例代码:
//元素没有类型限制,任何元素都当成object处理,如果是值类型会有装箱操作
ArrayList arrayList = new ArrayList();
arrayList.Add("object");//可以放string
arrayList.Add(Enumerable.Range(1,100).ToArray());//可以放Array
Func<string, int> func = m => 1;
arrayList.Add(func);//可以放Delegate
示例代码:
//移除数据
arrayList.RemoveAt(0);//通过索引坐标移除
arrayList.Remove("object");//匹配第一个元素值,满足就移除
集合--List
List:泛型集合,内存上都是连续摆放的,不定长,保证类型安全,避免装箱拆箱(都是统一的类型)。
示例代码:
var list = new List<int>() { 1, 2, 3 };
list.Add(1);
list.AddRange(new List<int> { 4, 5, 6 });//批量添加
list.Any();//判断是否有数据
list.Clear();//清除所有数据
list.ForEach((m) => { });//foreach循环,参数=>viod委托
list.Skip(1).Take(2);//取索引为0之后的两条数据
集合--LinkedList
LinkedList:双向链表 元素不连续分配,每个元素都有记录前后节点。
示例代码:
//在头部和尾部都标识了上一个元素和下一个元素所处位置
LinkedList<int> list = new LinkedList<int>();
list.AddLast(1);
list.AddFirst(1);
list.AddBefore(list1, 0);//前面增加
list.AddAfter(list1, 0);//后面增加
list.Remove(1);
list.Remove(list1);//根据节点删除
list.RemoveFirst();
list.RemoveLast();
list.Clear();
链表不能通过元素索引访问。找元素只能遍历。增删比较快,增加或删除,只需把这个元素的前后两个元素指向的元素节点改一下。
集合--Queue
Queue队列就是先进先出。它并没有实现 IList,ICollection。所以它不能按索引访问元素,不能使用Add和Remove。
//示例: A不断写入任务,B不断获取任务执行 ,每次拿最近的一个任务
Queue<string> queue = new Queue<string>();
queue.Enqueue("object");//添加数据
queue.Enqueue("object1");
foreach (var item in queue)
{
Console.WriteLine(item);
}
queue.Dequeue();//获取最先进入队列的元素,获得并移除
queue.Peek();//获取但不移除
Queue的常用方法和属性:
- Enqueue():在队列的末端添加元素。
- Dequeue():在队列的头部读取和删除一个元素,注意这里读取元素的同时也删除了这个元素。如果队列中不再有任何元素就抛出异常。
- Peek():在队列的头读取一个元素,但是不删除它。
- Count:返回队列中的元素个数。
- TrimExcess():重新设置队列的容量,因为调用Dequeue方法读取删除元素后不会重新设置队列的容量。
- Contains():确定某个元素是否在队列中。
- CopyTo():把元素队列复制到一个已有的数组中。
- ToArray():返回一个包含元素的新数组。
集合--Stack
Stack:栈也是链表, 先进后出,先产生的数据最后使用。
示例代码:
Stack<string> stack = new Stack<string>();
stack.Push("object");//添加数据
stack.Push("object1");
stack.Pop();//获取最后进入队列的元素 获得并移除
stack.Peek();//获取不移除
C#中(线程)栈的内存释放也是一样,先实例化的对象最后释放(在栈中声明的变量,最先声明的最后GC)。
集合--HashTable
Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似key-value的键值对,其中key通常可用来快速查找,同时key是区分大小写,value用于存储对应于key的值。
Hashtable中keyvalue键值对均为object类型,所以Hashtable可以支持任何类型的key-value键值对。
示例代码:
Hashtable ht=new Hashtable(); //创建一个Hashtable实例
ht.Add("E","e");//添加keyvalue键值对
ht.Add("A","a");
ht.Add("C","c");
ht.Add("B","b");
string s=(string)ht["A"];
if(ht.Contains("E")) //判断哈希表是否包含特定键,其返回值为true或false
Console.WriteLine("the E key exist");
ht.Remove("C");//移除一个keyvalue键值对
Console.WriteLine(ht["A"]);//此处输出a
ht.Clear();//移除所有元素
Console.WriteLine(ht["A"]); //此处将不会有任何输出
集合--Dictionary
Dictionary:相当于泛型版本的HashTable。在使用Dictionary前,你必须对它的键类型和值类型进行声明。
Dictionary的描述:
1、从一组键(Key)到一组值(Value)的映射,每一个添加项都是由一个值及其相关连的键组成
2、任何键都必须是唯一的
3、键不能为空引用null(VB中的Nothing),若值为引用类型,则可以为空值
4、Key和Value可以是任何类型(string,int,custom class 等)
1、创建及初始化
Dictionary<int,string>myDictionary=newDictionary<int,string>();
2、添加元素
myDictionary.Add(1,"C#");
myDictionary.Add(2,"C++");
myDictionary.Add(3,"ASP.NET");
myDictionary.Add(4,"MVC");
Hashtable和Dictionary区别:
1、Dictionary<K,V>在使用中是顺序存储的,而Hashtable由于使用的是哈希算法进行数据存储,是无序的。
2、Dictionary的key和value是泛型存储,Hashtable的key和value都是object。
3、Dictionary是泛型存储,不需要进行类型转换,Hashtable由于使用object,在存储或者读取值时都需要进行类型转换,所以比较耗时。
集合--非泛型集合
泛型集合类是在.NET2.0的时候出来的,也就是说在1.0的时候是没有这么方便的东西的。现在基本上我们已经不使用这些集合类了,除非在做一些和老代码保持兼容的工作的时候。
ArraryList被List<T>替代。
HashTable 被Dictionary<TKey,TValue>替代。
Queue 被Queue<T>替代。
SortedList 被SortedList<T>替代。
Stack 被Stack<T>替代。
泛型
泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许编写一个可以与任何数据类型一起工作的类或方法。可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。
这里是一个泛型类型Stack <T>,用于堆栈类型T的实例:
Stack<T>声明单个类型参数T:
public class Stack<T> {
int position;
T[] data = new T[100];
public void Push (T obj) {
data[position++] = obj;
}
public T Pop() {
return data[--position];
}
}
我们可以使用Stack <T>如下:
Stack<int> stack = new Stack<int>();
stack.Push(5);
stack.Push(10);
int x = stack.Pop(); // x is 10
int y = stack.Pop(); // y is 5
Stack< int> 用类型参数int填充类型参数T。
Stack<T> 是一个开放类型,而Stack <int>是一个闭合类型。
泛型--关联性泛型集合类
即键值对集合,允许通过Key来访问和维护集合。
1.Dictionary<TKey,TValue>
Dictionary<TKey,TValue>可能是我们最常用的关联性集合了,它的访问,添加,删除数据所花费的时间是所有集合类里面最快的,因为它内部用了Hashtable作为存储结构,所以不管存储了多少键值对,查询/添加/删除所花费的时间都是一样的,它的时间复杂度是O(1)。
Dictionary<TKey,TValue>优势是查找插入速度快,那么什么是它的劣势呢?因为采用Hashtable作为存储结构,就意味着里面的数据是无序排列的,所以想按一定的顺序去遍历Dictionary<TKey,TValue>里面的数据是要费一点工夫的。
作为TKey的类型必须实现GetHashCode()和Equals() 或者提供一个IEqualityComparer,否则操作可能会出现问题。
2.SortedDictioanry<TKey,TValue>
SortedDictionary<TKey,TValue>和Dictionary<TKey,TValue>大致上是类似的,但是在实现方式上有一点点区别。SortedDictionary<TKey,TValue>用二叉树作为存储结构的。并且按key的顺序排列。那么这样的话SortedDictionary<TKey,TValue>的TKey就必须要实现IComparable<TKey>。如果想要快速查询的同时又能很好的支持排序的话,那就使用SortedDictionary吧。
3.SortedList<TKey,TValue>
SortedList<TKey,TValue>是另一个支持排序的关联性集合。但是不同的地方在于,SortedList实际是将数据存存储在数组中的。也就是说添加和移除操作都是线性的,时间复杂度是O(n),因为操作其中的元素可能导致所有的数据移动。但是因为在查找的时候利用了二分搜索,所以查找的性能会好一些,时间复杂度是O(log n)。所以推荐使用场景是这样地:如果你想要快速查找,又想集合按照key的顺序排列,最后这个集合的操作(添加和移除)比较少的话,就是SortedList了。
泛型--非关联性泛型集合类
非关联性集合就是不用key操作的一些集合类,通常可以用元素本身或者下标来操作。
List<T>
泛型的List 类提供了不限制长度的集合类型,List在内部维护了一定长度的数组(默认初始长度是4)。所以如果知道我们将要用这个集合装多少个元素的话,可以在创建的时候指定初始值,这样就避免了重复的创建新数组和拷贝值。
另外的话由于内部实质是一个数组,所以在List的未必添加数据是比较快的,但是如果在数据的头或者中间添加删除数据相对来说更低效一些因为会影响其它数据的重新排列。
LinkedList<T>
LinkedList在内部维护了一个双向的链表,也就是说我们在LinkedList的任何位置添加或者删除数据其性能都是很快的。因为它不会导致其它元素的移动。一般情况下List已经够我们使用了,但是如果对这个集合在中间的添加删除操作非常频繁的话,就建议使用LinkedList。
HashSet<T>
HashSet是一个无序的能够保持唯一性的集合。我们也可以把HashSet看作是Dictionary<TKey,TValue>,只不过TKey和TValue都指向同一个对象。HashSet非常适合在我们需要保持集合内元素唯一性但又不需要按顺序排列的时候。
SortedSet<T>
SortedSet和HashSet,就像SortedDictionary和Dictionary一样,还记得这两个的区别么?SortedSet内部也是一个二叉树,用来支持按顺序的排列元素。
Stack<T>
后进先出的队列
不支持按下标访问
Queu<T>
先进先出的队列
不支持按下标访问
泛型--泛型类型参数
在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时指定的特定类型的占位符。 泛型类( GenericList<T>)无法按原样使用,因为它不是真正的类型。 若要使用 GenericList<T>,必须通过指定尖括号内的类型参数来声明并实例化构造类型。 此特定类的类型参数可以是编译器可识别的任何类型。
示例代码:
GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
在 GenericList<T> 的每个实例中,类中出现的每个 T 在运行时均会被替换为类型参数。
泛型--泛型约束
定义泛型类时,可以对调用端代码能够在实例化类时用于类型参数的几种类型施加限制。 如果调用端代码尝试使用约束所不允许的类型来实例化类,则会产生编译时错误。 这些限制称为约束。 通过使用 where 上下文关键字指定约束。
下表列出了六种类型的约束:
在 GenericList<T> 的每个实例中,类中出现的每个 T 在运行时均会被替换为类型参数。
where T:类(类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型)
class MyClass<U>
where U : class///约束U参数必须为“引用类型”
{
}
public void MyMetod<T>(T t)
where T : class
{
}
where T:<基类名>(类型参数必须是指定的基类或派生自指定的基类)
public class Employee{}
public class GenericList<T>
where T : Employee
泛型--泛型方法
泛型方法在方法的签名中声明类型参数。
使用泛型方法,许多基本算法只能以通用方式实现。
这里是一个泛型的方法示例:
public void ShowT<T>(T t)
{
Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType());
}
Console.WriteLine("*********泛型方法调用***************");
ShowT<int>(11);
ShowT<DateTime>(DateTime.Now);
ShowT<People>(new People { Id = 11, Name = "Tom" });
Console.ReadKey();
LINQ
LINQ代表语言集成查询(Language Integrated Query),是.Net框架的扩展,它允许我们用SQL查询数据库的方式来查询数据的集合,使用它,你可以从数据库、程序对象的集合以及XML文档中查询数据。
查询表达式由查询体后的from子句组成,其子句必须按一定的顺序出现,并且from子句和select子句这两部分是必须的。
参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/linq/
LINQ提供了一系列的优势,其中最重要的是其强大的表达能力,使开发表达声明。一些LINQ的优点如下。
- LINQ提供语法高亮,证明有助于找出在设计时的错误。
- LINQ提供智能感知这意味着很容易写更精确的查询。
- 写LINQ代码是相当快的,因此开发时间也被显著减少。
- LINQ使得调试方便,因为它在C#语言的集成。
- 两个表之间的关系看很容易使用LINQ由于其分层特征,这使得编写查询在更短的时间加入多个表。
- LINQ允许一个单一的LINQ语法的使用,同时查询多个不同的数据源,这是主要是因为其统一的基础。
- LINQ是可扩展的,这意味着有可能使用LINQ的知识来查询新的数据源类型。
- LINQ提供了一个查询连接多个数据源,以及突破复杂问题转换为一组短的查询易于调试的工具。
- LINQ提供易于改造转换一种数据类型到另一种,如SQL数据转换为XML数据。
委托
C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。顾名思义,让别人帮你办件事。委托是C#实现回调函数的一种机制。
委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。
这个实例演示了委托的用法。委托 printString 可用于引用带有一个字符串作为输入的方法,并不返回任何东西。
我们使用这个委托来调用两个方法,第一个把字符串打印到控制台,第二个把字符串打印到文件
lambda表达式
Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数。
首先定义一个Citys集合,初始化有一些数据。然后调用LINQ的first方法,查询出来长度大于7的第一个结果,看到了吧,这里用的就是Lambda表达式,
如果我们自己写,还要写循环遍历集合,然后判断字符串长度是否大于7,起码要写四五行代码,而这里只要一行就够了,而且LINQ也要写很长。
这里用的是最简单的Lambda表达式,(input parameters) => expression的形式。
利用委托方法和lambda表达式将2013替换成2014,当然也可以做其他任何操作,是由我们传入的lambda表达式决定的
Lambda 的特点
- Lambda 中包含输入参数的数量,必须与委托类型包含的参数数量一致。
- Lambda 中的每个输入参数,必须都能够通过隐式转换为其对应的委托参数类型。
- Lambda 中的返回值(如果有),必须能够隐式转换为委托的返回类型
反射
什么是反射
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。
可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
反射(Reflection)有下列用途:
它允许在运行时查看特性(attribute)信息。
它允许审查集合中的各种类型,以及实例化这些类型。
它允许延迟绑定的方法和属(property)。
它允许在运行时创建新类型,然后使用这些类型执行一些任务。
反射用到的主要类:
System.Type 类:通过这个类可以访问任何给定数据类型的信息。
System.Reflection.Assembly类:它可以用于访问给定程序集的信息,或者把这个程序集加载到程序中。
System.Type类:对于反射起着核心的作用。但它是一个抽象的基类,Type有与每种数据类型对应的派生类,我们使用这个派生类的对象的方法、字段、属性来查找有关该类型的所有信息。获取给定类型的Type引用有3种常用方式:
Type类的属性:
Name 数据类型名;
FullName 数据类型的完全限定名(包括命名空间名);
Namespace 定义数据类型的命名空间名;
IsAbstract 指示该类型是否是抽象类型;
IsArray 指示该类型是否是数组;
IsClass 指示该类型是否是类;
IsEnum 指示该类型是否是枚举;
IsInterface 指示该类型是否是接口;
IsPublic 指示该类型是否是公有的;
IsSealed 指示该类型是否是密封类;
IsValueType 指示该类型是否是值类型;
Type类的方法:
GetConstructors():返回ConstructorInfo类型,用于取得该类的构造函数的信息;
GetEvents():返回EventInfo类型,用于取得该类的事件的信息;
GetFields():返回FieldInfo类型,用于取得该类的字段(成员变量)的信息;
GetInterfaces():返回InterfaceInfo类型,用于取得该类实现的接口的信息;
GetMembers():返回MemberInfo类型,用于取得该类的所有成员的信息;
GetMethods():返回MethodInfo类型,用于取得该类的方法的信息;
GetProperties():返回PropertyInfo类型,用于取得该类的属性的信息可以调用这些成员,其方式是调用Type的InvokeMember()方法,或者调用MethodInfo, PropertyInfo和其他类的Invoke()方法;
优点:
1、反射提高了程序的灵活性和扩展性。
2、降低耦合性,提高自适应能力。
3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
事件
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。