C# 不多人知道的科技

本文來告訴你們在C#不多有人會發現的科技。即便是工做了好多年的老司機也不必定會知道這些科技,若是以爲我是在騙你,那麼請看看本文的內容。web

本來最初 C# 的設計是簡單和高效開發的,在通過了這麼多年衆多公司和開發者的努力下,整個 C# 裏面包含了大量有趣的功能。其中一部分功能是針對於某些特殊需求設計的,例如高性能或高併發或無內存回收等。在通過了 10 多年的迭代,不多人能徹底瞭解整個 C# 語言和框架級作了哪些有趣的功能面試

我在網上找了不少大神的博客,而後和不少大神聊天,知道了一些科技,因而就在本文和你們分享一下。若是你們有了解本博客裏面沒有收藏的科技,還請告訴我express

如今整個 C# 從編譯器到運行時都是開源的,全部權在 dotnet 基金會上,所有開源的項目都基於最友好的 MIT 協議和 Apache 2 開源協議,文檔協議遵循CC-BY協議。這將容許任何人任何組織和企業任意處置,包括使用,複製,修改,合併,發表,分發,再受權,或者銷售。惟一的限制是,軟件中必須包含上述版 權和許可提示,後者協議將會除了爲用戶提供版權許可以外,還有專利許可,而且受權是免費,無排他性的(任何我的和企業都能得到受權)而且永久不可撤銷,用戶使用.NET 和 C# 徹底不用擔憂收費問題和版權問題,以及後續沒法維護問題。而 dotnet 基金會是一個開放的平臺,我也是 dotnet 基金會的成員之一。微軟在 2020 的時候依然是 dotnet 基金會最大的支持組織安全

如今最火的 dotnet 倉庫是 dotnet csharplang 倉庫,當前的 C# 語言特性由整個社區決定,這是一個官方開放用來討論 C# 語言將來的倉庫,每天都有大佬們在討論語言的特性,歡迎你們加入併發

接下來讓我告訴你們一些不多有人會發現的科技框架

無限級判斷空

在 C# 6.0 可使用??判斷空,那麼就可使用下面代碼ide

var v1 = "123";
            string v2 = null;
            string v3 = null;

            var v = v1 ?? v2 ?? v3;

實際上能夠無限的使用??判斷前面一個函數爲空,那麼問題來了,下面的代碼輸出的是多少?函數

var n = 2 + foo?.N ?? 1;

上面代碼的 foo 就是空的,那麼 n 是多少?是 1 仍是 2 仍是 3 仍是空?高併發

想要了解這道題的推導過程請看C# 高級面試題 裏面寫了不少老司機都不必定能解出性能

使用 using 關鍵詞省略長的定義

例若有下面這個代碼,在這個代碼裏面使用了不少的 List 嵌套,以下面代碼所示裏面有不少定義的代碼

var foo = new System.Collections.Generic.Dictionary<System.Collections.Generic.List<System.Collections.Generic.List<string>>, string>();

能夠看到上面代碼中,有大量的代碼都是用來做爲類型的定義,假設這個值做爲某個方法的參數,那纔是可怕

一個簡單的方法是使用 using 關鍵詞,如在文件的開頭添加以下代碼

using HvcnrclHnlfk = System.Collections.Generic.Dictionary<System.Collections.Generic.List<System.Collections.Generic.List<string>>,string>;

在添加了上面代碼以後,在這個文件裏的全部用到如上面很長的定義的代碼均可以使用 using 後面的值能夠代替,如本文上面使用了 HvcnrclHnlfk 這個詞,來看看替換以後的代碼長度

var foo = new HvcnrclHnlfk();

辣麼大

實際上寫到這裏我有些很差意思,好像剛剛說的都是你們都知道的,那麼我就要開始寫你們不多知道的科技

等等,什麼是 辣麼大 大哇?其實這是 lambda 表達式的翻譯

請看看下面這段有趣的代碼

Func<string,string, EventHandler> foo = (x, y) => (s, e) =>
            {
                var button = (Button) s;
                button.Left = x;
                button.Top = y;
            };

            Button1.Click += foo(0, -1);

上面的代碼經過一個 lambda 表達式返回一個另外一個 lambda 表達式,或者說用一個委託返回另外一個委託。這是一個特別有趣的寫法,經過函數返回函數的思想能夠用來寫出一些有趣的邏輯,特別是在多層嵌套的時候

固然使用委託但是會出現另外一個問題的,請問下面的代碼實際調用的是哪一個委託,下面代碼的 a 和 b 和 c 都是 Action委託,同時都不是空的

