概述
tmd学的东西杂七杂八的,越来越多,脑子可不是无限大的硬盘,之前用烂的知识过一段时间可能也会手生。
为了能够在长时间未使用C#而又需要用到C#的时候能够快速回忆起来,故此做了该笔记。
(没错,就是为了应付笔试和面试!)
不过C#还真的是易学好懂又贼好用的语言啊…
C#语言基础
C#要基于.NET Framework运行。
编译过程:高级语言->编译->dll/exe文件->CLR/JIT->机器码
几个关键的概念
CRL:公共语言运行库 或者 .net运行库,是.net framework的核心
IL(Intermediate Language):中间语言,Microsoft Intermediate Language,MSIL 微软中间语言
JIT编辑: Just In Time Compilation 及时编辑
数据类型
内存分配上的数据类型
- 引用类型:存储在托管堆中。(CLR会对其进行定期垃圾回收处理)
- 值类型:存储在堆栈上。
基本内置类型
C#的内置类型(int、float、string)都内置于.NET Framework中,比如int实际上是System.Int32的一个实例,因此在语法上,可以将基本内置类型看作是支持某些方法的类。
1 | int i = 5; |
Object类型
Object默认是所有对象的根类型,它支持Equlas()、ToString()等基本方法,不过通常需要我们去重写。
类型推断
var关键字:相当于C++中的auto
常量
运行时常量(readonly)
编译时常量(const)
类基础
访问权限
- public:略
- private:略
- protected:略
- internal:只能在包含它的程序集中访问该项
- protected internal:只能在包含它的程序集和派生类型的任何代码中访问该项
函数成员
方法
重载无法识别ref、out以及返回值。
参数传递:
ref和out两者都是通过关键字找到内存地址,使值类型像引用类型一样传递,但ref需要对参数进行初始化。
属性
get、set
构造函数
C#新特性———静态构造函数:
在构造函数方法前面添加了static关键字便是静态构造函数,没有访问修饰符和和参数
静态构造函数不是程序员主动调用的,是.NET在合适的时机进行调用,通常是实例化或者调用静态成员变量的时候
静态构造函数只会被调用一次
执行顺序:静态变量->静态构造函数->实例变量->实例构造函数
终结器
匿名类
var+new
弱引用
可以使用WeakReference创建弱引用,在垃圾回收时会将其回收。
注意点是使用前要先判断弱引用是否存在。
部分类
parial关键字运行把类、结构、方法或接口放在多个文件中。
不同文件中,声明类时使用的访问权限关键字必须相同。
扩展方法
可以在不知道源代码的基础上扩充类。
扩展方法所在的类必须声明为static。
扩展方法必须声明为public和static。
1 | public static class M{ |
继承
结构和类
结构:值类型,派生自System.ValueType,不支持继承,但可以实现接口。
类:引用类型,默认派生自System.Object。
实现继承
即普通的继承,C#默认都是public继承(C++有private继承)
调用函数的基类版本
base.MethodName();
抽象类和抽象函数
1 | abstract class Demo{ |
抽象类不能实例化,抽象方法是在基类中声明的方法,必须在所有派生类中重写。
密封类和密封方法
1 | sealed class Demo{} |
将类和方法声明为sealed,表示不能继承或重写该类/方法
构造函数的执行顺序
基类->成员->派生类
接口
接口引用完全可以看成类引用——它可以引用任何实现该接口的类。
接口数组:
1 | Ixxx[] demo = new Ixxx[2]; |
泛型
性能
泛型的具体类型在JIT编译器动态生成的类中使用,不再进行boxing和unboxing。
保证了类型安全。
二进制代码的重用:泛型类可以定义一次,并且在许多不同的类型实例化,不需要像C++模板那样访问源代码。
泛型类的定义会放在程序集中,所以用特定类型实例化泛型类不会在IL代码中复制这些类。
但是,在JIT编译器把泛型类编译成本地代码时,会给每个值类型创建一个新类。
泛型类的功能
默认值
1 | public T demo(){ |
可以使用default关键字为T类型赋予默认值。(不能用null,因为T不一定是引用类型)
约束
1 | public class demo<T> where T:IT{ |
where子句指定了T类型实现接口的要求
- where T:struct T必须是值类型
- where T:class T必须是引用类型
- where T:IT T必须实现IT接口
- where T:BaseT T必须失效BaseT基类
- where T:new() T必须有一个默认的构造函数
- where T1:T2 T必须派生自泛型类型T2
静态成员
泛型类的静态成员只能在类的一个实例中共享
1 | demo<string>.x = 4; |
协变与抗变
协变:参数类型是协变的,可以用子类对象替代父类对象输入。
抗变:返回值是抗变的,不能用子类代替父类进行返回。
泛型接口正常情况无法进行子父类的转换:
1 | List<Animal> animals = new List<Cat>();//error! |
可以用out关键字让泛型接口实现协变,返回类型只能是T。
1 | public interface ICustomerListOut<out T> |
in关键字让泛型接口实现抗变,输入参数只能是T。
1 | public interface ICustomerListIn<in T> |
数组
数组协变
数组支持协变。
枚举
foreach原理:
- 数组或集合实现带 GetEnmuerator() 方法的 IEumerable 接口。
- GetEnmuerator() 方法返回一个实现 IEumerable 接口的枚举。
遍历过程:
调用GetEnumerator()方法,获得枚举器,只要MoveNext()返回true——就用Current属性访问数组中的元素。
1 | public class demo(){ |
yield return语句返回元素后,等到MoveNext()调用时,再从之前返回的地方继续。(这也是U3D实现协程的一个基础)
可以把yield类型看作内部类Enumerator。
元组
Tuple<int,int>;
运算符和类型重载
待更新
委托、Lambda和事件
委托
.NET的委托可以看作是一个类型安全的,且可以判断参数和返回值类型的函数指针(C++)
Action
Func
1 | Func<in T1,in T2,out TResult> |
Lambda
当参数是委托类型时,可以使用lambda表达式实现委托引用的方法。
闭包:通过lambda表示式可以访问lambda表达式外部的变量。
闭包原理:编译器会对lambda表达式创建一个匿名类,它有一个构造函数赖传递外部变量。
关于forech的坑:
在C#5.0之前,闭包的(构造函数)参数传递是在函数调用时。
在C#5.0之后,闭包会在while循环的代码块中自动创建一个局部变量来捕捉正确的值。
事件
事件基于委托,为委托提供了一种发布/订阅机制。(观察者模式)
待补充…
字符串和正则表达式
待补充…
集合
待补充…
LINQ
待补充…
动态语言扩展
待补充…
异步编程
待补充…
内存管理和指针
Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址上。
32位-4GB内存;
64位-更大…
值类型
栈从高地址向低地址填充。
引用类型
托管堆,存储引用类型时,会从堆中选第一个未使用的足够大小的连续块。
垃圾回收
自动压缩过程:释放对象后,其它对象会移动回堆的顶部,形成连续的内存块(减少内存碎片)
对象具有代龄:刚进堆的是0代,1次垃圾回收后,0代变成1代,2代以此类推。
大对象堆:在.NET中,较大对象有自己的托管堆(大对象堆),在大对象堆的对象不执行压缩过程。
堵塞线程:第二代和大对象回收放在后台线程上进行,只有0.1代对象回收回堵塞程序线程。
调用System.GC.Collect()方法可以强制垃圾回收。
释放非托管资源
- 析构函数
- 实现System.IDisposable接口
不安全的代码
把代码块标记为unsafe后,就可以使用指针:
反射
自定义特性
特性不会影响编译过程,编译器不能识别它们,特性在应用于程序元素时,可以在编译好的程序集中用作元数据。
使自定义特性强大的因素是使用反射。
自定义特性需要实现Attribute接口
1 | [ ] |
反射
System.Type类
获取指向任何类型的Type引用有三种方法:
- Type t = typeof(double);
- Type t = d.GetType();
- Type t = Type.GetType(“hello”);
Type是许多反射功能的入口,它实现许多方法和属性。
获取方法:
- MethodInfo[] methods = t.GetMethods();
- MemberInfo member = t.GetMember();
等等,自行查阅API
Assembly类
允许访问给定程序集的元数据,包含可以加载和执行程序集的方法。
Assembly assembly = Assembly.Load(“SomeAssembly”);
方法查阅API
程序集
基本概念
程序集是.NET用于部署和配置单元的术语。
.NET应用程序包含一个或多个程序集,通常扩展名为EXE或DLL的.NET可执行程序称为程序集。
程序集和本地DLL或EXE有什么区别?它们的文件扩展名虽然相同,但.NET程序集包含元数据,这些元数据描述了程序集中定义的所有类型及其成员的信息。
程序集可以是私有或共享的,共享可以减少内存的消耗。