C#高级特性拾遗
杂项
- Conditional("some condition")可以优雅地替代宏定义
- 泛型类中的static字段和非泛型类的字段有所区别,后者中该static字段属于该类,而前者中该static字段仅属于该泛型类,例如 - Generic<A>.staticField和- Generic<B>.staticField是不同的。对于这个特性,可以有比较进阶的用法,例如:- 1 
 2
 3
 4
 5
 6
 7- public class Foo { 
 public static int lastId;
 }
 public class Foo<T> : Foo {
 public static int id = ++lastId;
 }- 那么对于每一个 - Foo<T>的子类,启id都是唯一的,从而实现id自我管理(而这里的泛型T只是被借壳而已,没有任何用处)。
- 使用 - Type.MakeGenericType,可以在运行时生成泛型类,这是一个java不具备的特性,举例:- 1 
 2
 3- var type = typeof(Dictionary<,>); 
 Type[] typeArgs = {typeof(string), typeof(int)};
 var genericType = type.MakeGenericType(typeArgs)
- default关键字:在泛型类和泛型方法中产生的一个问题,给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。 解决方案是使用default 关键字,此关键字对于引用类型会返回null,对于数值类型会返回零。 
- 对于 - ManualResetEvent:- 可以通过构造函数初始化为有信号或无信号状态。
- 当且仅当ManualResetEvent处于无信号状态时(也即Set没有被调用或者初始化为无信号状态),其他线程调用了ManualResetEvent.WaitOne才会被阻塞。
- 调用ManualResetEvent.Reset可以阻塞所有其他调用了ManualResetEvent.WaitOne的线程
- 应用场景:工作A和B需要并发执行,但是需要A执行完毕后再执行B。最佳实践:a、A线程(称之为ManualResetEvent控制线程)调用ManualResetEvent.Reset使之进入非信号状态;b、B线程(称之为被控制线程或等待线程?阻塞线程?)在适当位置调用ManualResetEvent.WaitOne等待信号;c、A线程调用ManualResetEvent.Set通知B线程继续执行。
 
- 可以通过实现一个public的无参的返回 - IEnumerator的- GetEnumerator()
 为一个类扩展foreach语法,例如(详情):- 1 
 2
 3
 4
 5
 6
 7- public IEnumerator<Tile> GetEnumerator() { 
 for(int x = 0; x < this.size.x; x++) {
 for(int y = 0; y < this.size.y; y++) {
 yield return this[x, y];
 }
 }
 }
GetAwaiter()
- 在C#中,任意类只要实现了:GetAwaiter()函数,就可以被await关键字异步等待由于1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19public struct Delay 
 {
 public TimeSpan TimeSpan {get;}
 private Delay(TimeSpan timeSpan)
 {
 TimeSpan = timeSpan;
 }
 public static Delay Second(int seconds)
 {
 return new Delay(TimeSpan.FromSeconds(seconds));
 }
 public TaskAwaiter GetAwaiter()
 {
 return Task.Delay(TimeSpan).GetAwaiter();
 }
 }Delay实现了GetAwaiter方法,现在就可以使用:await Delay.Seconds(2);而在实际应用中,可以使用C#的扩展功能对Delay进行扩展,而不必侵入修改,例如:这意味着:可以使用扩展的方式可以实现对任意类的1 
 2
 3
 4
 5
 6
 7public static class Extensions 
 {
 public static TaskAwaiter GetAwaiter(this Delay delay)
 {
 return Task.Delay(delay.TimeSpan).GetAwaiter();
 }
 }await异步等待,基于该理念,Delay这个结构体已经没有存在的必要,因为可以对TimeSpan进行扩展,例如:而现在可以使用:1 
 2
 3
 4
 5
 6
 7public static class Extensions 
 {
 public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
 {
 return Task.Delay(timeSpan).GetAwaiter();
 }
 }await TimeSpan.FromSeconds(2);更加简洁的一个操作是继续对int进行扩展,例如:现在就可以使用:1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12public static class Extensions 
 {
 public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
 {
 return Task.Delay(timeSpan).GetAwaiter();
 }
 public static TimeSpan Seconds(this int integer)
 {
 return TimeSpan.FromSeconds(integer);
 }
 }await 3.Seconds();了
required关键字
required关键字子在C# 11中被引入,表示字段必须由构造函数或对象初始化器进行初始化:
| 1 | public required string Title {get; init;} | 
尽量避免使用Async void方法
在下面代码中,如果ProcessDataAsync()方法中抛出异常,HandleButtonClick无法正常捕获!而如果把ProcessDataAsync的返回值改成async Task则可以正常捕获:
| 1 | public async void ProcessDataAsync() | 
接口中的静态方法
接口中的静态方法
C# 8.0引入了接口的静态方法,该静态方法只能通过接口访问(不能通过实现类访问)。接口的静态方法具有很大的局限性,一般的应用场景是在泛型接口上使用静态方法,举例如下:
| 1 | //约束T必须实现IDeserializable | 
在上述代码中,虽然接口的静态方法的使用场景具有局限性,但是我们同时也对泛型接口的类型T进行了约束。通过该约束,是的泛型接口的静态方法具备了实现基础,此时我们可以对Student类进行反序列化,如下所示:
| 1 | var student = IDeserializable<Student>.Deserialize("{ \"Id\" : 42, \"Name\" : \"Tom\" }"); | 
接口中的抽象静态方法
C# 10.0 进一步引入了抽象静态方法,在工厂设计模式中可以使用抽象静态方法,例如:
| 1 | interface IFactory<T> | 
这要求所有实现了IFactory<T>泛型接口的类都要实现  Student 抽象静态方法。另外一种应用场景是单例模式,例如:
| 1 | interface ISingleton<T> where T : ISingleton<T> |