泛型

  • 泛型类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //声明泛型类 T 表示一个占位 也可以声明多个
    public class TTest<T>
    {
    public T value;
    }

    private void Start()
    {
    TTest<int> testint = new TTest<int>();
    testint.value = 10;

    TTest<string> teststr = new TTest<string>();
    teststr.value = "aa";
    }
  • 泛型方法

    1
    2
    3
    public void TestFun<T>(T t)
    {
    }
  • 泛型接口

  • 泛型约束

    where

    有6种

    1. where T : struct类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class TTest<T> where T: struct
    {
    public T value;
    }

    private void Start()
    {
    TTest<int> testint = new TTest<int>(); //ok
    testint.value = 10;

    TTest<object> teststr = new TTest<object>(); //不通过
    teststr.value = "aa";
    }
    1. where T :Class类型参数必须是引用类型,包括任何类、接口、委托或数组类型

      ……

    2. where T: new()类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class TTest<T> where T: new()
    {
    public T value;
    }
    public class T1
    {
    //默认有一个无参的构造函数
    }
    public class T2
    {
    //声明了别的构造函数 默认的无参构造函数就没了
    public T2(int a)
    {

    }
    }

    private void Start()
    {
    TTest<T1> test1 = new TTest<T1>();

    TTest<T2> test2 = new TTest<T2>(); //错误
    }
    1. where T: 基类名 类型参数必须是指定的基类或派生自指定的基类。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class TTest<T> where T: MonoBehaviour
    {
    public T value;
    }
    public class T1:MonoBehaviour
    {

    }
    public class T2
    {

    }
    private void Start()
    {
    TTest<T1> test1 = new TTest<T1>();

    TTest<T2> test2 = new TTest<T2>(); //错误 不是派生子MonoBehaviour
    }
    1. where T 接口名 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

    2. where T U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class TTest<T,U> where T: U
    {
    public T value;
    }
    public class T1:MonoBehaviour
    {

    }
    public class T2
    {

    }
    private void Start()
    {
    TTest<T1,MonoBehaviour> test1 = new TTest<T1,MonoBehaviour>();

    TTest<T2,MonoBehaviour> test2 = new TTest<T2, MonoBehaviour>(); //错误 不是派生子MonoBehaviour
    }

反射

BindingFlags 枚举类型
BindingFlags.IgnoreCase 表示忽略 name 的大小写,不应考虑成员名的大小写
BindingFlags.DeclaredOnly 只应考虑在所提供类型的层次结构级别上声明的成员。不考虑继承成员。
BindingFlags.Instance 只搜索实例成员
BindingFlags.Static 只搜索静态成员
BindingFlags.Public 只搜索公共成员
BindingFlags.NonPublic 只搜索非公共成员
BindingFlags.FlattenHierarchy 应返回层次结构上的公共静态成员和受保护的静态成员。不返回继承类中的私有静态成员。静态成员包括字段、方法、事件和属性。不返回嵌套类型。
BindingFlags.InvokeMethod 表示调用方法,而不调用构造函数或类型初始值设定项。对 SetField 或 SetProperty 无效。
BindingFlags.CreateInstance 表示调用构造函数。忽略 name。对其他调用标志无效。
BindingFlags.GetField 表示获取字段值
BindingFlags.SetField 表示设置字段值。
BindingFlags.GetProperty 表示获取属性。
BindingFlags.SetProperty 表示设置属性。