((a + b + c) - (a + c))();

在數學上,其實函數也能夠視爲變量,頗有科技範的 C# 固然也支持如此的功能,將函數包裝爲委託的時候,可讓委託自己支持加減法哦,只是這個加減法的規則有些詭異。不信,請猜猜上面代碼執行了什麼函數

衝突的類型

在遇到某些類型,特別是放在 NuGet 上的多個不一樣的庫裏面的類型,這些類型有相同的類名,如 Data 或 Control 等很通用的命名的時候,在代碼中若是須要同時使用這兩個類,就須要補全整個命名空間,以下面代碼

var webControl = new System.Web.UI.WebControls.Control();
var formControl = new System.Windows.Forms.Control();

若是常用這兩個控件,那麼就須要寫不少補全命名空間的代碼,代碼不少。好在微軟的大佬們給出了一個坑方法,使用這個方法能夠不寫命名空間,或者說只須要在文件開始 using 一次,請看代碼

using web = System.Web.UI.WebControls;
using win = System.Windows.Forms;

web::Control webControl = new web::Control();
win::Control formControl = new win::Control();

參見:https://stackoverflow.com/a/9099/6116637

extern alias

若是使用了兩個不一樣的程序集放在兩個不一樣的 dll 文件裏面,這兩個程序集都有相同命名空間和類型,那麼如何使用指定的庫

以下面代碼所示,在兩個 dll 裏面都定義了 F.Foo 類型

//a.dll

namespace F
{
	public class Foo
	{

	}
}

//b.dll

namespace F
{
	public class Foo
	{
		
	}
}

這時就可使用 extern alias 關鍵詞

參見:C#用extern alias解決兩個assembly中相同的類型全名 - fresky - 博客園

字符串

你們看到了 C# 6.0 的$,是否是能夠和@一塊兒?

var str = "kktpqfThiq";
            string foo = $@"換行{str}";

注意兩個的順序,反過來直接告訴你代碼不能這樣寫

此知識點再也不適用,由於在 C# 8.0 的時候,能夠按照任意的順序使用 $ 和 @ 標記。詳細請看 $ - 字符串內插 - C# 參考 特別感謝 592844340 羣內熱心人員勘誤

特殊關鍵字

實際上有下面幾個關鍵字是沒有詳細的文檔,可能只有微軟的編譯器才知道

__makeref

__reftype

__refvalue

__arglist

不過在 C# 7.2 可使用其餘的關鍵字作到一些功能,詳細請看個人 C# 7.0 博客

使用 Unions (C++ 同樣的)

若是看到 C++ 可使用內聯,不要說 C# 沒有這個功能,實際上也可使用 FieldOffset 特性實現和 C++ 同樣的內聯的功能 ,請看下面代碼

[StructLayout(LayoutKind.Explicit)]
public class A
{
    [FieldOffset(0)]
    public byte One;

    [FieldOffset(1)]
    public byte Two;

    [FieldOffset(2)]
    public byte Three;

    [FieldOffset(3)]
    public byte Four;

    [FieldOffset(0)]
    public int Int32;
}

以下面代碼就定義了int變量,修改這個變量就是修改其餘的三個變量

static void Main(string[] args)
    {
        A a = new A { Int32 = int.MaxValue };

        Console.WriteLine(a.Int32);
        Console.WriteLine("{0:X} {1:X} {2:X} {3:X}", a.One, a.Two, a.Three, a.Four);

        a.Four = 0;
        a.Three = 0;
        Console.WriteLine(a.Int32);
    }

運行代碼能夠看到輸出以下

2147483647
FF FF FF 7F
65535

能夠看到修改其中某個值都會相互影響,這幾個值共用了相同的一個內存空間

接口默認方法

實際上能夠給接口使用默認方法,使用的方式以下

public static void Foo(this IF1 foo)
{
     //實際上你們也看到是如何定義
}

固然了,在 C# 8.0 還有更直接的方法,詳細請看 在 C# 中使用默認接口方法安全地更新接口

stackalloc

不少人都不知道這個科技,這是不安全代碼,從棧申請空間

int* block = stackalloc int[100];

使用的時候須要當心你的棧也許會炸掉

參見:stackalloc

指定編譯

這個是一個有趣的特性實現的功能,是一個編譯器技術,寫給編譯器看的特性。使用 Conditional 特性可讓代碼在指定條件不使用,以下面的代碼,規定了只有在 DEBUG 宏定義的時候才讓 F2 方法生效。所以在 Release 下就不會使用 F2 方法了

