天天看點

【C#】C#8.0常用新特性

經常在第三方.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]

微信公衆号

掃描下方二維碼關注個人微信公衆号,實時擷取更多幹貨

【C#】C#8.0常用新特性

作者:Garfield

同步更新至:http://www.randyfield.cn/

出處:http://www.cnblogs.com/RandyField/

本文版權歸作者和部落格園共有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected].