分布类

分部类

  • 把一个类分成几部分来声明
  • 使用关键字partial
  • 分部描述一个类
  • 增加程序的扩展性
  • 分部类可以写在多个脚本文件中
  • 分部类中部可以有重复的成员
  • 分部类的访问修饰符要一致
partial class Student{
	public bool sex;
	public string name;
}
partial class Student{
	public bool sex;
	public int id;
}

反射

反射

什么是程序集

  • 程序集是经由编译器编译得到的,供进一步编译执行的中间产物
    • 就是我们写的一个代码集合,我们现在写的所有代码 最终都会被编译器翻译为一个程序集供人使用
    • 比如一个dll或者exe
  • 在Windows系统中,它一般表现为dll(库文件)或者是exe(可执行文件)

元数据

  • 元数据就是用来描述数据的数据
  • 程序中的类,类中的函数、变量等等信息就是程序的元数据
  • 元数据,保存在程序集中

反射的概念

  • 程序正在运行时,可以查看其他程序集或者自身的元数据
  • 一个运行的程序查看本身或者其他程序元数据的行为就叫反射
  • 通过反射可以得到 其他程序集或者自己程序集的元数据(类、函数、变量、对象等)实例化,执行,操作他们

反射的作用

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

反射的语法

Type

  • Type(类的信息类)
  • 他是反射功能的基础 是访问呢元数据的主要方式
  • 使用Type的成员获取有关类型声明(构造函数、方法、字段、属性、和类的事件)的信息
int a = 42;
Type type = a.GetType();//GetType是Object中的方法
Console.WriteLine(type);//System.Int32

//通过typeof 传入类名 也可以得到对象的type
Type type2 = typeof(int);
Console.WriteLine(type2);//System.Int32
  
  //通过类的名字 也可以获取类型
  //类名要包含命名空间 不然找不到
  Type type3 = Type.GetType("System.Int32");
  Console.WriteLine(type3);//System.Int32
  
  //虽然有三个变量 但Type 1 2 3 指向的是一个房间

获取类中的所有公共成员元数据

//先得到Type
Type t = typeof(Test);
//得到所有的公共成员(包括构造函数) 需要引用System.Reflection;
MemberInfo[] infos = t.GetMembers();
foreach(var item in infos){
	Console.WriteLine(item);
}

获取类的所有构造函数的元数据

//先得到Type
Type t = typeof(Test);
//得到所有的构造函数
ConstructorInfo[] ctors = t.GetConstructors();
foreach(var item in ctors){
	Console.WriteLine(item);
}

获取类指定的构造函数的元数据 并调用

//先得到Type
Type t = typeof(Test);
//通过GetConstructor得到一个构造函数
//GetConstructor需要传入一个Type数组 数组中内容按顺序是参数类型
//得无参构造可以new一个长度为0的Type数组
ConstructorInfo info = t.GetConstructor(new Type[0]);
//执行无参构造
Test t = info.Invoke(null) as Test;//无参构造没有参数 传入null 返回object

//得有参构造
ConstructorInfo info2 = t.GetConstructor(new Type[]{typeof(int),typeof(string)});
//执行有参构造
Test t2 = info2.Invoke(new object[]{3,"阿拉拉"}) as Test;

得到类中所有的公共成员方法的元数据

//先得到Type
Type t = typeof(string);
MethodInfo[] methods = t.GetMethods();
foreach(var item in methods){
    Console.WriteLine(item);
}

得到类中的指定公共成员方法的元数据

//先得到Type
Type t = typeof(string);
//第一个参数传方法名 第二个参数传type数组
MethodInfo method = t.GetMethods("Substring",new Type[]{typeof(int),typeof(int)});
//调用该方法
string str = "hellow";
//第一个参数 相当于是 哪个对象要执行这个成员方法
method.Invoke(str,new object[]{2,3});//llo
//如果是静态的 第一个参数传null
method.Invoke(null,new object[]{2,3});//llo

获取所有公共成员变量的元数据

//先得到Type
Type t = typeof(Test);
FieldInfo[] fieldInfos = t.GetFields();
foreach(var item in fieldInfos){
	Console.WriteLine(item);
}

获取指定的公共成员变量的元数据

//先得到Type
Type t = typeof(Test);
FildInfo info = t.GetFild("变量名");

