C# 11
前言
.NET 7 来到 RC 阶段了. C# 11 也伴随而来咯, 这篇看看有哪些可能会用上的好功能呗.
参考
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
在 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 支持的不完整, 不知道为什么...