C# 11

前言

.NET 7 来到 RC 阶段了. C# 11 也伴随而来咯, 这篇看看有哪些可能会用上的好功能呗.

 

参考

What's new in C# 11

 

Generic attributes

参考: Docs – Generic attributes

在 C# 11.0 以前, Attribute 是无法使用 Generic 的. 

当需要传递类型时, 只能给予 Type, 然后通过反射达到目的, 这样就少了类型提示.

C# 11.0 支持了这个功能

before

public class Animal {
    public string Name { get; set; } = "";
}

[AttributeUsage(AttributeTargets.Class)]
public class TestAttribute : Attribute
{
    public TestAttribute(Type type) { }
}

[Test(typeof(Animal))]
public class Person
{

}

after

public class Animal {
    public string Name { get; set; } = "";
}

[AttributeUsage(AttributeTargets.Class)]
public class TestAttribute<T> : Attribute where T : Animal
{
    
}

[Test<Animal>()]
public class Person
{

}

limitation

虽然说支持 Generic, 但也不是完全. 过往可以用 typeof(AnyType) 的地方都可以替换成 Generic, 但是如果 typeof 不支持的, 那就不行了.

比如 typeof(string?), typeof(dynamic)

 

Newlines in string interpolations

参考: Docs – Newlines in string interpolations

在 C# – 6.0, 7.0, 8.0, 9.0 总结 有提到过. 6.0 的功能, 但它有一些 limitation. 11.0 算是优化了一些.

before

不能使用 new line, 除非加上 @

var value = $@"{
    (true ? "a" : "b")
}";

after

11.0 支持了 new line. 但也仅限于花括弧 {}, string 依然不可以 new line 需要配合 @ 才行

var value = $"{
    (true ? "a" : "b")
}";

 

Raw string literals

参考: Docs – Raw string literals

C# 写 JSON 是很难看的.

before

var json1 = @"{
    ""name"" : ""Derrick""  
}";
var json2 = "{ \"name\": \"Derrick\" }";

要嘛用反斜杠 \", 要嘛用 @ double ", 不管这样都破坏了内容

C# 11.0 有了新语法

after

var json = """
    { 
        "name" : "Derrick",
        "age" : 10
    }
""";

使用 triple ", 不需要反斜杠, 也不需要 @ 了.

它的好处就是只破坏了开头和结尾, 但内容是正确的. 

p.s. 花括弧必须是 new line 哦, 像下面这样是错误的. 

