C# – 6.0, 7.0, 8.0, 9.0 总结
前言
C# 这几年改了好几个版本, 多了许多语法糖,还带有 JavaScript / TypeScript 的味道了.
我觉得随着 blazor 的发展 (想取代前端开发 ?) 那 C# 必然需要更多类似 TS 的语法.
这里记入一下常用到的.
参考:
List of All New Features in C# 6.0: Part 1
1. using static (6.0)
一般上 using 都是配上一个 namespace, 然后下面调用时写 ClassName.StaticMethod();
有了 using static, 我们可以直接写 using static Namespace.ClassName 然后直接在下面调用 StaticMethod() 省去了每次写 ClassName.
感觉没有什么鸟用啦.
2. init property value (6.0, 7.0)
public int age { get; set; } = 5;
以前 init value 只能写在 construtor 里.
must use
3. Dictionary 字典 int value (6.0)
var before = new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" }, }; var after = new Dictionary<string, string> { ["key1"] = "value1", ["key2"] = "value2" };
新的写法更像 key-value pair 了.
must use
4. nameof (6.0)
var stringValue = nameof(IndexModel.OnGet); // "OnGet"
取代了原本直接写 string "OnGet", 写 string 最大的问题就是不容易批量换.
must use
5. catch when (6.0)
try { } catch (Exception ex) when (ex.Message.Length > 10) { throw; } catch (Exception ex) when (ex.Message.Length > 3) { throw; }
以前 catch 只能依据类型进入到不同的 catch, 现在可以多加一些表达式. 易读性提高了.
must use
6. Null-Conditional Operator (6.0)
Object?.Property, or Object?.Method() 当 object 是 null 的时候直接返回 null,
遇到 Func 和 Action 的时候要这样写哦
must use.
7. expression method (6.0)
public string getValue(string value) => "dada" + value;
以前一定要写 return, 类似这种 () { return ".."; }. 但 lambda 也不总是比 return 更好看哦.
大部分情况好用
8.String interpolation (6.0)
参考: $ - string interpolation (C# reference)
var x = $"{language}-some value";
$ 声明这个 string 是 interpolation, 花括弧 {} 表示要嵌套进去的 variable. 这个和 JavaScript 的 `hi, ${value}` 差不多.
另外需要注意的是, 当遇到特殊符号的处理.
当遇到 " 时, 需要添加反斜杠 \"
当遇到 {} 时, 需要写 double
当遇到 \ 时, 需要写 double
还有, 它不能 new line
可以通过 string literal 来解决, 添加 @ 在前面.
然而, 添加 @ 同时会改变一些规则
当遇到 " 时, 不再使用反斜杠, 取而代之的是用 double
当遇到 \ 时, 则不再需要用 double 了.
C# – 11.0 还有一些加强哦.
另外, 里面不需要写 .ToString, 比如
var number = 1.65m; var value1 = $"{number}"; // 1.65 var value2 = $"{number.ToString()}"; // 1.65
value1, value2 是等价的.
如果遇到要传参数可以加分号
var date = DateTime.Now; var value = $"{date:dd-MMM-yyyy}"; // 11-Jun-2023
must use
9. Tuples & Deconstruct (7.0)
参考: Docs – Tuples
C# 一直没有类似 JS 的 array / object. 一旦你用 array / object 就没有类型了. 后来 TS 有了 Tuples, C# 7.0 后也有了.
我一般把它当 TS 来用, 可以快速的定义一些匿名对象或者 array 等等. 不需要什么都搞 class. 灵活度大大提升了.
(string, DateTime, int) array = ("str", DateTime.Now, 3); var str = array.Item1; (string name, int age) obj = (name: "str", age: 11); var name = obj.name; var array2 = ("str1", "str2"); var value = array2.Item1; // string?
需要注意, 如果用 var 的话, 智能匹配出来的结果是 nullable string 哦, 建议声明类型来使用 Tuples 不要用 var
注: 方法也是可以直接返回 Tuples 的, C# 的 Tuples 一律用圆括弧 ()
Tuples must use
如果已经是 class object 了, 就不能像 TS 那样直接解构了. 这点有待加强.
public class Point { public Point(double x, double y) => (X, Y) = (x, y); public double X { get; } public double Y { get; } public void Deconstruct(out double x, out double y) => (x, y) = (X, Y); } public void OnGet(string? language, string? amp) { var p = new Point(3.14, 2.71); var (x, y) = p; var (whateverName, _) = p; // variable 名字可以随便取, 它依赖顺序, underscore 代表 skip var value1 = 5; (value1, var y) = p; // 也可以给 existing variable 赋值 }
需要特别声明一个 Deconstruct 的方法. 对象才能被解构.
Deconstruct 挺麻烦, 比起 JS 差远了, 不好用.
10. Pattern matching (7.0)
C# 一直在优化 pattern matching 语法, 以前写 if else 有时真的非常难读懂.
is + 变量
object obj = DateTime.Now; if (obj is DateTime date) { ((DateTime)(obj)).AddDays(10); date.AddDays(10); }
声明一个变量在 is 条件判断后面就可以以当下的类型替代原本的 obj 来调用了. 类似 ts
配合 swtich 也是可以用
object obj = 1; switch (obj) { case int number when number > 0: // do something break; }
对了, case when 和之前提到的 catch when 是类似的, 也和 sql 语法 case when 一样哦.
must use
11. Local functions (7.0) & static local function (8.0)
写 C# 经常遇到这种情况,就是你的一个方法里面需要重复一些代码执行, 一般的做法就是开一个私有 method 封装.
但是这样动作可能有点大. 因为只是某个方法内部需要复用而已丫. 于是 local function 就很适合这种场景了.
跟 js 很类似.
string getValue(string value) => "value" + value; var x = getValue("dada");
上面我刻意写成了没有 return. 刚才介绍过的功能.
must use
8.0 之后还可以加上 static, 我个人喜欢用 static 多一些, 毕竟 local function 偏函数式概念了, 所以尽量做成纯函数.
12. 直接声明 default 变量 (7.0)
int number = default;
之前是需要调用的 default(int) 省去了重复, 适合用于 parameter 初始值哦
must use
13. number 可是下划线来分开 0 (7.0)
1,000,000 这个是 100万
在 C# int value = 1_000_000 可以这样写
must use
14 out variable (7.0)
if (int.TryParse("5", out var result)) Console.WriteLine(result); else Console.WriteLine("Could not parse input");
以前智能把变量声明在外面, 非常不友好.
must use
15. getter setter expression (7.0)
must use
private int myVar;
public int MyProperty
{
// get { return myVar; }
// set { myVar = value; }
get => myVar;
set => myVar = value;
}
注释的是之前的写法. 新的写法更好阅读了.
must use
16. Switch expressions (8.0)
当我们想写一堆 if else 然后返回一个值的时候, 这个超好用的.
var value = "dada"; var final = value switch { "kk" => "value 1", "dada" => "value 2", _ => throw new NotImplementedException() };
must use
17. switch property patterns tuple patterns (8.0)
我们知道 switch 可以匹配类似, 现在还可以匹配 property 和 tuple
var person = new Person(); var final = person switch { { Name: "dada", Age: 3 } => "w", _ => throw new NotImplementedException(), }; var tuple = ("a", "b"); var final1 = tuple switch { ("a", "b") => "dada", _ => throw new NotImplementedException() };
匹配只能是最简单的等于 =, 这个有点弱. 如果可以写多点判断逻辑会更好用. 并没有完全能替代 if else, 只是某些情况适用.
不错用
18. using declarations (8.0)
using var file = new System.IO.StreamWriter("WriteLines2.txt"); file.Flush();
省略掉了花括弧, 它的 scope 就是跟着当前的 block 跑. 如果要提早 displose 的话, 可以用回之前花括弧写法.
must use
19. Nullable reference types (8.0)
string? abc, Person? person
从前对象, string 都是 nullable. 在编写代码的时候没有智能提醒. 现在有了这个我们就可以表示是不是 nullable 了.
must use
20. Indices and ranges (8.0)
注意 ^1 是最后一个, ^0 是拿 length
让拿 range 或者 substring 都可以下面这样写
var a = new string[] { "1", "2", "3", "4" }; var t = a[0..2]; // [1, 2] var t1 = a[..2]; // [1, 2] 前面没写就是从 0 start var t2 = a[0..]; // [1, 2, 3, 4] // 后面没写就是^0 var t3 = a[0..^0]; // [1, 2, 3, 4] // 后面没写就是^0
第一个参数是开始的位置, 会拿到. 第二个参数是结尾的位置 (不是拿 length 哦, 是位置,位置), 不会拿到. 和 js 的逻辑是一样的.
如果超出范围只是拿不到, 不会报错. 当然你不要去写 negetive 啦.
注: List<T> 支持 indices 但不支持 range...即使可以这样 myList[^2] 但不可以 myList[0..1]
解决方法是用 .Take(0..1)
must use
21. Null-coalescing assignment (8.0)
通过 ?? 来给 null 复值. 取代了 ? : 的写法
??= 和 += 是一脉的
string? a = null; var b = a ?? "dada"; // dada a = a ?? "dada"; a ??= "dada";
must use
22.Record types & Init only setters (9.0)
record 我还没有开始用, 之后才补上, 简单说就是 immuatable 对象啦. 要用 C# 来替代 js 写前端, 那么 C# 本身就得有 js 的灵活. 所以那些以为只要会 C# 配上 blozar 就可以不用学前端那一套, too young too simple.
init only setter, 只有在初始化的时候赋值, 之后就不可写了。注: 用反射依然可以写进去。
public class Person { public string Name { get; init; } = ""; public int Age { get; set; } } public void OnGet() { var person = new Person { Name = "dada" }; person.Name = "a"; }
不错用
23. Fit and finish features (9.0)
WeatherStation station = new() { Location = "Seattle, WA" };
如果类型能被推到出来, 那么就可以不需要在 new 的时候用写多一次类型了.
适用于写入 parameter 的场景哦
public class Person { public string Name { get; set; } = ""; } public void Method(Person person) { } public void OnGet() { Method(new Person { Name = "dada" }); // 旧 Method(new() { Name = "dada" }); // 新 }
和函数省略 return 做成表达式一样, 不是每个地方省略就一定更好阅读,有时候废话一点也挺好.
省略 != 好, 不推荐
24. Pattern matching enhancements (9.0)
多了 is, not, and, or 等语法. 有点像 sql 那样. 就是少一点符号, 用词来替代.
我觉得挺好的.
public static bool IsLetterOrSeparator(this char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';
关于 pattern 可以看这篇: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#c-language-specification
switch case when is not and or 这些词语都会经常出现在里头. 目前我的感觉是并没有完全替代原来的 if else || && 这些. 只是语义化了一些, 提高了阅读体验是真的.
有些场景不错用.