获取和设置对象的值

一般用来得别的程序集里的值

多线程和异步方法

什么是进程

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

什么是线程

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

通过代码开启线程

  • 线程类Thread
  • 需要引用命名空间 System.Threading;
using System.Threading;

class Test{

    public Main(){
        //开启一个新的线程 需要传入一个委托类型
        Thread t = new Thread(NewThreadLogic);
        //启动线程 默认是前台线程
        t.Start();
	}

    static void NewThreadLogic(){
        //新开线程 执行的代码逻辑 
        while(true){
        //前台线程:当主线程的代码执行完 依然不会停止 可能会导致进程无法正常关闭
        Console.WriteLine("新的线程");
        }
    }
}

后台线程

  • 主线程的代码执行完毕后 如果前台线程的代码没有执行完 程序依然不会关闭
  • 前台线程可能会影响程序的正常关闭
  • 可通过IsBackground属性 将线程设置为后台线程 在主线程的代码执行完后 后台线程也会一起关闭
t.IsBackground = true;//设置为后台线程	

关闭线程

  • 如果开启的线程中不是死循环 是能够结束的逻辑 那么 不用刻意的去关闭它
  • 如果是死循环 想要中止这个线程 有两种方式

方法一:将线程置空

方法参数默认值

参数默认值

  • 支持多个参数添加默认值 每个参数都可以有默认值
  • 没有默认值的参数要写在前面
static int Speak(string str = "我不想说话"){

}

泛型

泛型

  • 泛型实现了类型参数化达到代码重用目的
  • 通过类型参数化来实现一份代码上操作多种类型
  • 泛型相当于类型占位符
  • 定义类或方法时使用替代符代表变量类型
  • 当真正使用类或者方法时在具体指定类型

泛型分类

  • 泛型类 class 类名<泛型占位符>
  • 泛型接口 interface 接口名<泛型占位符>
  • 泛型函数 函数名<泛型占位符>(参数列表)
  • 泛型占位字母可以有多个 用逗号分开 一般是大写字母
  • 可以一定程度上避免装箱拆箱
  • 不确定泛型类型时 获取默认值 可以使用default(占位字符)
//泛型类
class TestClass<T,K>{
   public T value;
   public K  value2;
}
TestClass<int,string> t = new TestClass<int,string>();
t.value = 10;
t.value2 = "阿拉拉";

泛型接口

interface TestInterFace<T>{
    T Value{get;set;}
}
class Test : TestInterFace<int>{
    public int Value{
        get{ return 10;}
        set;
    }
}

泛型方法

多态

多态

  • 多态按字面意思就是"多种状态"
  • 让继承同一父类的子类门 在执行相同方法时有不同的表现(状态)

vob关键字

  • v:virtual(虚函数)虚函数可以被子类重写
  • o:override(重写) 方法被重写后 当父类装子类时 会调用被重写后的方法
  • b:base(父类) 可以使用base调用父类的方法
class Fater{
	public int age;
	public string name;
	public void Speak(){
		Console.WriteLine("我是父类");
	}
    //虚函数
	public virtual void Sing(){
		Console.WriteLine("我是父类~~~~~~");
	}
}

class Son:Fater{
	public new void Speak(){
		Console.WriteLine("我是子类");
	}
    //重写父类方法 多态
	public override void Sing(){
        //可通过base保留父类的行为
        //base.Sing();
		Console.WriteLine("我是子类~~~~~~");
	}
}


Fater f = new Son();
f.Speak();//我是父类
f.Sing();//我是子类~~~~~~~

抽象类

  • 被abstract修饰的类
  • 抽象类不能被实例化 但是继承的子类可以
  • 遵循里氏替换原则 可以用作为容器来装子类
  • 可以包含抽象方法
    • 抽象方法:
        1. 只能在抽象类中声明
        2. 没有方法体
        3. 不能是私有的
        4. 继承后必须实现 用override重写
  • 继承抽象类必须重写其抽象方法
absetract class Fruits{
    //抽象类中可以包含成员属性
    public string name;
    
    //抽象方法没有方法体
    public abstract void Bad();
}
absetract class Apple : Fruits{
    //抽象类中可以包含成员属性
    public string name;
    
    //抽象方法必须被重写
    public override void Bad(){
        Console.WriteLine("阿拉拉");
    }
}