另外, 你甚至可以用更多的 ", 比如 4 个 """" .... """" 那么, 内容就可以使用到 triple """. 这个设计相当灵活. (开头结尾最少是 triple ", double 不够哦)

配搭 interpolations

var json = $$"""
    { 
        "name" : "{{ value }}",
        "age" : 10
    }
""";

double $ 表示, 当出现 double {{ }} 的时候才是 interpolations. 也可以声明更多, 比如 triple $ 对应的就是 {{{ }}} 才是 interpolations.

这个写法估计未来会成为主流, 以后应该不再需要使用 @, double ", 反斜杠 \ 了.

总结

    public IndexModel(ILogger<IndexModel> logger)
    {
        // 第一种
        var newLine1 = "Title\r\nDescription";
        var quote1 = "key=\"value\"";

        // 第二种
        var newLine2 = @"Title
Description";
        var quote2 = @"key=""value""";

        // 第三种
        var newLine3 = """
Title
Description
""";
        var quote3 = """
key="value"
""";
    }

第二种 new line 要注意空格哦, 去掉空格后就会靠右很多, 很丑, 而且 new line 是 \r\n 而不是 \n 哦.

我觉得嘛...如果空格不是困扰的话, 第 2, 3 是很好的.

 

List patterns

参考: Docs – List patterns

在 C# – 10.0 我有提到了 Pattern Matching 的使用.

C# 11.0 提升了 matching 的能力, 现在连 Array List 都可以 matching 了.

它有点像 TypeScript 的 extend 配上 Tuple

simple match (constant pattern)

var values = new string[] { "a", "b", "c" };
var yes = values is ["a", "b", "c"]; // true

List 也是可以

var values = new List<string> { "a", "b", "c" };
var yes = values is ["a", "b", "c"]; // true

greater than match

除了简单的 equal match, 其它 operator 也是可以拿来 match 的, 比如 >=, 是不是很牛?

var values = new List<int> { 1, 2, 3 };
var yes = values is [1, 2, >= 3]; // true

skip single match (discard pattern)

underscore 表达 skip 掉, 不管什么值都可以

var values = new List<string> { "a", "b", "c" };
var yes = values is ["a", _, "c"]; // true

skip multiple match (range pattern)

通过 double dot 点点, 可表达 skip 掉 0 到多个 值匹配.

var values = new List<int> { 1, 2, 3, 4 };
var yes = values is [1, .., >= 4]; // true

上面这个表示, 开头是 1, 最后是 >=4, 中间是什么, 有没有, 有多少值都无所谓.

get var from list (var pattern)

通过 var 关键字, 可以提取变量. 类似 TypeScript 的 infer.

var values = new List<int> { 1, 2, 3 };
if (values is [var first, _, >= 3]) {
    Console.WriteLine(first); // 1
}

还可以拿 multiple values 哦

var values = new[] { 1, 2, 3, 4, 5 };
if (values is [1, 2, .. var vals])
{
    Console.WriteLine(vals);
}

limitation

List 无法拿 multiple values

match 和 var 不可以同时使用

 

Required Property

当我们声明一个 property setter = init 时, 如果没有给予一个 Constructor 或者 default value 那么它会出现提示.

before

解决方式

public class Person
{
    public Person() 
    {
        Name = ""; // 1
    }
    public string Name { get; init; } = ""; // 2
    public string Name { get; init; } = null!; // 3
}

这 3 种写法都可以移除提示. 但是呢, 这不够好. 因为 setter init 只要在 new Class 的时候给予值就可以了.

所以下面这个方式也应该可以移除提示才对.

但很遗憾 C# 11.0 之前没有任何方法做到这个.

after

在 C# 11.0 多了一个叫 required 的关键字

当声明 required 以后, 在 new Class 的时候如果没有给予 init value 那么就会报错.

像这样给予 init value 后就不会报错了.

var person = new Person { 
    Name = ""
};

另外, required 并不能靠 Construtor 和 default value 去除哦 

所以 required 并不是拿来替代原本的方案的. 它应该只用来声明那些在 new Class 需要给予 init value 的 property.

SetsRequiredMembers Attribute 可以用来声明 Constructor 给予了所有 required property 值. (我目前还没有明白它的使用场景. 以后再补上)

public class Person
{
    [SetsRequiredMembers]
    public Person() 
    {
        Name = "dada";
    }

    public required string Name { get; init; }
}

public class Program
{
    public static void Main() 
    {
        var person = new Person(); // 不报错
    }
}

 

Regular Expression Improvements (.NET 7)

before

var numberRegex = new Regex("\\d");
var matched1 = numberRegex.Match("123").Success; // true
var matched2 = numberRegex.Match("abc").Success; // false

现在 Visual Studio 会提示不要这样写, 要改成 compile time 写法

after

public static partial class Program
{
    public static void Main()
    {
        var numberRegex = NumberRegex();
        var matched1 = numberRegex.Match("123").Success; // true
        var matched2 = numberRegex.Match("abc").Success; // false
    }

    [GeneratedRegex("\\d")]
    private static partial Regex NumberRegex();
}

注: 只有这种 hardcode 的正则可以用新写法, 如果是拼接的就只能 runtime

欲知详情看这篇: Regular Expression Improvements in .NET 7

 

INumber (.NET 7) & Interface Static Abstract

参考: YouTube – The weirdest C# 11 feature but also the best one

假设有一个方法, 参数是 int, 那么变量 double 就无法放进去

C# 有很多数字类型, double, float, int. 那有没有一个抽象的写法呢? 不管什么 number 都都可以调用这个方法, 然后累加 1 并返回.

public static void Main() 
{
    double age = 10d;
    var newAge = Method(age);

    T Method<T>(T age) where T : INumber<T> {
        return age + T.One;
    }
}

首先是把 int 换成 generic T, 然后声明 T 必须是 INumber (这个是 .NET 7 才有的)

interface static abstract

return age + 1 是不 ok 的, 必须利用 C# 11.0 的新特性 T.One

它是 interface static abstract property, 大概长这样

interface MyInterface<T> 
{ 
    static abstract T Value { get; }
}

实现 MyInterface 接口的类, 必须实现 static T Value. 于此同时 generic 就可以像 T.One 那样调用接口的静态函数了.

 

LINQ Order & OrderDescending (.NET 7)

只是一个语法糖

public static void Main() 
{
    var values = new int[] { 2, 5, 1, 4, 3 };
        
    var before1 = values.OrderBy(v => v);
    var after1 = values.Order(); // 1, 2, 3, 4, 5

    var before2 = values.OrderByDescending(v => v);
    var after2 = values.OrderDescending(); // 5, 4, 3, 2, 1
}

 

Microseconds and Nanoseconds (.NET 7)

参考:

Microseconds and Nanoseconds available in .NET 7

Docs – Adding Microseconds and Nanoseconds to TimeStamp, DateTime, DateTimeOffset, and TimeOnly

milli, micro, nano, tick

先讲讲基础, 我一路以来都只处理到 millisecond 而已, 没有玩过 micro 和 nano.

1 second = 1000 milliseconds 毫秒

1 millisecond = 1000 microsecond 微秒

1 microsecond = 1000 nanoseconds 纳秒

1 tick = 100 nanoseconds

1 microsecond = 10 tick

Get micro, nano from tick

在 .NET 7 以前, 要处理 micro 和 nano 只能依靠 tick.

var date1 = new DateTimeOffset(2022, 10, 3, 16, 46, 23, 123, TimeSpan.FromHours(8)); // 初始化只能设置到 Millisecond 哦
var date = DateTimeOffset.Parse("2022-10-03T16:46:23.123456793Z"); // 解析可以到 8 位而已, 后面无视, 同时第 8 位会拿来进位, 所以最终是 123ms 456micro, 800ns 相等于 1234568 tick
var tick = date.Ticks; // 638004123831234568 (两千多年换算成 tick, 不是 1970 开始哦)

var tickOnly = date.Ticks % TimeSpan.TicksPerSecond; // 1234568
var microTick = date.Ticks % TimeSpan.TicksPerMillisecond; // 4568
var nanoTick = date.Ticks % (TimeSpan.TicksPerMillisecond / 1000); // 8

var micro = (microTick - nanoTick) / 10; // 456
var nano = nanoTick * 100; // 800

通过各做转换技术, 最终就得到 micro 和 nano 了. 如果要做 +- 那就 date.AddTicks(nanoTick);

.NET 7

var date1 = new DateTimeOffset(2022, 10, 3, 16, 46, 23, 123, 456, TimeSpan.FromHours(8)); // 初始化可以到 Microsecond, 但 Nanosecond 不可以哦
date1 = DateTimeOffset.Parse("2022-10-03T16:46:23.123456793Z"); // 和之前一样
var micro = date1.Microsecond; // 456
var nano = date1.Nanosecond; // 800
date1.AddMicroseconds(-micro); // 有 AddMicroseconds, 但没有 AddNanoseconds 哦

比之前好多了, 只是 nano 支持的不完整, 不知道为什么...

 

posted @ 2022-09-25 05:49  兴杰  阅读(213)  评论(0编辑  收藏  举报