C#高级特性拾遗

杂项

  • Conditional("some condition") 可以优雅地替代宏定义

  • 泛型类中的static字段和非泛型类的字段有所区别,后者中该static字段属于该类,而前者中该static字段仅属于该泛型类,例如Generic<A>.staticFieldGeneric<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

    1. 可以通过构造函数初始化为有信号或无信号状态。
    2. 当且仅当ManualResetEvent处于无信号状态时(也即Set没有被调用或者初始化为无信号状态),其他线程调用了ManualResetEvent.WaitOne才会被阻塞。
    3. 调用ManualResetEvent.Reset可以阻塞所有其他调用了ManualResetEvent.WaitOne的线程
    4. 应用场景:工作A和B需要并发执行,但是需要A执行完毕后再执行B。最佳实践:a、A线程(称之为ManualResetEvent控制线程)调用ManualResetEvent.Reset使之进入非信号状态;b、B线程(称之为被控制线程或等待线程?阻塞线程?)在适当位置调用ManualResetEvent.WaitOne等待信号;c、A线程调用ManualResetEvent.Set通知B线程继续执行。
  • 可以通过实现一个public的无参的返回IEnumeratorGetEnumerator()
    为一个类扩展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
    19
    public 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
    7
    public 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
    7
    public 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
    12
    public 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public async void ProcessDataAsync()
{
// Some asynchronous operations
}

public async void HandleButtonClick(object sender, EventArgs e)
{
try
{
ProcessDataAsync();
}
catch (Exception ex)
{
// This will never catch the async exceptions!
}
}

接口中的静态方法

接口中的静态方法

C# 8.0引入了接口的静态方法,该静态方法只能通过接口访问(不能通过实现类访问)。接口的静态方法具有很大的局限性,一般的应用场景是在泛型接口上使用静态方法,举例如下:

1
2
3
4
5
6
7
8
9
10
11
//约束T必须实现IDeserializable
interface IDeserializable<T> where T : IDeserializable<T>
{
static T? Deserialize(string json) => JsonSerializer.Deserialize<T>(json);
}

class Student : IDeserializable<Student>
{
public int Id { get; set; }
public string Name { get; set; }
}

在上述代码中,虽然接口的静态方法的使用场景具有局限性,但是我们同时也对泛型接口的类型T进行了约束。通过该约束,是的泛型接口的静态方法具备了实现基础,此时我们可以对Student类进行反序列化,如下所示:

1
var student = IDeserializable<Student>.Deserialize("{ \"Id\" : 42, \"Name\" : \"Tom\" }");

接口中的抽象静态方法

C# 10.0 进一步引入了抽象静态方法,在工厂设计模式中可以使用抽象静态方法,例如:

1
2
3
4
interface IFactory<T>
{
static abstract T Create();
}

这要求所有实现了IFactory<T>泛型接口的类都要实现 Student 抽象静态方法。另外一种应用场景是单例模式,例如:

1
2
3
4
5
6
7
8
9
interface ISingleton<T> where T : ISingleton<T>
{
static abstract T Instance { get; }
}

class SingletonClass : ISingleton<SingletonClass>
{
public static SingletonClass Instance => new();
}