經常在第三方.NET庫中,看到一些“稀奇古怪”的寫法,這是啥?沒錯,這可能就是有所耳聞,但是不曾嘗試的C#新文法,本篇就對C#8.0中常用的一些新特性做一個總覽,并不齊全,算是抛磚引玉。
1.索引與範圍
1.1 索引
使用
^
操作符:
^1
指向最後一個元素,
^2
倒數第二個元素:
char[] vowels = new char[] {'a','e','i','o','u'};
char lastElement = vowels [^1]; // 'u'
char secondToLast = vowels [^2]; // 'o'
1.2 範圍
使用
..
操作符slice一個數組
左閉右開
ps:是兩個點,不是es6的擴充運算符的三個點
char[] vowels = new char[] {'a','e','i','o','u'};
char[] firstTwo = vowels [..2]; // 'a', 'e'
char[] lastThree = vowels [2..]; // 'i', 'o', 'u'
char[] middleOne = vowels [2..3] // 'i'
char[] lastTwo = vowels [^2..]; // 'o', 'u'
1.3 Index類型與Range類型
主要借助于Index類型和Range類型實作索引與範圍
char[] vowels = new char[] {'a','e','i','o','u'};
Index last = ^1;
Range firstTwoRange = 0..2;
char[] firstTwo = vowels [firstTwoRange]; // 'a', 'e'
1.4 擴充-索引器
可以定義參數類型為Index或Range的索引器
class Sentence
{
string[] words = "The quick brown fox".Split();
public string this [Index index] => words [index];
public string[] this [Range range] => words [range];
}
2.空合并操作
??=
string s=null;
if(s==null)
s="Hello,world";//s==null,s is Hello,world,or s is still s
//you can write this
s??="hello,world";
3.using聲明
如果省略了using後面的{},及聲明語句塊,就變為了using declaration,當執行落到所包含的語句塊之外時,該資源将被釋放
if (File.Exists ("file.txt"))
{
using var reader = File.OpenText ("file.txt");
Console.WriteLine (reader.ReadLine());
...
}
當執行走到
if
語句塊之外時,
reader
才會被釋放
4.readonly成員
允許在結構體的函數中使用readonly修飾符,確定如果函數試圖修改任何字段,會産生編譯時錯誤:
struct Point
{
public int X, Y;
public readonly void ResetX() => X = 0; // Error!
}
如果一個readonly函數調用一個非readonly成員,編譯器會産生警告。
public struct Point
{
public double X { get; set; }
public double Y { get; set; }
public double Distance => Math.Sqrt(X * X + Y * Y);
public readonly override string ToString() =>
$"({X}, {Y}) is {Distance} from the origin";
}
readonly ToString()
調用
Distance
warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an implicit copy of 'this'
Distance
屬性不會更改狀态,是以可以通過将
readonly
修飾符添加到聲明來修複此警告
5.靜态本地函數
在本地函數加上static,以確定本地函數不會從封閉範圍捕獲任何變量。 這樣做會生成
CS8421
,“靜态本地函數不能包含對 的引用”。這有助于減少耦合,并使本地方法能夠根據需要聲明變量,而不會與包含的方法中的變量發生沖突。
int M()
{
int y = 5;
int x = 7;
return Add(x, y);
static int Add(int left, int right) => left + right;
}
Add()
不通路封閉範圍中的任何變量,當然如果Add函數通路了本地變量,那就有問題:
int M()
{
int y;
LocalFunction();
return y;
static void LocalFunction() => y = 0; //warning
}
6.預設接口成員
允許向接口成員添加預設實作,使其成為可選實作:
interface ILogger
{
void Log (string text) => Console.WriteLine (text);
}
預設實作必須顯式接口調用:
class Logger : ILogger { }
...
((ILogger)new Logger()).Log ("message");
接口也可以定義靜态成員(包括字段),然後可以在預設實作中通路:
interface ILogger
{
void Log (string text) => Console.WriteLine (Prefix + text);
static string Prefix = "";
}
或者在接口外部,因為接口成員是隐式公共的,是以還可以從接口外部通路靜态成員的:
ILogger.Prefix = "File log: ";
除非給靜态接口成員加上(private,protected,or internal)加以限制,執行個體字段是禁止的。
7.switch表達式
可以在一個表示式上下文中使用switch
string cardName = cardNumber switch
{
13 => "King",
12 => "Queen",
11 => "Jack",
_ => "Pip card" // equivalent to 'default'
};
注意:switch關鍵字是在變量名之後,{}裡面是case子句,要逗号。switch表達式比switch塊更簡潔,可以使用LINQ查詢。
如果忘記了預設表達式
_
(這個像不像golang的匿名變量),然後switch比對失敗。會抛異常的。
還可以基于元組比對
int cardNumber = 12;
string suit = "spades";
string cardName = (cardNumber, suit) switch
{
(13, "spades") => "King of spades",
(13, "clubs") => "King of clubs",
...
};
還可以使用屬性模式
System.Uri
類,具有屬性
Scheme
,
Host
,
Port
, 和
IsLoopback
.考慮如下場景:寫一個防火牆,我們可以使用switch表達式的屬性模式來決定防火牆的規則(阻止或允許):
bool ShouldAllow (Uri uri) => uri switch
{
{ Scheme: "http", Port: 80 } => true,
{ Scheme: "https", Port: 443 } => true,
{ Scheme: "ftp", Port: 21 } => true,
{ IsLoopback: true } => true,
_ => false
};
還可以使用位置模式
包含可以通路的解構函數的
Point
類,可以使用位置模式
public class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) =>
(x, y) = (X, Y);
}
象限枚舉
public enum Quadrant
{
Unknown,
Origin,
One,
Two,
Three,
Four,
OnBorder
}
使用位置模式 來提取
x
和
y
的值。 然後,它使用
when
子句來确定該點的
Quadrant
:
static Quadrant GetQuadrant(Point point) => point switch
{
(0, 0) => Quadrant.Origin,
var (x, y) when x > 0 && y > 0 => Quadrant.One,
var (x, y) when x < 0 && y > 0 => Quadrant.Two,
var (x, y) when x < 0 && y < 0 => Quadrant.Three,
var (x, y) when x > 0 && y < 0 => Quadrant.Four,
var (_, _) => Quadrant.OnBorder,
_ => Quadrant.Unknown
};
8.可空引用類型
使用可空類型,可有效避免
NullReferenceExceptions
,但是如果編譯器認為異常還是可能會出現,就會發出警告。
void Foo (string? s) => Console.Write (s.Length); // Warning (.Length)
如果需要移除警告,可以使用
null-forgiving operator
(!)
void Foo (string? s) => Console.Write (s!.Length);
當然上面的例子是很危險的,因為實際上,這個字元串是可能為NULL的。
void Foo (string? s)
{
if (s != null) Console.Write (s.Length);
}
上面的例子,就不需要!操作符,因為編譯器通過靜态流分析(static flow analysis`)且足夠智能,分析出代碼是不可能抛出ReferenceException的。當然編譯器分析也不是萬能的,比如在數組中,它就不能知道數組元素哪些有資料,哪些沒有被填充,是以下面的内容就不會生成警告:
var strings = new string[10];
Console.WriteLine (strings[0].Length);
9.異步流
在C#8.0之前,可以使用
yield
傳回一個疊代器(iterator),或者使用await編寫異步函數。但是如果想在一個異步函數傳回一個疊代器怎麼辦?
C#8.0引入了異步流-asynchronous streams,解決了這個問題
//async IAsyncEnumerable
async IAsyncEnumerable<int> RangeAsync (int start, int count, int delay)
{
for (int i = start; i < start + count; i++)
{
await Task.Delay (delay);
yield return i;
}
}
await foreach調用異步流
await foreach (var number in RangeAsync (0, 10, 100))
Console.WriteLine (number);
LINQ查詢
這個需要System.Linq.Async
IAsyncEnumerable<int> query =
from i in RangeAsync (0, 10, 500)
where i % 2 == 0 // Even numbers only.
select i * 10; // Multiply by 10.
await foreach (var number in query)
Console.WriteLine (number);
ASP.Net Core
[HttpGet]
public async IAsyncEnumerable<string> Get()
{
using var dbContext = new BookContext();
await foreach (var title in dbContext.Books
.Select(b =>b.Title)
.AsAsyncEnumerable())
yield return title;
}
可釋放
實作 System.IAsyncDisposable 即可,可以使用using自動調用,亦可手動實作
IAsyncDisposable
接口
10.字元串插值
$
和
@
标記的順序可以任意安排:
$@"..."
和
@$"..."
均為有效的内插逐字字元串,這個在C#6.0時,是有嚴格的順序限制。
作者:Garfield
同步更新至個人部落格:http://www.randyfield.cn/
本文版權歸作者所有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected]
微信公衆号
掃描下方二維碼關注個人微信公衆号,實時擷取更多幹貨

作者:Garfield
同步更新至:http://www.randyfield.cn/
出處:http://www.cnblogs.com/RandyField/
本文版權歸作者和部落格園共有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected].