欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#中的9個“黑魔法”

 更新時間:2020年04月01日 09:16:05   作者:.NET騷操作  
這篇文章主要介紹了C#中的9個“黑魔法”與“騷操作”,本文通過實(shí)例代碼給大家講解的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

我們知道C#是非常先進(jìn)的語言,因?yàn)槭撬苡羞h(yuǎn)見的“語法糖”。這些“語法糖”有時過于好用,導(dǎo)致有人覺得它是C#編譯器寫死的東西,沒有道理可講的——有點(diǎn)像“黑魔法”。

那么我們可以看看C#這些高級語言功能,是編譯器寫死的東西(“黑魔法”),還是可以擴(kuò)展(騷操作)的“鴨子類型”。

我先列一個目錄,大家可以對著這個目錄試著下判斷,說說是“黑魔法”(編譯器寫死),還是“鴨子類型”(可以自定義“騷操作”):

  1. LINQ操作,與IEnumerable<T>類型;
  2. async/await,與Task/ValueTask類型;
  3. 表達(dá)式樹,與Expression<T>類型;
  4. 插值字符串,與FormattableString類型;
  5. yield return,與IEnumerable<T>類型;
  6. foreach循環(huán),與IEnumerable<T>類型;
  7. using關(guān)鍵字,與IDisposable接口;
  8. T?,與Nullable<T>類型;
  9. 任意類型的Index/Range泛型操作。

1. LINQ操作,與IEnumerable<T>類型

不是“黑魔法”,是“鴨子類型”。

LINQC# 3.0發(fā)布的新功能,可以非常便利地操作數(shù)據(jù)?,F(xiàn)在12年過去了,雖然有些功能有待增強(qiáng),但相比其它語言還是方便許多。

如我上一篇博客提到,LINQ不一定要基于IEnumerable<T>,只需定定義一個類型,實(shí)現(xiàn)所需要的LINQ表達(dá)式即可,LINQselect關(guān)鍵字,會調(diào)用.Select方法,可以用如下的“騷操作”,實(shí)現(xiàn)“移花接木”的效果:

void Main()
{
 var query = 
  from i in new F()
  select 3;
  
 Console.WriteLine(string.Join(",", query)); // 0,1,2,3,4
}

class F
{
 public IEnumerable<int> Select<R>(Func<int, R> t)
 {
  for (var i = 0; i < 5; ++i)
  {
   yield return i;
  }
 }
}

2. async/await,與Task/ValueTask類型

不是“黑魔法”,是“鴨子類型”。

async/await發(fā)布于C# 5.0,可以非常便利地做異步編程,其本質(zhì)是狀態(tài)機(jī)。

async/await的本質(zhì)是會尋找類型下一個名字叫GetAwaiter()的接口,該接口必須返回一個繼承于INotifyCompletionICriticalNotifyCompletion的類,該類還需要實(shí)現(xiàn)GetResult()方法和IsComplete屬性。

  • 先調(diào)用t.GetAwaiter()方法,取得等待器a;
  • 調(diào)用a.IsCompleted取得布爾類型b;
  • 如果b=true,則立即執(zhí)行a.GetResult(),取得運(yùn)行結(jié)果;
  • 如果b=false,則看情況:

如果a沒實(shí)現(xiàn)ICriticalNotifyCompletion,則執(zhí)行(a as INotifyCompletion).OnCompleted(action)
如果a實(shí)現(xiàn)了ICriticalNotifyCompletion,則執(zhí)行(a as ICriticalNotifyCompletion).OnCompleted(action)
執(zhí)行隨后暫停,OnCompleted完成后重新回到狀態(tài)機(jī);

有興趣的可以訪問Github具體規(guī)范說明:https://github.com/dotnet/csharplang/blob/master/spec/expressions.md

正常Task.Delay()是基于線程池計(jì)時器的,可以用如下“騷操作”,來實(shí)現(xiàn)一個單線程的TaskEx.Delay()

static Action Tick = null;

void Main()
{
 Start();
 while (true)
 {
  if (Tick != null) Tick();
  Thread.Sleep(1);
 }
}