BindingFlags.Public|BindingFlags.Instance 默认查找public、instance内容
BindingFlags.NonPublic|BindingFlags.Instance 查找nonpublic、instance内容
BindingFlags.Instance和BindingFlags.Static二者必须有一项或者都有。如果你的类是instance,就选instance,反之选static。如果两者都不选,是找不到任何方法的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
using UnityEngine;
using System;
using TestType;
using System.Reflection;
public class Test : MonoBehaviour
{
private void Start()
{
MyTest myTest = new MyTest(); //实例对象

//获取类信息
Type type1 = typeof(MyTest);
Type type2 = (new MyTest()).GetType();
Type type3 = Type.GetType("TestType.MyTest"); //命名空间+类名

#region 获取类型的属性
Debug.Log(type1.Name);
Debug.Log(type1.IsAbstract);//指示该类型是否是抽象类型
Debug.Log(type1.IsClass); //是否是类
#endregion

#region 获取类中信息
/* GetConstructor(), GetConstructors():返回ConstructorInfo类型, 用于取得该类的构造函数的信息
GetEvent(), GetEvents():返回EventInfo类型,用于取得该类的事件的信息
GetField(), GetFields():返回FieldInfo类型,用于取得该类的字段(成员变量)的信息
GetInterface(), GetInterfaces():返回InterfaceInfo类型,用于取得该类实现的接口的信息
GetMember(), GetMembers():返回MemberInfo类型,用于取得该类的所有成员的信息
GetMethod(), GetMethods():返回MethodInfo类型,用于取得该类的方法的信息
type1.GetEnumName :返回枚举
GetProperty(), GetProperties():返回PropertyInfo类型,用于取得该类的属性的信息可以调用这些成员,其方式是调用Type的InvokeMember()方法,或者调用MethodInfo, PropertyInfo和其他类的Invoke()方法。*/

MemberInfo[] members = type1.GetMembers(); //获取所有成员
type1.GetConstructors(); //获取所有构造函数


//获取无参数构造函数
ConstructorInfo constructorInfo = type1.GetConstructor(new Type[0]); //要传入一个tpye数组 如果没有参数就是0
//调用构造函数 实例化一个对象
var obj = constructorInfo.Invoke(null); //没有参数写null
MyTest myt = obj as MyTest;
Debug.Log($"---{myt.publicnum2}");

//获取有参数的构造函数
ConstructorInfo constructorInfo2 = type1.GetConstructor(new Type[] { typeof(string), typeof(int) }); //要传入一个tpye数组 参数为对应构造函数的参数
var obj2 = constructorInfo2.Invoke(new object[] { "aaa", 2 }) as MyTest; //传入对应实参
Debug.Log($"---{obj2.publicnum2}");

//获取成员变量
type1.GetFields();
var num = type1.GetField("num2");
var num1 = type1.GetField("num1", BindingFlags.Instance | BindingFlags.NonPublic); //反射私有成员变量
Debug.Log(num1.GetValue(myTest)); //必须要设置获取对象
num1.SetValue(myTest, 22); //设置值


//获取成员方法
type1.GetMethods();
MethodInfo methodInfo = type1.GetMethod("DoDebug", new Type[] { typeof(int) });
methodInfo.Invoke(myTest, new object[] { 123 });
//如果是静态方法,第一个方法传入null methodInfo.Invoke(null, new object[] { 123 });
#endregion

#region Activator
//Activator帮助我们快捷创建对象 快捷创建一个 无参数构造函数
MyTest myTest1 = Activator.CreateInstance(typeof(MyTest)) as MyTest;
//有参数 直接后面传入对应参数
myTest1 = Activator.CreateInstance(typeof(MyTest),"str",10) as MyTest;
#endregion

#region Assembly
//...
#endregion
}
}
namespace TestType
{
public class MyTest
{
public const string constStr = "bbb";
public static int staticNum = 10;

public int publicnum2 = 1;
public string publicstr = "aaa";

private int num1 = 100;
public int PropertyNum3 { get; set; } = 2;

public MyTest()
{

}

public MyTest(int i)
{
Debug.Log($"{nameof(MyTest)} 构造函数打印参数: {i}");
}
public MyTest(string str, int i) : this(i) //this:调用该构造函数时候 先调用上一个构造函数
{
Debug.Log($"{nameof(MyTest)} 构造函数打印参数: {i} _{str}");
}

public void DoDebug(int i)
{
Debug.Log($"{nameof(MyTest)} DoDebug方法打印参数: {i}");
}
}
}

C#反射机制 - 知乎 (zhihu.com)

值类型和引用类型

  1. 变量声明周期

{}包裹的变量 对于值类型 执行完 就会清空栈里面的值类型数据 对于引用类型会断开与堆中链接 等待下次GC回收

1
2
3
4
5
6
7
8
{
int a=10; //执行完毕会立即回收清除
string str="aa"; //栈上清除了 堆上还存在等待GC
}
while(true)
{
int a=10; //这里在栈中会一直出栈 又新的压入栈
}
  1. 值类型在内存中

    值类型分配在内存栈中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public struct TestStruct
    {
    public int a;
    public object obj;
    }
    class Program
    {

    static void Main(string[]args)
    {
    TestStruct tc=new TestStruct();
    }
    }

    上一段代码 Struct 在内存中

    image-20211028142635452

  2. 引用类型在内存中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class TestClass
    {
    public int a=10;
    public object obj=new object();
    public TestStruct ts=new TestStruct();
    }
    class Program
    {

    static void Main(string[]args)
    {
    TestClass tc=new TestClass();
    }
    }

    image-20211028143344536

    数组也是引用类型

    1
    2
    int []nums=new int[5];
    object[]objs=new object[5];

    image-20211028144044738

  3. struct 在继承接口进行里氏转换的时候会装修拆箱

    原因是因为接口是引用类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    interface IStruct
    {
    int Value { get; set; }
    }

    public struct TestStruct: IStruct
    {
    public int Value {
    get;set;
    }
    }
    TestStruct ts1 = new TestStruct();
    ts1.Value = 10;
    TestStruct ts2 = ts1;
    ts2.Value = 100;
    Debug.Log($"ts1:{ts1.Value} ts2:{ts2.Value}"); //10 100

    IStruct Its1 = ts1;
    Its1.Value = 10;
    IStruct Its2 = Its1;
    Its2.Value = 100;
    Debug.Log($"ts1:{Its1.Value} ts2:{Its2.Value}"); //100 100