抽象类和接口的区别

导入导出资源包

导入导出资源包

  • 导出资源包
    • 在Project面板中 右键 选择Export Package
    • 选中Include dependencies会把依赖的脚本一起导出(默认选中)
  • 导入资源包
    • 可直接拖入Project面板导入
    • 也可以在Project面板中 右键 选择Import Package导入

成员属性

成员属性

  • 用于保护成员变量
  • 解决访问修饰符的局限性
  • 为成员属性的获取和赋值 添加逻辑处理
  • 可以让成员变量在内部 只能获取不能修改 或者只能修改不能获取
class Person{
	private string name;
	private int age;
	public string Name{
		get{
			return name;
		}
		set{
			//关键字value 用来表示外部传入的值
            name = value;
		}
	}

}
//使用
Person p = new Person();
p.Name = "罗杰";

get和set 可以添加访问修饰符

  • 默认不加 会使用属性声明时的访问权限
  • 加的访问修饰符要低于属性的访问权限
  • 不能让get和set的访问权限都低于属性权限
class Person{
	private string name;
	private int age;
	public string Name{
        //不可以在外面获得
		private get{
			return name;
		}
		set{
			//关键字value 用来表示外部传入的值
            name = value;
		}
	}

}
//使用
Person p = new Person();
p.Name = "罗杰";//报错

自动属性

迭代器

迭代器

  • 迭代器(iterator)有时又称光标(cursor)是程序设计的软件设计模式
  • 而迭代器模式提供一个方法顺序 访问一个聚合对象中的各个元素 而又不暴露其内部的标识
  • 是可以在容器对象上遍历访问的接口 设计人员无需关心容器对象的内存分配的实现细节
  • 可以用foreach遍历的类 都是实现了迭代器的

标准迭代器的实现方法

  • 可通过实现IEnumerator和IEnumerable这两个接口 来实现迭代器
  • 需要引用命名空间 System.Collections
class CustomList : IEumerable,IEnumerator{
    private int[] list;
    private int index = -1;
    public CustomList(){
        list = {1,2,3,4,5,6,7,8,9};
    }
    //只要实现了IEumerable接口 Foreach就不会报错
    public IEnumerator GetEnumerator(){
        Reset();
        return this;
    }
    //IEnumerator接口的方法
    //代表当前的元素
    public object Current => list[index];
    public void Reset(){
        index = -1;
    }
     public bool MoveNext(){
        index++;
        return lsit.Length>index;
     }    
}

foreach本质

  1. 先调用in后面这个对象里面 通过IEumerable接口实现的GetEnumerator方法
  2. 执行得到的IEnumerator对象中的MoveNext方法
  3. 只要MoveNext方法的返回值是true 就会去得到Current 赋值给item
  4. 遍历结束后会调用Reset方法 重置index
foreach(var item in CustomList){
    Console.WriteLine(item);//1 2 3 4 5 6 7 8 9
}

通过语法糖来实现迭代器

  • 语法糖的主要作用 就是将复杂逻辑简单化,可以增加程序的可读性
  • 程序内部会自动生成IEnumerator
class CustomList : IEumerable{
    private int[] list;
    private int index = -1;
    public CustomList(){
        list = {1,2,3,4,5,6,7,8,9};
    }
    //只要实现了IEumerable接口 Foreach就不会报错
    public IEnumerator GetEnumerator(){
        for(int i =0;i<list.Length;i++){
            //yield关键字 配合迭代器使用的
            //可以理解为 暂时返回 保留当前状态
            //C#语法糖
            yield return list[i];
        }
    }  
}

递归

递归

  • 递归就是自己调用自己
  • 必须有结束调用的条件
void Fun(int a){
	print(a);
	++a;
	Fun(a);
	return;
}

变量内存占用和取值范围

  • 基础数据类型取值范围
  • string不属于基础数据类型
关键字占用字节数取值范围
sbyte1-128~127
byte10~255
short2-32768~32767
ushort20~65535
int4-2147483648~2147483647
uint40~4292967295
long8-9223372036854775808~9223372036854775808
ulong80~18446744073709551615
char20~65535
float4存储7到8位有效数字 根据编译器不同 有效数字可能不一样
double8存储15~17位有效数字
bool(true,false)
decimal16存储27~28位有效数字

变长参数