async void Start()
{
 Console.WriteLine("執(zhí)行開始");
 for (int i = 1; i <= 4; ++i)
 {
  Console.WriteLine($"第{i}次,時間:{DateTime.Now.ToString("HH:mm:ss")} - 線程號:{Thread.CurrentThread.ManagedThreadId}");
  await TaskEx.Delay(1000);
 }
 Console.WriteLine("執(zhí)行完成");
}

class TaskEx
{
 public static MyDelay Delay(int ms) => new MyDelay(ms);
}

class MyDelay : INotifyCompletion
{
 private readonly double _start;
 private readonly int _ms;
 
 public MyDelay(int ms)
 {
  _start = Util.ElapsedTime.TotalMilliseconds;
  _ms = ms;
 }
 
 internal MyDelay GetAwaiter() => this;
 
 public void OnCompleted(Action continuation)
 {
  Tick += Check;
  
  void Check()
  {
   if (Util.ElapsedTime.TotalMilliseconds - _start > _ms)
   {
    continuation();
    Tick -= Check;
   }
  }
 }

 public void GetResult() {}
 
 public bool IsCompleted => false;
}

運(yùn)行效果如下:

執(zhí)行開始
第1次,時間:17:38:03 - 線程號:1
第2次,時間:17:38:04 - 線程號:1
第3次,時間:17:38:05 - 線程號:1
第4次,時間:17:38:06 - 線程號:1
執(zhí)行完成

注意不需要非得使用TaskCompletionSource<T>才能創(chuàng)建定定義的async/await。

3. 表達(dá)式樹,與Expression<T>類型

是“黑魔法”,沒有“操作空間”,只有當(dāng)類型是Expression<T>時,才會創(chuàng)建為表達(dá)式樹。

表達(dá)式樹C# 3.0隨著LINQ一起發(fā)布,是有遠(yuǎn)見的“黑魔法”。

如以下代碼:

Expression<Func<int>> g3 = () => 3;

會被編譯器翻譯為:

Expression<Func<int>> g3 = Expression.Lambda<Func<int>>(
 Expression.Constant(3, typeof(int)), 
 Array.Empty<ParameterExpression>());

4. 插值字符串,與FormattableString類型

是“黑魔法”,沒有“操作空間”。

插值字符串發(fā)布于C# 6.0,在此之前許多語言都提供了類似的功能。

只有當(dāng)類型是FormattableString,才會產(chǎn)生不一樣的編譯結(jié)果,如以下代碼:

FormattableString x1 = $"Hello {42}";
string x2 = $"Hello {42}";

編譯器生成結(jié)果如下:

FormattableString x1 = FormattableStringFactory.Create("Hello {0}", 42);
string x2 = string.Format("Hello {0}", 42);

注意其本質(zhì)是調(diào)用了FormattableStringFactory.Create來創(chuàng)建一個類型。

5. yield return,與IEnumerable<T>類型;

是“黑魔法”,但有補(bǔ)充說明。

yield return除了用于IEnumerable<T>以外,還可以用于IEnumerable、IEnumerator<T>、IEnumerator。

因此,如果想用C#來模擬C++/Javagenerator<T>的行為,會比較簡單:

var seq = GetNumbers();
seq.MoveNext();
Console.WriteLine(seq.Current); // 0
seq.MoveNext();
Console.WriteLine(seq.Current); // 1
seq.MoveNext();
Console.WriteLine(seq.Current); // 2
seq.MoveNext();
Console.WriteLine(seq.Current); // 3
seq.MoveNext();
Console.WriteLine(seq.Current); // 4

IEnumerator<int> GetNumbers()
{
 for (var i = 0; i < 5; ++i)
  yield return i;
}

yield return——“迭代器”發(fā)布于C# 2.0。

6. foreach循環(huán),與IEnumerable<T>類型

是“鴨子類型”,有“操作空間”。

foreach不一定非要配合使用IEnumerable<T>類型,只要對象存在GetEnumerator()方法即可:

void Main()
{
 foreach (var i in new F())
 {
  Console.Write(i + ", "); // 1, 2, 3, 4, 5, 
 }
}

class F
{
 public IEnumerator<int> GetEnumerator()
 {
  for (var i = 0; i < 5; ++i)
  {
   yield return i;
  }
 }
}

另外,如果對象實(shí)現(xiàn)了GetAsyncEnumerator(),甚至也可以一樣使用await foreach異步循環(huán):

