C# 很少人知道的科技

本文來告訴大家在C#很少有人會發(fā)現(xiàn)的科技。即使是工作了好多年的老司機也不一定會知道這些科技,如果覺得我是在騙你,那么請看看本文的內容。
原本最初 C# 的設計是簡單和高效開發(fā)的,在經過了這么多年眾多公司和開發(fā)者的努力下,整個 C# 里面包含了大量有趣的功能。其中一部分功能是針對于某些特殊需求設計的,例如高性能或高并發(fā)或無內存回收等。在經過了 10 多年的迭代,很少人能完全了解整個 C# 語言和框架級做了哪些有趣的功能
我在網上找了很多大神的博客,然后和很多大神聊天,知道了一些科技,于是就在本文和大家分享一下。如果大家有了解本博客里面沒有收藏的科技,還請告訴我
現(xiàn)在整個 C# 從編譯器到運行時都是開源的,所有權在 dotnet 基金會上,全部開源的項目都基于最友好的 MIT 協(xié)議和 Apache 2 開源協(xié)議,文檔協(xié)議遵循CC-BY協(xié)議。這將允許任何人任何組織和企業(yè)任意處置,包括使用,復制,修改,合并,發(fā)表,分發(fā),再授權,或者銷售。唯一的限制是,軟件中必須包含上述版 權和許可提示,后者協(xié)議將會除了為用戶提供版權許可之外,還有專利許可,并且授權是免費,無排他性的(任何個人和企業(yè)都能獲得授權)并且永久不可撤銷,用戶使用.NET 和 C# 完全不用擔心收費問題和版權問題,以及后續(xù)無法維護問題。而 dotnet 基金會是一個開放的平臺,我也是 dotnet 基金會的成員之一。微軟在 2020 的時候依然是 dotnet 基金會最大的支持組織
現(xiàn)在最火的 dotnet 倉庫是?dotnet csharplang?倉庫,當前的 C# 語言特性由整個社區(qū)決定,這是一個官方開放用來討論 C# 語言未來的倉庫,天天都有大佬們在討論語言的特性,歡迎大家加入
接下來讓我告訴大家一些很少有人會發(fā)現(xiàn)的科技
無限級判斷空
在 C# 6.0 可以使用??判斷空,那么就可以使用下面代碼
var v1 = "123";
string v2 = null;
string v3 = null;
var v = v1 ?? v2 ?? v3;
實際上可以無限的使用??判斷前面一個函數(shù)為空,那么問題來了,下面的代碼輸出的是多少?
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>();
可以看到上面代碼中,有大量的代碼都是用來作為類型的定義,假設這個值作為某個方法的參數(shù),那才是可怕
一個簡單的方法是使用 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 表達式,或者說用一個委托返回另一個委托。這是一個特別有趣的寫法,通過函數(shù)返回函數(shù)的思想可以用來寫出一些有趣的邏輯,特別是在多層嵌套的時候
當然使用委托可是會出現(xiàn)另一個問題的,請問下面的代碼實際調用的是哪個委托,下面代碼的 a 和 b 和 c 都是?Action委托,同時都不是空的
((a + b + c) - (a + c))();
在數(shù)學上,其實函數(shù)也可以視為變量,很有科技范的 C# 當然也支持如此的功能,將函數(shù)包裝為委托的時候,可以讓委托本身支持加減法哦,只是這個加減法的規(guī)則有些詭異。不信,請猜猜上面代碼執(zhí)行了什么函數(shù)
沖突的類型
在遇到某些類型,特別是放在 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++ 可以使用內聯(lián),不要說 C# 沒有這個功能,實際上也可以使用 FieldOffset 特性實現(xiàn)和 C++ 一樣的內聯(lián)的功能 ,請看下面代碼
[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
指定編譯
這個是一個有趣的特性實現(xiàn)的功能,是一個編譯器技術,寫給編譯器看的特性。使用 Conditional 特性可以讓代碼在指定條件不使用,如下面的代碼,規(guī)定了只有在 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 不會被執(zhí)行
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炸了。實際上這個代碼還是可以運行的
在一般的函數(shù),如下面的 Foo 函數(shù),在調用就需要使用f.Foo()的方法,方法里 this 就是 f 這個對象,如果?f == null那么在調用方法就直接不讓運行,如何到方法里的判斷
f.Foo(); //如果 f 為空,那么這里就不執(zhí)行
void Foo()
{
// 如果 this 為空,怎么可以調用這個方法
if (this == null) Console.WriteLine("this is null");
}
實際上是可以做的,請看(C#)if (this == null)?你在逗我,this 怎么可能為 null!用 IL 編譯和反編譯看穿一切 - walterlv 這篇博客
如上面博客,關鍵在修改?callvirt?為?call?調用,直接修改 IL 可以做出很多特殊的寫法
那么這個可以用在哪里?可以用在防止大神反編譯,如需要使用下面邏輯
//執(zhí)行的代碼
//不執(zhí)行的代碼
此時簡單的反編譯也許會這么寫
if(true)
{
//執(zhí)行的代碼
}
else
{
//不執(zhí)行的代碼
}
但是直接寫 true 很容易讓反編譯看到不使用代碼,而且在優(yōu)化代碼會被去掉,所以可以使用下面代碼
if(this == null)
{
//執(zhí)行的代碼
}
else
{
//不執(zhí)行的代碼
}
實際在微軟代碼也是這樣寫,點擊string的實現(xiàn)源代碼可以看到微軟代碼
重載的運算符
實際上我可以將 null 強轉某個類,創(chuàng)建一個新的對象,請看代碼
Fantastic fantastic = (FantasticInfo) null;
fantastic.Foo();
這里的 FantasticInfo 和 Fantastic 沒有任何繼承關系,而且調用 Foo 不會出現(xiàn)空引用,也就是 fantastic 是從一個空的對象創(chuàng)建出來的
是不是覺得上面的科技很黑,實際原理沒有任何黑的科技,請看代碼
public class Fantastic
{
private Fantastic()
{
}
public static implicit operator Fantastic(FantasticInfo value) => new Fantastic();
public void Foo()
{
}
}
public class FantasticInfo
{
}
通過這個方式可以讓開發(fā)者無法直接創(chuàng)建 Fantastic 類,而且在不知道 FantasticInfo 的情況無法創(chuàng)建 Fantastic 也就是讓大家需要了解 FantasticInfo 才可以通過上面的方法創(chuàng)建,具體請看只有你能 new 出來!.NET 隱藏構造函數(shù)的 n 種方法(Builder Pattern / 構造器模式) - walterlv
課件鏈接:https://r302.cc/J4gxOX
當然還有新的?C# 7.0?和?C# 8.0?的新的語法
例如下面的內部方法返回自身
方法返回自身可以接近無限調用
有一天我看到了下面的代碼,你猜小伙伴用什么代碼定義了 Foo 這個代碼?
Foo()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()();
其實只需要定義一個委托,用內部方法實現(xiàn)委托,因為內部方法是可以返回自身,于是就可以使用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 寫程序
表達式樹獲取函數(shù)命名
定義一個類,下面通過表達式樹從類獲得函數(shù)命名
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);
}
}
這樣就可以拿到函數(shù)的命名
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
數(shù)字格式
string format = "000;-#;(0)";
string pos = 1.ToString(format); // 001
string neg = (-1).ToString(format); // -1
string zer = 0.ToString(format); // (0)
參見:自定義數(shù)字格式字符串
調用堆棧
如果需要獲得調用方法的堆棧,可以使用這個文章的方法
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 職業(yè)技術學院?https://t.me/dotnet_campus?使用 Telegram 方法請看?如何使用 Telegram
特別感謝
特別感謝?呂毅 - walterlv?提供的逗比代碼
特別感謝隊長提供的?.NET Core也是國產化信息系統(tǒng)開發(fā)的重要選項 - 張善友 - 博客園?博客。本文開頭為了更準確的描述,于是抄了隊長的博客內容
【推薦】.NET Core開發(fā)實戰(zhàn)視頻課程?★★★
.NET Core實戰(zhàn)項目之CMS 第一章 入門篇-開篇及總體規(guī)劃
【.NET Core微服務實戰(zhàn)-統(tǒng)一身份認證】開篇及目錄索引
Redis基本使用及百億數(shù)據量中的使用技巧分享(附視頻地址及觀看指南)
.NET Core中的一個接口多種實現(xiàn)的依賴注入與動態(tài)選擇看這篇就夠了
用abp vNext快速開發(fā)Quartz.NET定時任務管理界面
在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務輕松實現(xiàn)作業(yè)調度
