• 协变逆变(out、in)
    用来修饰泛型字母,只有泛型接口和泛型委托能使用
    用out修饰的泛型只能作为返回值
    delegate T TestOut<out T>();

    用in修饰的泛型只能作为参数
    delegate void TestIn<in T>(T t);


    class Father{}
    class Son : Father{}
    协变:父类泛型委托装子类泛型委托
    TestOut<Son> os = () =>{ return new Son()};
    TestOut<Father> of = os; //加了out后会判断返回值有没有父子关系
    Father f = of(); //Father存of执行后的返回值

    逆变:子类泛型委托装父类泛型委托
    TestIn<Father> iF = (value) =>{};
    TestIn<Son> iS = iF;
    iS(new Son());

  • 多线程 Thread
    进程(Process)是计算机程序关于某数据集合上的一次运行活动
    是系统进行资源分配和调度的基本单位,是操作系统结构的基础
    进程之间可以相互独立运行互不干扰,也可以相互访问、操作

    线程
    操作系统能够进行运算调度的最小单位
    被包含在进程中,是进程中的实际运作单位
    一条线程指的是进程中的一个顺序控制流,一个进程中可以并发多个线程

    语法
    线程的执行代码需要封装到一个函数中
    Thread t = new Thread(NewThread); //新线程将要执行的代码逻辑被封装到了一个函数语句块中

    static void NewThread()
    {
    while(true){}
    };

    线程,启动!
    t.Start();

    后台进程
    随着前台线程的结束而结束
    不设置后台线程会导致进程无法正常关闭
    t.IsBackground = true;

    关闭释放线程
    如果开启的线程中不是死循环,是能结束逻辑的就不用刻意去关闭它
    有两种方式中止线程:
    1、设置bool值为false跳出while循环
    2、t.Abort(); //线程自带的Abort()方法,但在.Net core版本中无法中止会报错

    线程休眠
    t.Sleep(100); //毫秒为单位


    线程间的共享数据
    多个线程使用的内存是共享的,都属于该应用程序(进程)
    同时操作通一片内存区域时可能会出问题,可以通过加锁(lock)的形式避免问题

    加锁 lock(引用类型对象)
    当我们在多个线程访问相同的东西进行逻辑处理时
    为了避免不必要的逻辑顺序执行差错
    lock中对象被锁住会等待被锁住的对象解锁,只有解锁了才运行逻辑

  • 预处理器指令
    编译器
    编译器是一种翻译程序,用于将源语言程序(某种程序设计语言写成,比如C#、C++、Java等语言写的程序)翻译为目标语言程序(二进制数表示的伪机器代码写的程序)

    预处理指令
    指导编译器在实际编译开始前对信息进行预处理
    以#开头,预处理指令不是语句、所以不以分号结束

    常见的预处理器指令
    #define //定义一个符号
    #undef //取消定义的符号
    上述两者写在脚本文件最上方,一般配合 #if 指令使用或配合特性

    #if
    #elif
    #else
    #endif
    和if语句规则一样

    #warning //警告
    #error //报错

  • 反射
    程序正在运行时可以查看其它程序集或者自身的元数据
    一个运行的程序查看本身或者其他程序的元数据的行为就叫做反射

    程序集
    程序集是经由编译器编译得到的供进一步编译执行的中间产物
    在Windows系统中一般是后缀为.dll的库文件或.exe的可执行文件

    元数据:用来描述数据的数据

    反射的作用
    可以在程序编译后获得信息,提高了程序的拓展性和灵活性
    1、程序运行时得到所有元数据,包括元数据的特性
    2、程序运行时实例化对象、操作对象
    3、程序运行时创建新对象,用这些对象执行任务

    语法
    Type(类的信息类)
    反射功能的基础,访问元数据的主要方式

    GetType() 可以获取对象的Type
    GetType("System.Name") 用类名也可以,但必须包含命名空间
    typeof() 传入类名,也可得到对象的Type

    获取类中所有公共成员
    Type t = typeof(Test);
    MemberInfo[] info = t.GetMembers(); //使用了反射

    获取类的公共构造函数并调用
    ConstructorInfo[] c = t.GetConstructors(); //获取所有构造函数

    1、得到无参构造
    ConstructorInfo info = t.GetConstructor(new Type[0]);
    info.Invoke(null);
    Test obj = info.Invoke(null) as Test; //可以使用对象装起来

    2、得到有参构造
    ConstructorInfo info2 = t.GetConstructor(new Type[] {typeof(int)}); //括号中代表的是int类型的构造
    Test obj = info2.Invoke(new object[] {2}) as Test; // 把 i 赋值为2

    获取类的公共成员变量
    1、得到所有成员变量
    FieldInfo[] info = t.GetFields();

    2、得到指定名称公共成员变量
    FieldInfo infoGet = t.GetField("i");

    3、通过反射获取和设置对象的值
    Test test = new Test();
    infoGet.GetValue(test); //获取对象的某个变量的值
    infoGet.SetValue(test , 114514);

    获取类中的成员方法
    使用Type类中的GetMethod方法得到类中的方法
    Type s = typeof(string); //获得string的元数据

    1、如果存在方法重载,用Type数组表示参数类型
    MethodInfo[] m = s.GetMethods(); //得到string类型的所有方法
    MethodeInfo subStr = s.GetMethod("Substring" , new Type[] {typeof(int) , typeof(int)});

    2、调用方法
    string str = "Hello,World";
    subStr.Invoke(str , new object[] { 6 , 5 });


    补充
    Type内还有得枚举、得事件、得接口、得属性等
    GetEnumName、GetEvent、GetInterface、GetProperty

  • Activator
    用于快速实例化对象的类,可将Type对象快捷实例化为对象
    先得到Type
    Type tp = typeof(Test);

    1、无参构造
    Test obj = Activator.CreateInstance(tp) as Test;

    2、无参构造
    obj = Activator.CreateInstance(tp , 99) as Test; //调用一个参数的构造

    创建的参数一定要和构造函数的参数相同

  • Assembly 程序集类
    用来加载其他程序集,加载后才用Type使用其他程序集的信息
    如果想要使用不是自己程序集的内容,就需要先加载程序集

    加载程序集
    用于加载同一文件夹下的其他程序集
    Assembly.Load("程序集名称");
    Assembly.LoadFrom("包含程序集清单的文件名称或路径");
    Assembly.LoadFile();

    1、加载指定程序集
    2、再加载程序集中的一个类对象,之后使用反射
    3、类库创建

  • 特性 Attribute
    是一种允许我们向程序集添加元数据的语言结构,用于保存程序结构信息的某种特殊类型的类
    比如一个类、成员变量、成员方法等,为它们添加更多的额外信息
    需要继承特性基类使用

    语法:
    写在类、函数、变量上一行,表示它们具有该特性信息
    [ 特性名(参数列表) ]
    本质上就是在调用特性类的构造函数

    class MyCustom : Attribute
    {
    public string info;
    public MyCustom(string info)
    {
    this.info = info;
    }
    }

    特性的使用
    如果特性名在申明时名字中含有Attribute,系统会在调用时自动去除这段字符,使用会方便点
    [MyCustom("我是一个特性")]
    class Test{}

    Test t = new test();

    三种获取Type的方式
    Type x = t.GetType();
    x = typeof(Test);
    x = Type.GetType("命名空间");

    判断是否使用了某个特性
    IsDefined( 参数的类型 ,是否搜索继承链(寻找父类中的特性,属性和事件忽略此参数))
    if( x.IsDefined(typeof(MyCustom), false) ){}

    获取Type元数据中所有的特性
    object[] array = x.GetCustomAttributes()

    限制自定义特性的使用范围
    由系统提供
    [AttributeUsage(AttributeTargets.class(能够用在哪些地方,加.class就是类),
    AllowMultiple = true(是否允许多个特性实例用在同一个目标上),
    Inherited = true (特性是否能被派生类和重写成员继承) )]

    调用者信息特性
    CallerFilePath
    CallerLineNumber
    CallerMemberName

    条件编译特性 Conditional
    配合 #define使用
    主要用在调试代码上

    外部DLL包函数特性 DllImport
    [DllImport("路径名或文件名")]
    public static extern int Add(int a, int b) //extern关键字用于声明方法的外部实现

  • 迭代器 iterator
    提供一个方法顺序访问一个聚合对象中的各个元素
    而又不暴露其内部的标识

    实现方法
    继承接口:IEnumerator,IEnumerable

    class CustomList : IEnumerator , IEnumerable
    {}

    foreach(int item in list){}
    foreach本质:
    1、先获取in后面这个对象的IEnumerator,会调用对象其中的GetEnumeratorf获取
    2、执行得到这个IEnumerator对象中的MoveNext方法
    3、只要MoveNext方法的返回值是 true,就会去获取Current并复制给item


    IEnumerable中的 yield return 语法糖
    public IEnumerable GetEnumerator()
    {
    for(int i = 0 ; i < list.Length;i++)
    { yield return list[i]; } //暂时返回保留当前状态
    }