C# 高级概念总结
对C#高级概念进行一些汇总,不断更新,欢迎讨论,指出错误。
文章目录
索引器
数组的下标索引也是实现的索引器(C#的数组并不等同于C++的数组,C#的数组是进行了封装的(Array类),C#的指针数组在不安全的代码中,可否直接拿到数组大小是区分是否是封装的数组的依据之一,不能拿到大小的数组,使用 sizeof/sizeof单个元素 获取指针数组大小,另外,指针数组使用下标进行访问也是对指针使用的简化,本质上指针数组还是通过指针进行的数组访问,而非索引方式。)
实现自己的索引方法。
数组的下标索引也是实现的索引器(C#的数组并不等同于C++的数组,C#的数组是进行了封装的(Array类),C#的指针数组在不安全的代码中,可否直接拿到数组大小是区分是否是封装的数组的依据之一,不能拿到大小的数组,使用 sizeof/sizeof单个元素 获取指针数组大小,另外,指针数组使用下标进行访问也是对指针使用的简化,本质上指针数组还是通过指针进行的数组访问,而非索引方式。)
实现自己的索引方法。(核心:使用this构建自己的索引器)
首先假定我们有自己的类person ,我们要声明一个容器存储person,然后这个容器可以实现,各种索引方法(比如根据名字进行索引,性别(索引返回多个对象))
public class Person
{
public DateTime Birthday
{
get;
}
public string FirstName
{
get;
set;
}
public string LastName
{
get;
}
public Person(string firstname, string lastname, DateTime birthday)
{
this.Birthday = birthday;
this.FirstName = firstname;
this.LastName = lastname;
}
public override string ToString()
{
return $"我是被重载了的ToString:Firstname:{FirstName}\nLastname:{LastName}\nBirthday:{Birthday}";
}
}
int索引器
我们的容器类就叫 PersonCollection
public class PersonCollection
{
private Person[] _people;
public PersonCollection(params Person[] people)
{
_people = people.ToArray();//todo 将所有传进来的people数组 变成一个数组? toarray用法测试
}
/// <summary>
/// int 索引器
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public Person this[int index]
{
get
{
return _people[index];
}
set
{
_people[index] = value;
}
}
int 型索引 和数组其实没啥区别,就是把数组封装了起来,具体的存储还是用 数组存的。
使用索引
Console.WriteLine(personCollection[2]);
string 索引
/// <summary>
/// string 索引器 FirstName 只找第一个 通过While循环拿到
/// set会将所有符合的都设置;while遍历实现
/// </summary>
/// <param name="Firstindex"></param>
/// <returns></returns>
public Person this[string Firstindex]
{
get
{
int indexin = 0;
while (indexin < _people.Length)
{
if (_people[indexin].FirstName == Firstindex)
{
return _people[indexin];
}
}
return null;
}
set
{
int indexin = 0;
while (indexin < _people.Length)
{
if (_people[indexin].FirstName == Firstindex)
{
_people[indexin].FirstName = Firstindex;
}
}
}
}
string 索引的本质是while 循环+判断,也可使用for循环。
使用索引:
Console.WriteLine(personCollection["A"]);
自定义类的索引(核心:Linq)
1.返回 IEnumerable 对象(可遍历对象),因为Linq会吧所有符合条件的数据返回。
public IEnumerable this[DateTime birthday]
{
get
{
return _people.Where(p => p.Birthday == birthday); //todo 总结Linq的使用
}
set
{
}
}
2.返回 泛型版本的 IEnumerable 对象
public IEnumerable<Person> this[DateTime birthday]
{
get
{
return _people.Where(p => p.Birthday == birthday); //todo 总结Linq的使用
}
set
{
}
}
返回对象的使用(可以使用类型推断 var)
两个返回对象都可以使用 foreach 迭代
不同:
泛型版本
IEnumerable<Person> linqIEnumerable = personCollection[new DateTime(1950, 3, 1)];//收集返回的所有符合条件的对象
Console.WriteLine(linqIEnumerable.First<Person>());//todo 返回第一个元素
foreach (var item in linqIEnumerable)
{
Console.WriteLine(item);
}
非泛型版本:
泛型版本拥有更多的方法,比如拿到第一个元素,比如将IEnumerable 转化为一个数组,或者list
迭代器
包含yield 语句且 具有迭代块(声明为返回IEnumerator或IEnumerable的函数(以及实现两者接口的泛型版本),这些具有迭代块的对象可以使用foreach 进行迭代遍历)
IEnumerator 实现迭代器更合适,IEnumerable实现索引器合适。
两者的泛型版本都是更好的选择。
IEnumerator 和 IEnumerable
foreach 迭代实现了GetEnumerator方法的对象(继承IEnumerable的对象)
总结:
1.IEnumerable 实现返回 IEnumerator的yield return 方法(实际上不继承IEnumerable 也可以实现IEnumerator方法 从而进行迭代 如上图)
其次 传入的数组已经可以实现迭代(数组具有迭代方法),可以不用实现自己的迭代,实现自己的迭代,可以再迭代中进行细微控制(while循环中进行具体控制)
2.继承 IEnumerator 则实现3个方法,
其中 position=-1 (初始位置设置为第一个元素0 之前)
方法不能使用foreach 迭代,使用while 和Current 迭代
public class People1 : IEnumerator
{
public string[] pers;
private int position = -1;
public People1(string[] per)
{
pers = per;
}
public object Current
{
get
{
try
{
return pers[position];
}
catch (IndexOutOfRangeException ex)
{
throw new InvalidOperationException();
}
}
}
public bool MoveNext()
{
position++;
return position < pers.Length;
}
public void Reset()
{
position = -1;
}
}
特性
是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。
特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。
属性
使用Get Set,控制字段读写属性进行,且可以再读写过程中,添加函数调用(字段设为private,属性封装为public,VS:ctrl R E)
get
{
console.writeline(“X被读了”);
return code;
}
set
{
console.writeline(“XXX被修改了”);
code = value;
}
抽象类可拥有抽象属性,这些属性应在派生类中被实现。(不常用)
装箱拆箱
概念:通过object 实现,object是根类型,通过把栈对象装到object 然后移动到堆里,实现值对象和引用对象的转换(避免装箱拆箱,是优化程序速度的手段之一)
装箱(值类型->引用类型),因为要操作托管堆上开辟空间,而操作堆比操作栈耗时(栈在三级缓存中的一级缓存中,所以速度更快),且可能引起GC(托管堆由CLR进行管理),开辟空间完毕后,堆栈上的值类型数据复制到申请的堆内存空间中,
因此进行一次装箱要进行分配内存和拷贝内存这两项是比较影响性能的。
拆箱(引用类型->值类型)将存储在堆上的引用类型值转换为值类型并给值类型变量
经过这两步,UnBox和Box是互反的操作。严格的意义上的拆箱,并不影响性能,但是伴随这之后的拷贝数据操作会同Box操作一样对性能有影响 所以装箱拆箱都对性能有影响。
避免办法:
- 要定义自己的值类型,使用struct 代替class,C# struct是存在栈上面的。class则是托管堆上。
2.使用强制类型转换,比如输入输出时: i=5,console.writeline(i.ToString())
3.使用泛型
公共语言运行库(CLR),
CLR 通过精细的算法,跟踪引用变量是否可以访问,CLR 会定期删除不能访问的对象,此即垃圾回收(GC)
GC 的原理是什么%太
CLR 相当于JVM(java虚拟机)
IL/MSIL 中间语言,
相当于Java的class,交由虚拟机执行(java虚拟机JVM, .NET虚拟机CLR)
运行在CLR上的中间语言,解释型语言,C#以及.net家族其余语言编译成IL中间语言,然后由CLR运行,(与Java一样,java先编译成class,然后交由JVM解释运行),
.net 平台结构
托管与非托管资源,C# 内存管理
C# 中基本数据类型大部分都是值类型(15个数据类型,除string和object是引用),存储到操作系统分配的栈中(栈处于计算机三级缓存结构的一级缓存中,所以运行速度快。)
由我们声明的类,都是引用类型,分配到托管堆中,由CLR 进行管理,实现自动垃圾回收。
如果要把自己的类放到栈中,用struct(声明为一个结构),而在C++中 struct和class并无区别。
提问:struct 自动垃圾回收么? 如果不垃圾回收,要实现disposal 接口么(相当于C++的析构函数,手动释放,C#无析构函数一说)
GC原理:
GC对托管资源的分配,在托管堆中,维护一个内存指针,随着内存分配不断移动
什么是垃圾:GC通过遍历应用程序中的“根”来寻找垃圾。我们可以认为根是一个指向引用类型对象内存地址的指针。如果一个对象没有了根,就是它不再被任何位置所引用(=null),那么它就是垃圾的候选者了。(简而言之 没有了引用的对象即被视为垃圾,当内存达到GC标准,则被回收)。
对象代龄的概念(第一批GC留下来的会成为1代,代龄越大,GC保留可能越大),此外GC还会在工作过程中汲取经验,根据应用程序的特点而自动调整每代对象区域的容量,从而可以更高效的工作。
Finalize(相当于C#的析构函数,因为其不确定性(C++销毁对象时立即执行,但因为C#的垃圾回收器工作方式,所以不确定C#析构函数何时执行,由GC自动释放)
Dispose(手动释放)using语句自动调用Dispose(所以一般使用using 打开文件句柄,数据库,网络连接等(使用完毕自动关闭))
参考:https://www.cnblogs.com/juzzs/p/5308813.html
强引用和弱引用(强引用不会被GC回收,弱引用在某些情况下用)
深拷贝,浅拷贝
数组clone是创建数组,copy是赋值数组元素到另一个数组,两者都是创建浅表副本(值类型会赋值,引用类型是复制引用,修改一个,另一个也会改,即浅拷贝),如要深拷贝(需要迭代数组,并且创建新对象)
多线程,
线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果您的应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。
线程是轻量级进程。一个使用线程的常见实例是现代操作系统中并行编程的实现。使用线程节省了 CPU 周期的浪费,同时提高了应用程序的效率。
到目前为止我们编写的程序是一个单线程作为应用程序的运行实例的单一的过程运行的。但是,这样子应用程序同时只能执行一个任务。为了同时执行多个任务,它可以被划分为更小的线程。
Thread 由使用者管理
ThreadPool(线程池),由.net进行管理
task
异步方法
协程
网络编程,
TCP UDP
socket
Protobuf序列化消息,
消息的编写(自定义协议),(消息头,消息体),
消息的接收,(读写缓冲区)
消息的分发(反射分发,委托分发),
粘包的处理,
心跳机制,
反射,
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。(网络编程可以使用反射进行消息分发)
元数据,
元数据描述了 数据类型的 类名 、公共方法…等等关于数据类型的信息
使用反射查看元数据,从而获取对象,调用。
动态编程,
irruntime,纯C# 热更新实现原理,IL注入
元组
数组是相同元素的集合,而元组是不同元素的集合,使用Tuple,超过8个最后使用TRest,表示传递元组,从而实现任意数量元组
var(类型推断) tuple=Tuple.Create<string,string,string,int,int,int,double,Tuple.Create<int,string>>(/传入对应类型的数据/)
数据库与LINQ,
LINQ 实现数据库的增删改查
C#源码刨析
《clr via c#》
C# 哈希表Hashtable与字典表Dictionary<K,V>的对比
List 维护一个内部数组(可以初始化大小,合适的大小不会频繁扩容(2倍伸展)影响性能)