边长参数

  • 在参数前添加param关键字 并存入数组中
  • 只可以有一个边长参数 并且必须放在最后一个 前面可以有多个参数
//可传入多个int类型的参数
static int Sum(params int[] arr){

}
Sum(1,2,3,4,5);

PlayPrefs封装

using System;
using System.Collections;
using System.Reflection;
using UnityEngine;

public class PlayerPrefsDataMgr
{
    private static PlayerPrefsDataMgr _instance = new PlayerPrefsDataMgr();
    public static PlayerPrefsDataMgr Instance
    {
        get
        {
            return _instance;
        }
        private set
        {
        }
    }

    private PlayerPrefsDataMgr()
    {

    }

    /// &lt;summary&gt;
    /// 保存数据
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;obj&quot;&gt;要保存的对象&lt;/param&gt;
    /// &lt;param name=&quot;key&quot;&gt;唯一值&lt;/param&gt;
    public void SaveData(object obj, string key)
    {
        Type type = obj.GetType();
        FieldInfo[] fields = type.GetFields();
        string keyName;
        for (int i = 0; i &lt; fields.Length; i++)
        {
            keyName = key + &quot;_&quot; + type.Name + &quot;_&quot; + fields[i].FieldType.Name + &quot;_&quot; + fields[i].Name;
            SaveValue(keyName, fields[i].GetValue(obj));
        }
        PlayerPrefs.Save();
    }

    /// &lt;summary&gt;
    /// 获得数据
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;type&quot;&gt;传入对象类型&lt;/param&gt;
    /// &lt;param name=&quot;key&quot;&gt;唯一值Type&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public object GetData(Type type, string key)
    {
        object obj = Activator.CreateInstance(type);
        FieldInfo[] fildinfo = type.GetFields();
        string keyName;
        foreach (var info in fildinfo)
        {
            keyName = key + &quot;_&quot; + type.Name + &quot;_&quot; + info.FieldType.Name + &quot;_&quot; + info.Name;
            Debug.Log(keyName);
            info.SetValue(obj, GetValue(info.FieldType, keyName));
        }
        return obj;
    }

    /// &lt;summary&gt;
    /// 把数据存入PlayerPrefs
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;keyName&quot;&gt;&lt;/param&gt;
    /// &lt;param name=&quot;value&quot;&gt;&lt;/param&gt;
    private void SaveValue(string keyName, object value)
    {
        Type type = value.GetType();
        if (type == typeof(string)) PlayerPrefs.SetString(keyName, value.ToString());
        else if (type == typeof(int)) PlayerPrefs.SetInt(keyName, (int)value);
        else if (type == typeof(float)) PlayerPrefs.SetFloat(keyName, (float)value);
        else if (type == typeof(bool)) PlayerPrefs.SetInt(keyName, (bool)value ? 1 : 0);
        else if (typeof(IList).IsAssignableFrom(type))
        {
            IList list = value as IList;
            PlayerPrefs.SetInt(keyName, list.Count);
            for (int i = 0; i &lt; list.Count; i++)
            {
                SaveValue(keyName + &quot;_&quot; + i, list[i]);
            }
        }
        else if (typeof(IDictionary).IsAssignableFrom(type))
        {
            IDictionary dict = value as IDictionary;
            PlayerPrefs.SetInt(keyName, dict.Count);
            var keys = dict.Keys;
            int index = 0;
            foreach (var key in dict.Keys)
            {
                SaveValue(keyName + &quot;_&quot; + index + &quot;_&quot; + &quot;key&quot;, key);
                SaveValue(keyName + &quot;_&quot; + index + &quot;_&quot; + &quot;value&quot;, dict[key]);
                index++;
            }
        }
        else
        {
            SaveData(value, keyName);
        }
    }