async Task Main()
{
 await foreach (var i in new F())
 {
  Console.Write(i + ", "); // 1, 2, 3, 4, 5, 
 }
}

class F
{
 public async IAsyncEnumerator<int> GetAsyncEnumerator()
 {
  for (var i = 0; i < 5; ++i)
  {
   await Task.Delay(1);
   yield return i;
  }
 }
}

await foreachC# 8.0隨著異步流一起發(fā)布的,具體可見我之前寫的《代碼演示C#各版本新功能》。

7. using關(guān)鍵字,與IDisposable接口

是,也不是。

引用類型和正常的值類型using關(guān)鍵字,必須基于IDisposable接口。

ref structIAsyncDisposable就是另一個故事了,由于ref struct不允許隨便移動,而引用類型——托管堆,會允許內(nèi)存移動,所以ref struct不允許和引用類型產(chǎn)生任何關(guān)系,這個關(guān)系就包含繼承接口——因?yàn)?code>接口也是引用類型。

但釋放資源的需求依然存在,怎么辦,“鴨子類型”來了,可以手寫一個Dispose()方法,不需要繼承任何接口:

void S1Demo()
{
 using S1 s1 = new S1();
}

ref struct S1
{
 public void Dispose()
 {
  Console.WriteLine("正常釋放");
 }
}

同樣的道理,如果用IAsyncDisposable接口:

async Task S2Demo()
{
 await using S2 s2 = new S2();
}

struct S2 : IAsyncDisposable
{
 public async ValueTask DisposeAsync()
 {
  await Task.Delay(1);
  Console.WriteLine("Async釋放");
 }
}

8. T?,與Nullable<T>類型

是“黑魔法”,只有Nullable<T>才能接受T?,Nullable<T>作為一個值類型,它還能直接接受null值(正常值類型不允許接受null值)。

示例代碼如下:

int? t1 = null;
Nullable<int> t2 = null;
int t3 = null; // Error CS0037: Cannot convert null to 'int' because it is a non-nullable value type

生成代碼如下(int?Nullable<int>完全一樣,跳過了編譯失敗的代碼):

IL_0000: nop
IL_0001: ldloca.s 0
IL_0003: initobj valuetype [System.Runtime]System.Nullable`1<int32>
IL_0009: ldloca.s 1
IL_000b: initobj valuetype [System.Runtime]System.Nullable`1<int32>
IL_0011: ret

9. 任意類型的Index/Range泛型操作

有“黑魔法”,也有“鴨子類型”——存在操作空間。

Index/Range發(fā)布于C# 8.0,可以像Python那樣方便地操作索引位置、取出對應(yīng)值。以前需要調(diào)用Substring等復(fù)雜操作的,現(xiàn)在非常簡單。

string url = "https://www.super-cool.com/product/7705a33a-4d2c-455d-a42c-c95e6ac8ee99/summary";
string productId = url[35..url.LastIndexOf("/")];
Console.WriteLine(productId);

生成代碼如下:

string url = "https://www.super-cool.com/product/7705a33a-4d2c-455d-a42c-c95e6ac8ee99/amd-r7-3800x";
int num = 35;
int length = url.LastIndexOf("/") - num;
string productId = url.Substring(num, length);
Console.WriteLine(productId); // 7705a33a-4d2c-455d-a42c-c95e6ac8ee99

可見,C#編譯器忽略了Index/Range,直接翻譯為調(diào)用Substring了。

但數(shù)組又不同:

var range = new[] { 1, 2, 3, 4, 5 }[1..3];
Console.WriteLine(string.Join(", ", range)); // 2, 3

生成代碼如下:

int[] range = RuntimeHelpers.GetSubArray<int>(new int[5]
{
 1,
 2,
 3,
 4,
 5
}, new Range(1, 3));
Console.WriteLine(string.Join<int>(", ", range));

可見它確實(shí)創(chuàng)建了Range類型,然后調(diào)用了RuntimeHelpers.GetSubArray<int>,完全屬于“黑魔法”。

但它同時也是“鴨子”類型,只要代碼中實(shí)現(xiàn)了Length屬性和Slice(int, int)方法,即可調(diào)用Index/Range