public sealed clas Foo
    {
        public Foo F1()
        {
            Console.WriteLine("進入F1");
            return this;
        }

        [Conditional("DEBUG")]
        public void F2()
        {
            Console.WriteLine("F2");
        }
    }

簡單讓代碼跑一下

static void Main(string[] args)
        {
            var foo = new Foo();
            foo.F1();
            foo.F2();
        }

結果是什麼,你們也知道,在 Debug 和 Release 輸出是不相同。可是這麼簡單的怎麼會在這裏說呢,請你們看看這個代碼輸出什麼

static void Main(string[] args)
     {
         var foo = new Foo();
         foo.F1().F2();
     }

實際上在 Release 下什麼都不會輸出,此時的 F1 不會被執行

true 判斷

下面寫個見鬼的代碼

var foo = new Foo(10);

            if (foo)
            {
                Console.WriteLine("個人類沒有繼承 bool ,竟然能夠這樣寫");
            }

沒錯 Foo 沒有繼承 bool 竟然能夠這樣寫

實際上就是重寫 true 方法,請看代碼

public class Foo
    {
        public Foo(int value)
        {
            _count = value;
        }

        private readonly int _count;

        public static bool operator true(Foo mt)
        {
            return mt._count > 0;
        }

        public static bool operator false(Foo mt)
        {
            return mt._count < 0;
        }
    }

是否是以爲不少有人這樣寫,下面讓你們看一個不多人會知道的科技,感謝walterlv 提供

重寫運算返回

不多人知道實際上重寫 == 能夠返回任意的類型,而不是隻有 bool 類型,請看下面代碼

是能夠編譯經過的,由於我重寫運算

class Foo
    {
        public int Count { get; set; }
     
        public static string operator ==(Foo f1, Foo f2)
        {
            if (f1?.Count == f2?.Count)
            {
                return "lindexi";
            }

            return "";
        }

        public static string operator !=(Foo f1, Foo f2)
        {
            return "";
        }
    }

能夠重寫的運算不少,返回值能夠本身隨意定義

await 任何類型

等待任意的類型,包括已定義的基礎類型,以下面代碼

await "林德熙逗比";

await "不告訴你";

這個代碼是能夠編譯經過的,可是隻有在個人設備。在看了這個博客以後,可能你也能夠在你的設備編譯

其實 await 是能夠寫不少次的,以下面代碼

await await await await await await await await await await await await await await await await await await await await await await await "林德熙逗比";

變量名使用中文

實際上在C#支持全部 Unicode 字符,這是編譯器支持的,因此變量名使用中文也是能夠的,並且可使用特殊的字符

public string H\u00e5rføner()
        {
            return "能夠編譯";
        }

if this == null

通常看到下面的代碼都以爲是不可能進入輸出的

if (this == null) Console.WriteLine("this is null");

若是在 if 裏面都能使用 this == null 成立,那麼必定是vs炸了。實際上這個代碼仍是能夠運行的

在通常的函數,以下面的 Foo 函數,在調用就須要使用f.Foo()的方法,方法裏 this 就是 f 這個對象,若是 f == null那麼在調用方法就直接不讓運行,如何到方法裏的判斷

f.Foo(); //若是 f 爲空,那麼這裏就不執行

void Foo()
{
   // 若是 this 爲空,怎麼能夠調用這個方法
   if (this == null) Console.WriteLine("this is null");
}