异步编程

  1. 异步编程不能加快目标代码的运行效率
  2. 异步方法不等于多线程
  3. 异步方法一般返回值是Task<T>Task,方法名以Async结尾
  4. 调用异步方法一般在前面加上await 用于取得放回值
  5. 异步方法具有传染性,一个方法中如果要用await,则这个方法必须要用async修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Program
{
static void Main(string[] args)
{
Start();
Console.ReadKey();
}
static async void Start()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);//1
string s = await TestRead();
string s2 = await TestRead2();
}

static async Task<string> TestRead()
{
//用await获取结果
string str = await File.ReadAllTextAsync(@"c:/temp/1.txt");
//一般不用
//var task = File.ReadAllTextAsync(@"c:/temp/1.txt");
//string s = task.Result;
return str;
}
//不用await 包装
static Task<string> TestRead2()
{
return File.ReadAllTextAsync(@"c:/temp/1.txt");
}
}

死锁情况:使用 Task.Wait()?立刻死锁(deadlock) - walterlv

明确了会造成死锁的条件和不会造成死锁的条件后,我们只需要做到以下几点即可避免死锁了:

  1. 在 UI 线程,如果使用了 async/await,就尽量不要再使用 Task.Wait()/Task.Result 了,就一直异步一条路走到黑好了(微软称其为 Async All the Way)。
  2. 如果可能,尽量在异步任务后添加 .ConfigureAwait(false);这样,异步任务后面继续执行的代码就不会回到原 UI 线程了,而是直接从线程池中再取出一个线程执行;这样,即便 UI 线程后续可能有别的原因造成阻塞,也不会产生死锁了。

第一段代码,基于.net core 3.1 windows窗口程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Program
{
static void Main(string[] args)
{
Start();
Console.ReadKey();
}
static void Start()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);//1
Test();
}

static async void Test()
{
await Task.Delay(TimeSpan.FromSeconds(1f));
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);//可能1,2,3,4

}
}

第二段,Untiy中编写的

1
2
3
4
5
6
7
8
9
10
11
void Start()
{
Debug.Log(Thread.CurrentThread.ManagedThreadId);//1
Test();
}

static async void Test()
{
await Task.Delay(TimeSpan.FromSeconds(1f));
Debug.Log(Thread.CurrentThread.ManagedThreadId);//1 和上面一样
}

为什么会有这两种情况

是因为Unity在框架中实现了一个UnitySynchronizationContext的类,该类继承SynchronizationContext

ConfigureAwait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async void Start()
{
Debug.Log("id1:" + Thread.CurrentThread.ManagedThreadId); //1
await Test();
Debug.Log("id3:" + Thread.CurrentThread.ManagedThreadId);//1
}
static async Task Test()
{
await Task.Run(() =>
{
Debug.Log("id2:" + Thread.CurrentThread.ManagedThreadId); //其他 线程
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000000; i++)
{
sb.Append("xxxxxxxxxxxxx");
}
File.WriteAllText(@"C:\Users\Administrator\Desktop\1.txt", sb.ToString());
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async void Start()
{
Debug.Log("id1:" + Thread.CurrentThread.ManagedThreadId); //1
await Test().ConfigureAwait(false);
Debug.Log("id3:" + Thread.CurrentThread.ManagedThreadId);//其他 线程
}
static async Task Test()
{
await Task.Run(() =>
{
Debug.Log("id2:" + Thread.CurrentThread.ManagedThreadId); //其他 线程
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000000; i++)
{
sb.Append("xxxxxxxxxxxxx");
}
File.WriteAllText(@"C:\Users\Administrator\Desktop\1.txt", sb.ToString());
});
}

await 后面需要接的类型是 TaskAwaiter(INotifyCompletion) 可等待对象,也就是说要实现await后面必须要返回一个可等待对象实例

Unity 中的 async-await 咋用_东北砍王的栖息地-CSDN博客

什么是TaskCompletionSource

TaskCompletionSource是一个可以用来创建Task并且手动进行管理的类,我们可以手动设置结束(setResult()),或者取消(SetCancel()),或者设置异常,是一个包装类.

TaskCompletionSource这是一种受你控制创建Task的方式。你可以使Task在任何你想要的时候完成,你也可以在任何地方给它一个异常让它失败。

他并不是真的启动了一个新的线程,这要看开发者是怎么处理的

什么是synchronizationContext

同步上下文,利用此对象可以实现线程间数据的同步、异步访问。包含一个Send 和一个Post

send是一个同步方法 实际就是调用了 回调

Post是一个异步,会从其他线程中去调用,然后返回到当前线程中