var range2 = new F()[2..];
Console.WriteLine(range2); // 2 -> -2

class F
{
 public int Length { get; set; }
 public IEnumerable<int> Slice(int start, int end)
 {
  yield return start;
  yield return end;
 }
}

生成代碼如下:

F f = new F();
int length2 = f.Length;
length = 2;
num = length2 - length;
string range2 = f.Slice(length, num);
Console.WriteLine(range2);

總結(jié)

如上所見,C#的“黑魔法”確實(shí)挺多,但“鴨子類型”也有很多,“騷操作”的“操作空間”很大。

據(jù)傳C# 9.0將添加“鴨子類型”的元祖——Type Classes,到時候“操作空間”肯定比現(xiàn)在更大,非常期待!

到此這篇關(guān)于C#中的9個“黑魔法”的文章就介紹到這了,更多相關(guān)c# 黑魔法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C#實(shí)現(xiàn)Json轉(zhuǎn)Unicode的方法

    C#實(shí)現(xiàn)Json轉(zhuǎn)Unicode的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)Json轉(zhuǎn)Unicode的方法,可實(shí)現(xiàn)輸入為帶有json格式的文本,輸出正常文本的功能,需要的朋友可以參考下
    2014-09-09
  • unity實(shí)現(xiàn)鼠標(biāo)經(jīng)過時ui及物體的變色操作

    unity實(shí)現(xiàn)鼠標(biāo)經(jīng)過時ui及物體的變色操作

    這篇文章主要介紹了unity實(shí)現(xiàn)鼠標(biāo)經(jīng)過時ui及物體的變色操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • C#設(shè)置PDF表單不可編輯以及提取表單數(shù)據(jù)的操作

    C#設(shè)置PDF表單不可編輯以及提取表單數(shù)據(jù)的操作

    PDF表單是PDF中的可編輯區(qū)域,允許用戶填寫指定信息,當(dāng)表單填寫完成后,有時候我們可能需要將其設(shè)置為不可編輯,以保護(hù)表單內(nèi)容的完整性和可靠性,本文將給大家介紹C#設(shè)置PDF表單不可編輯以及提取表單數(shù)據(jù)的操作,需要的朋友可以參考下
    2024-06-06
  • C#的Socket實(shí)現(xiàn)UDP協(xié)議通信示例代碼

    C#的Socket實(shí)現(xiàn)UDP協(xié)議通信示例代碼

    本篇文章主要介紹了C#的Socket實(shí)現(xiàn)UDP協(xié)議通信示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • C#函數(shù)式編程中的標(biāo)準(zhǔn)高階函數(shù)詳解

    C#函數(shù)式編程中的標(biāo)準(zhǔn)高階函數(shù)詳解

    這篇文章主要介紹了C#函數(shù)式編程中的標(biāo)準(zhǔn)高階函數(shù)詳解,本文講解了何為高階函數(shù)、Map、 Filter、Fold等內(nèi)容,需要的朋友可以參考下
    2015-01-01
  • 一篇文章弄懂C#中的async和await

    一篇文章弄懂C#中的async和await

    這篇文章主要給大家介紹了如何通過一篇文章弄懂C#中async和await的相關(guān)資料,async和await相信大家應(yīng)該不陌生,讓異步處理變得更友好,本文通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2021-07-07
  • C#中Response.Write常見問題匯總

    C#中Response.Write常見問題匯總

    這篇文章主要介紹了C#中Response.Write常見問題匯總,總結(jié)了C#中Response.Write的常用技巧,非常實(shí)用,需要的朋友可以參考下
    2014-09-09
  • C# 使用Microsoft Edge WebView2的相關(guān)總結(jié)

    C# 使用Microsoft Edge WebView2的相關(guān)總結(jié)

    這篇文章主要介紹了C# 使用Microsoft Edge WebView2的相關(guān)總結(jié),幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下
    2021-02-02
  • Unity實(shí)現(xiàn)QQ列表折疊菜單

    Unity實(shí)現(xiàn)QQ列表折疊菜單

    這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)QQ列表折疊菜單,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • C#各種異常處理方式總結(jié)

    C#各種異常處理方式總結(jié)

    這篇文章介紹了C#各種異常的處理方式,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-08-08

最新評論