其實是能夠作的,請看(C#)if (this == null)?你在逗我,this 怎麼可能爲 null!用 IL 編譯和反編譯看穿一切 - walterlv 這篇博客

如上面博客,關鍵在修改 callvirt 爲 call 調用,直接修改 IL 能夠作出不少特殊的寫法

那麼這個能夠用在哪裏?能夠用在防止大神反編譯,如須要使用下面邏輯

//執行的代碼

//不執行的代碼

此時簡單的反編譯也許會這麼寫

if(true)
{
   //執行的代碼
}
else
{
   //不執行的代碼 
}

可是直接寫 true 很容易讓反編譯看到不使用代碼,並且在優化代碼會被去掉,因此可使用下面代碼

if(this == null)
{
   //執行的代碼
}
else
{
   //不執行的代碼 
}

實際在微軟代碼也是這樣寫,點擊string的實現源代碼能夠看到微軟代碼

重載的運算符

實際上我能夠將 null 強轉某個類,建立一個新的對象,請看代碼

Fantastic fantastic = (FantasticInfo) null;
fantastic.Foo();

這裏的 FantasticInfo 和 Fantastic 沒有任何繼承關係,並且調用 Foo 不會出現空引用,也就是 fantastic 是從一個空的對象建立出來的

是否是以爲上面的科技很黑,實際原理沒有任何黑的科技,請看代碼

public class Fantastic
    {
        private Fantastic()
        {
        }

        public static implicit operator Fantastic(FantasticInfo value) => new Fantastic();

        public void Foo()
        {
        }
    }

    public class FantasticInfo
    {
    }

經過這個方式可讓開發者沒法直接建立 Fantastic 類,並且在不知道 FantasticInfo 的狀況沒法建立 Fantastic 也就是讓你們須要瞭解 FantasticInfo 才能夠經過上面的方法建立,具體請看只有你能 new 出來!.NET 隱藏構造函數的 n 種方法(Builder Pattern / 構造器模式) - walterlv

課件連接: https://r302.cc/J4gxOX

固然還有新的 C# 7.0 和 C# 8.0 的新的語法

例以下面的內部方法返回自身

方法返回自身能夠接近無限調用

有一天我看到了下面的代碼,你猜小夥伴用什麼代碼定義了 Foo 這個代碼?

Foo()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()();

其實只須要定義一個委託,用內部方法實現委託,由於內部方法是能夠返回自身,因而就可使用5行代碼寫出 Foo 的定義

delegate Foo Foo(); // 定義委託

static void Main(string[] args)
{
    Foo Foo() // 定義內部方法
    {
        return Foo;
    }
}

不過括號還不能夠無限使用,由於編譯器有一個表達式的長度限制

無限長度的委託調用

試試這個代碼,也許你能夠無限寫下去,只要 Roslyn 不會炸就能夠

delegate Fx Fx(Fx fx);
 Fx fx = fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx =>
                fx => fx => fx => fx => fx => fx => fx => fx => fx => fx;

如下部分準確來講是 .NET 提供的功能,請問 C# 和 .NET 是什麼關係?其實我也沒法用一兩句話說清,扔掉了 .NET 依然能夠用 C# 寫程序,反過來扔掉 C# 也依然能用 .NET 寫程序

表達式樹獲取函數命名

定義一個類,下面經過表達式樹從類得到函數命名

class Foo
    {
        public void KzcSevfio()
        {
        }
    }
static void Main(string[] args)
        {
            GetMethodName<Foo>(foo => foo.KzcSevfio());
        }

        private static void GetMethodName<T>(Expression<Action<T>> action) where T : class
        {
            if (action.Body is MethodCallExpression expression)
            {
                Console.WriteLine(expression.Method.Name);
            }
        }

這樣就能夠拿到函數的命名

DebuggerDisplay

若是想要在調試的時候,鼠標移動到變量顯示他的信息,能夠重寫類的 ToString

public sealed class Foo
    {
        public int Count { get; set; }

        public override string ToString()
        {
            return Count.ToString();
        }
    }

可是若是 ToString 被其餘地方用了,如何顯示?

微軟告訴你們,使用 DebuggerDisplay 特性

[DebuggerDisplay("{DebuggerDisplay}")]
    public sealed class Foo
    {
        public int Count { get; set; }

        private string DebuggerDisplay => $"(count {Count})";
    }

他可使用私有的屬性、字段,使用方法很簡單

參見Using the DebuggerDisplay Attribute

數字格式

string format = "000;-#;(0)";

string pos = 1.ToString(format);     // 001
string neg = (-1).ToString(format);  // -1
string zer = 0.ToString(format);     // (0)

參見:自定義數字格式字符串

調用堆棧

若是須要得到調用方法的堆棧,可使用這個文章的方法

class Program
    {
        static void Main(string[] args)
        {
            var foo = new Foo();
            foo.F1();
        }
    }

    public sealed class Foo
    {
        public void F1()
        {
            F2();
        }

        void F2()
        {
            var stackTrace = new StackTrace();
            var n = stackTrace.FrameCount;
            for (int i = 0; i < n; i++)
            {
                Console.WriteLine(stackTrace.GetFrame(i).GetMethod().Name);
            }
        }
    }

輸出

F2
F1

參見:WPF 判斷調用方法堆棧

歡迎加入 dotnet 職業技術學院 https://t.me/dotnet_campus 使用 Telegram 方法請看 如何使用 Telegram

特別感謝

特別感謝 呂毅 - walterlv 提供的逗比代碼

特別感謝隊長提供的 .NET Core也是國產化信息系統開發的重要選項 - 張善友 - 博客園 博客。本文開頭爲了更準確的描述,因而抄了隊長的博客內容

相關文章
相關標籤/搜索