    /// &lt;summary&gt;
    /// 把数据从PlayerPrefs取出
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;type&quot;&gt;&lt;/param&gt;
    /// &lt;param name=&quot;key&quot;&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    private object GetValue(Type type, string key)
    {

        if (type == typeof(string)) return PlayerPrefs.GetString(key);
        else if (type == typeof(int)) return PlayerPrefs.GetInt(key);
        else if (type == typeof(float)) return PlayerPrefs.GetFloat(key);
        else if (type == typeof(bool)) return PlayerPrefs.GetInt(key) == 1;
        else if (typeof(IList).IsAssignableFrom(type))
        {
            IList list = Activator.CreateInstance(type) as IList;
            for (int i = 0; i &lt; PlayerPrefs.GetInt(key); i++)
            {
                list.Add(GetValue(type.GetGenericArguments()[0], key + &quot;_&quot; + i));
            }
            return list;
        }
        else if (typeof(IDictionary).IsAssignableFrom(type))
        {
            IDictionary dict = Activator.CreateInstance(type) as IDictionary;
            Type keyType = type.GetGenericArguments()[0];
            Type valueType = type.GetGenericArguments()[1];
            for (int i = 0; i &lt; PlayerPrefs.GetInt(key); i++)
            {
                dict.Add(GetValue(keyType, key + &quot;_&quot; + i + &quot;_&quot; + &quot;key&quot;), GetValue(valueType, key + &quot;_&quot; + i + &quot;_&quot; + &quot;value&quot;));
            }
            return dict;
        }
        else
        {
            return GetData(type, key);
        }
    }
}

nginx配置访问密码

  1. 用openssl生成密钥:
openssl passwd 12345
  1. 把密钥写入 htpasswd 文件,其中admin为用户名:
echo "admin:fIHcRVEKijgoM" > htpasswd
  1. 修改nginx配置
auth_basic "Please input password"; 
auth_basic_user_file /usr/share/nginx/passwd/htpasswd;

apt安装php7.4

安装php

sudo apt install php7.4-fpm
sudo apt install php7.4-{sqlite3,curl,sockets,mbstring,iconv}

调整php-fpm的进程数量

pm = static
pm.max_children = 2
pm.max_requests = 500

nginx开启php支持

location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

开启伪链

ssh配置密钥登陆

修改默认端口
nano /etc/ssh/sshd_config
#修改默认端口
Prot 25428
新建非root用户,禁用root登陆
adduser username
apt update && apt install sudo
visudo
#在 User Privilege Specification 下加入一行 username ALL=(ALL) NOPASSWD: ALL
username ALL=(ALL) NOPASSWD: ALL
nano /etc/ssh/sshd_config
#PermitRootLogin Yes改为no
PermitRootLogin no
systemctl restart ssh
使用密钥登陆,关闭密码登陆

运行PuTTYgen

协变和逆变

适用于泛型接口和泛型委托

协变:out修饰符修饰泛型参数 只能作为返回值类型

逆变:in修饰符修饰泛型参数 只能作为参数使用

协变和逆变在泛型委托函数中中:

协变:泛型委托参数为父类 装子类泛型委托

逆变:泛型委托参数为子类 装父类泛型委托

C# 虚函数

当子类继承父类,子类重写父类的方法时,如果使用父类装子类对象 调用重写的方法时 会执行父类中的方法。

可将父类的函数声明为虚函数,子类使用override关键字重写父类方法,调用重写的方法时 会执行子类中的方法。

using System;

namespace csharpleran
{
    struct StructTest
    {

        public class Fater {
            public Fater(int i) {
                Console.WriteLine(i);
            }

            public virtual void Eat(string str) {
                Console.WriteLine("Fater virtual Eat");
            }

            public void Eat() {
                Console.WriteLine("Fater Eat");
            }
        }

        public class Player : Fater {

            /// <summary>
            /// 子类调用父类的构造函数
            /// </summary>
            /// <param name="i"></param>
            public Player(int i):base(i)
            {
                Console.WriteLine("子类子类子类");
            }

            /// <summary>
            /// 调用重载构造函数
            /// </summary>
            public Player() : this(1) { 
                
            
            }

            public override void Eat(string str)
            {
                Console.WriteLine("Player override Eat");
            }

            public new void Eat()
            {
                Console.WriteLine("Player Eat");
            }
        }

        class Program
        {
         
            static void Main(string[] args)
            {
                Fater f = new Player();
                f.Eat(); //Fater Eat
                (f as Player).Eat();//Player Eat
                f.Eat("");//Player override Eat
                (f as Fater).Eat("");//Player virtual Eat

            }
        }
    }
}

as和in在继承中的作用

is 返回一个bool 判断一个对象是否是指定类的对象 as可以将一个对象转换为指定类型的对象(用于父类容器 装子类对象 可通过as调用子类对象中的方法)