C#高级特性拾遗
杂项
Conditional("some condition")
可以优雅地替代宏定义泛型类中的static字段和非泛型类的字段有所区别,后者中该static字段属于该类,而前者中该static字段仅属于该泛型类,例如
Generic<A>.staticField
和Generic<B>.staticField
是不同的。对于这个特性,可以有比较进阶的用法,例如:1
2
3
4
5
6
7public 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
3var 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
7public 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> |