C#知识总结
泛型
泛型类
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
3public void TestFun<T>(T t)
{
}泛型接口
泛型约束
where
有6种
where T : struct
类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。
1
2
3
4
5
6
7
8
9
10
11
12
13public 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";
}where T :Class
类型参数必须是引用类型,包括任何类、接口、委托或数组类型……
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
23public 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>(); //错误
}where T: 基类名
类型参数必须是指定的基类或派生自指定的基类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public 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
}where T 接口名
类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。where T U
为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public 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 | using UnityEngine; |
值类型和引用类型
- 变量声明周期
用{}
包裹的变量 对于值类型
执行完 就会清空栈里面的值类型数据 对于引用类型
会断开与堆中链接 等待下次GC回收
1 | { |
值类型在内存中
值类型分配在内存栈中
1
2
3
4
5
6
7
8
9
10
11
12
13public struct TestStruct
{
public int a;
public object obj;
}
class Program
{
static void Main(string[]args)
{
TestStruct tc=new TestStruct();
}
}上一段代码 Struct 在内存中
引用类型在内存中
1
2
3
4
5
6
7
8
9
10
11
12
13
14public 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();
}
}数组
也是引用类型1
2int []nums=new int[5];
object[]objs=new object[5];struct 在继承接口进行里氏转换的时候会装修拆箱
原因是因为接口是引用类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22interface 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
异步编程
- 异步编程不能加快
目标代码
的运行效率 - 异步方法不等于多线程
- 异步方法一般返回值是
Task<T>
或Task
,方法名以Async
结尾 - 调用异步方法一般在前面加上
await
用于取得放回值 - 异步方法具有
传染性
,一个方法中如果要用await
,则这个方法必须要用async
修饰
1 | class Program |
死锁情况:使用 Task.Wait()?立刻死锁(deadlock) - walterlv
明确了会造成死锁的条件和不会造成死锁的条件后,我们只需要做到以下几点即可避免死锁了:
- 在 UI 线程,如果使用了
async
/await
,就尽量不要再使用Task.Wait()
/Task.Result
了,就一直异步一条路走到黑好了(微软称其为 Async All the Way)。- 如果可能,尽量在异步任务后添加
.ConfigureAwait(false)
;这样,异步任务后面继续执行的代码就不会回到原 UI 线程了,而是直接从线程池中再取出一个线程执行;这样,即便 UI 线程后续可能有别的原因造成阻塞,也不会产生死锁了。
第一段代码,基于.net core 3.1 windows窗口程序
1 | class Program |
第二段,Untiy中编写的
1 | void Start() |
为什么会有这两种情况
是因为Unity在框架中实现了一个UnitySynchronizationContext
的类,该类继承SynchronizationContext
ConfigureAwait
1 | async void Start() |
1 | async void Start() |
await 后面需要接的类型是 TaskAwaiter(INotifyCompletion)
可等待对象,也就是说要实现await
后面必须要返回一个可等待对象实例
Unity 中的 async-await 咋用_东北砍王的栖息地-CSDN博客
什么是TaskCompletionSource
TaskCompletionSource是一个可以用来创建Task并且手动进行管理的类,我们可以手动设置结束(setResult()),或者取消(SetCancel()),或者设置异常,是一个包装类.
TaskCompletionSource
他并不是真的启动了一个新的线程,这要看开发者是怎么处理的
什么是synchronizationContext
同步上下文,利用此对象可以实现线程间数据的同步、异步访问。包含一个Send
和一个Post
send
是一个同步方法 实际就是调用了 回调
Post
是一个异步,会从其他线程中去调用,然后返回到当前线程中