前言
本文來告訴大家在C#很少有人會(huì)發(fā)現(xiàn)的科技。即使是工作了好多年的老司機(jī)也不一定會(huì)知道這些科技,如果覺得我是在騙你,那么請看看本文的內(nèi)容。
原本最初 C# 的設(shè)計(jì)是簡單和高效開發(fā)的,在經(jīng)過了這么多年眾多公司和開發(fā)者的努力下,整個(gè) C# 里面包含了大量有趣的功能。其中一部分功能是針對于某些特殊需求設(shè)計(jì)的,例如高性能或高并發(fā)或無內(nèi)存回收等。在經(jīng)過了 10 多年的迭代,很少人能完全了解整個(gè) C# 語言和框架級(jí)做了哪些有趣的功能。
在網(wǎng)上找了很多大神的博客,然后和很多大神聊天,知道了一些科技,于是就在本文和大家分享一下。如果大家有了解本博客里面沒有收藏的科技,還請告訴我。
現(xiàn)在整個(gè) C# 從編譯器到運(yùn)行時(shí)都是開源的,所有權(quán)在 dotnet 基金會(huì)上,全部開源的項(xiàng)目都基于最友好的 MIT 協(xié)議和 Apache 2 開源協(xié)議,文檔協(xié)議遵循CC-BY協(xié)議。這將允許任何人任何組織和企業(yè)任意處置,包括使用,復(fù)制,修改,合并,發(fā)表,分發(fā),再授權(quán),或者銷售。唯一的限制是,軟件中必須包含上述版 權(quán)和許可提示,后者協(xié)議將會(huì)除了為用戶提供版權(quán)許可之外,還有專利許可,并且授權(quán)是免費(fèi),無排他性的(任何個(gè)人和企業(yè)都能獲得授權(quán))并且永久不可撤銷,用戶使用.NET 和 C# 完全不用擔(dān)心收費(fèi)問題和版權(quán)問題,以及后續(xù)無法維護(hù)問題。而 dotnet 基金會(huì)是一個(gè)開放的平臺(tái),我也是 dotnet 基金會(huì)的成員之一。微軟在 2020 的時(shí)候依然是 dotnet 基金會(huì)最大的支持組織
現(xiàn)在最火的 dotnet 倉庫是 dotnet csharplang 倉庫,當(dāng)前的 C# 語言特性由整個(gè)社區(qū)決定,這是一個(gè)官方開放用來討論 C# 語言未來的倉庫,天天都有大佬們在討論語言的特性,歡迎大家加入。
接下來讓我告訴大家一些很少有人會(huì)發(fā)現(xiàn)的科技
1、無限級(jí)判斷空
在 C# 6.0 可以使用??
判斷空,那么就可以使用下面代碼
var v1 = "123";
string v2 = null;
string v3 = null;
var v = v1 ?? v2 ?? v3;
實(shí)際上可以無限的使用??
判斷前面一個(gè)函數(shù)為空,那么問題來了,下面的代碼輸出的是多少?
上面代碼的 foo 就是空的,那么 n 是多少?是 1 還是 2 還是 3 還是空?想要了解這道題的推導(dǎo)過程請看C# 高級(jí)面試題 里面寫了很多老司機(jī)都不一定能解出2、使用 using 關(guān)鍵詞省略長的定義例如有下面這個(gè)代碼,在這個(gè)代碼里面使用了很多的 List 嵌套,如下面代碼所示里面有很多定義的代碼
var foo = new System.Collections.Generic.Dictionary<System.Collections.Generic.List<System.Collections.Generic.List<string>>, string>();
可以看到上面代碼中,有大量的代碼都是用來作為類型的定義,假設(shè)這個(gè)值作為某個(gè)方法的參數(shù),那才是可怕一個(gè)簡單的方法是使用 using 關(guān)鍵詞,如在文件的開頭添加如下代碼
using HvcnrclHnlfk = System.Collections.Generic.Dictionary<System.Collections.Generic.List<System.Collections.Generic.List<string>>,string>;
在添加了上面代碼之后,在這個(gè)文件里的所有用到如上面很長的定義的代碼都可以使用 using
后面的值可以代替,如本文上面使用了 HvcnrclHnlfk
這個(gè)詞,來看看替換之后的代碼長度
var foo = new HvcnrclHnlfk();
實(shí)際上寫到這里我有些不好意思,好像剛剛說的都是大家都知道的,那么我就要開始寫大家很少知道的科技等等,什么是 辣么大
大哇?其實(shí)這是 lambda 表達(dá)式的翻譯
Func<string,string, EventHandler> foo = (x, y) => (s, e) =>
{
var button = (Button) s;
button.Left = x;
button.Top = y;
};
Button1.Click += foo(0, -1);
上面的代碼通過一個(gè) lambda 表達(dá)式返回一個(gè)另一個(gè) lambda 表達(dá)式,或者說用一個(gè)委托返回另一個(gè)委托。這是一個(gè)特別有趣的寫法,通過函數(shù)返回函數(shù)的思想可以用來寫出一些有趣的邏輯,特別是在多層嵌套的時(shí)候當(dāng)然使用委托可是會(huì)出現(xiàn)另一個(gè)問題的,請問下面的代碼實(shí)際調(diào)用的是哪個(gè)委托,下面代碼的 a 和 b 和 c 都是 Action
委托,同時(shí)都不是空的
((a + b + c) - (a + c))();
在數(shù)學(xué)上,其實(shí)函數(shù)也可以視為變量,很有科技范的 C# 當(dāng)然也支持如此的功能,將函數(shù)包裝為委托的時(shí)候,可以讓委托本身支持加減法哦,只是這個(gè)加減法的規(guī)則有些詭異。不信,請猜猜上面代碼執(zhí)行了什么函數(shù)在遇到某些類型,特別是放在 NuGet 上的多個(gè)不同的庫里面的類型,這些類型有相同的類名,如 Data 或 Control 等很通用的命名的時(shí)候,在代碼中如果需要同時(shí)使用這兩個(gè)類,就需要補(bǔ)全整個(gè)命名空間,如下面代碼
var webControl = new System.Web.UI.WebControls.Control();
var formControl = new System.Windows.Forms.Control();
如果經(jīng)常使用這兩個(gè)控件,那么就需要寫很多補(bǔ)全命名空間的代碼,代碼很多。好在微軟的大佬們給出了一個(gè)坑方法,使用這個(gè)方法可以不寫命名空間,或者說只需要在文件開始 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如果使用了兩個(gè)不同的程序集放在兩個(gè)不同的 dll 文件里面,這兩個(gè)程序集都有相同命名空間和類型,那么如何使用指定的庫如下面代碼所示,在兩個(gè) dll 里面都定義了 F.Foo
類型
//a.dll
namespace F
{
public class Foo
{
}
}
//b.dll
namespace F
{
public class Foo
{
}
}
這時(shí)就可以使用 extern alias 關(guān)鍵詞大家看到了 C# 6.0 的$
,是不是可以和@
一起?
var str = "kktpqfThiq";
string foo = $@"換行
{str}";
注意兩個(gè)的順序,反過來直接告訴你代碼不能這樣寫此知識(shí)點(diǎn)不再適用,因?yàn)樵?C# 8.0 的時(shí)候,可以按照任意的順序使用 $
和 @
標(biāo)記。實(shí)際上有下面幾個(gè)關(guān)鍵字是沒有詳細(xì)的文檔,可能只有微軟的編譯器才知道
__makeref
__reftype
__refvalue
__arglist
不過在 C# 7.2 可以使用其他的關(guān)鍵字做到一些功能,詳細(xì)請看我的 C# 7.0 博客如果看到 C++ 可以使用內(nèi)聯(lián),不要說 C# 沒有這個(gè)功能,實(shí)際上也可以使用 FieldOffset 特性實(shí)現(xiàn)和 C++ 一樣的內(nèi)聯(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
變量,修改這個(gè)變量就是修改其他的三個(gè)變量
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
可以看到修改其中某個(gè)值都會(huì)相互影響,這幾個(gè)值共用了相同的一個(gè)內(nèi)存空間實(shí)際上可以給接口使用默認(rèn)方法,使用的方式如下
public static void Foo(this IF1 foo)
{
//實(shí)際上大家也看到是如何定義
}
當(dāng)然了,在 C# 8.0 還有更直接的方法,詳細(xì)請看 在 C# 中使用默認(rèn)接口方法安全地更新接口很多人都不知道這個(gè)科技,這是不安全代碼,從棧申請空間
int* block = stackalloc int[100];
使用的時(shí)候需要小心你的棧也許會(huì)炸掉這個(gè)是一個(gè)有趣的特性實(shí)現(xiàn)的功能,是一個(gè)編譯器技術(shù),寫給編譯器看的特性。使用 Conditional 特性可以讓代碼在指定條件不使用,如下面的代碼,規(guī)定了只有在 DEBUG 宏定義的時(shí)候才讓 F2 方法生效。因此在 Release 下就不會(huì)使用 F2 方法了
public sealed clas Foo
{
public Foo F1()
{
Console.WriteLine("進(jìn)入F1");
return this;
}
[Conditional("DEBUG")]
public void F2()
{
Console.WriteLine("F2");
}
}
static void Main(string[] args)
{
var foo = new Foo();
foo.F1();
foo.F2();
}
結(jié)果是什么,大家也知道,在 Debug 和 Release 輸出是不相同。但是這么簡單的怎么會(huì)在這里說呢,請大家看看這個(gè)代碼輸出什么
static void Main(string[] args)
{
var foo = new Foo();
foo.F1().F2();
}
實(shí)際上在 Release 下什么都不會(huì)輸出,此時(shí)的 F1 不會(huì)被執(zhí)行
var foo = new Foo(10);
if (foo)
{
Console.WriteLine("我的類沒有繼承 bool ,居然可以這樣寫");
}
沒錯(cuò) Foo 沒有繼承 bool 居然可以這樣寫實(shí)際上就是重寫 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;
}
}
是不是覺得很多有人這樣寫,下面讓大家看一個(gè)很少人會(huì)知道的科技,感謝walterlv 提供很少人知道實(shí)際上重寫 ==
可以返回任意的類型,而不是只有 bool 類型,請看下面代碼是可以編譯通過的,因?yàn)槲抑貙戇\(yùn)算
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 "";
}
}
可以重寫的運(yùn)算很多,返回值可以自己隨意定義等待任意的類型,包括已定義的基礎(chǔ)類型,如下面代碼
await "林德熙逗比";
await "不告訴你";
這個(gè)代碼是可以編譯通過的,但是只有在我的設(shè)備。在看了這個(gè)博客之后,可能你也可以在你的設(shè)備編譯其實(shí) await 是可以寫很多次的,如下面代碼
await await await await await await await await await await await await await await await await await await await await await await await "林德熙逗比";
實(shí)際上在C#支持所有 Unicode 字符,這是編譯器支持的,所以變量名使用中文也是可以的,而且可以使用特殊的字符
public string H\u00e5rf?ner()
{
return "可以編譯";
}
甚至以下的奇?zhèn)b代碼后續(xù)也許還能編過,當(dāng)然現(xiàn)在還是不行的哦
public class ?? : Exception { }
public static bool ??(this string value) => string.IsNullOrWhitespace(value);
string s = null;
if (s.??())
throw new ??();
16、if this == null
一般看到下面的代碼都覺得是不可能進(jìn)入輸出的
if (this == null) Console.WriteLine("this is null");
如果在 if 里面都能使用 this == null 成立,那么一定是vs炸了。實(shí)際上這個(gè)代碼還是可以運(yùn)行的在一般的函數(shù),如下面的 Foo 函數(shù),在調(diào)用就需要使用f.Foo()
的方法,方法里 this 就是 f 這個(gè)對象,如果 f == null
那么在調(diào)用方法就直接不讓運(yùn)行,如何到方法里的判斷
f.Foo(); //如果 f 為空,那么這里就不執(zhí)行
void Foo()
{
// 如果 this 為空,怎么可以調(diào)用這個(gè)方法
if (this == null) Console.WriteLine("this is null");
}
實(shí)際上是可以做的,請看(C#)if (this == null)?你在逗我,this 怎么可能為 null!用 IL 編譯和反編譯看穿一切 - walterlv 這篇博客如上面博客,關(guān)鍵在修改 callvirt
為 call
調(diào)用,直接修改 IL 可以做出很多特殊的寫法那么這個(gè)可以用在哪里?可以用在防止大神反編譯,如需要使用下面邏輯
//執(zhí)行的代碼
//不執(zhí)行的代碼
if(true)
{
//執(zhí)行的代碼
}
else
{
//不執(zhí)行的代碼
}
但是直接寫 true 很容易讓反編譯看到不使用代碼,而且在優(yōu)化代碼會(huì)被去掉,所以可以使用下面代碼
if(this == null)
{
//執(zhí)行的代碼
}
else
{
//不執(zhí)行的代碼
}
實(shí)際在微軟代碼也是這樣寫,點(diǎn)擊string的實(shí)現(xiàn)源代碼可以看到微軟代碼實(shí)際上我可以將 null 強(qiáng)轉(zhuǎn)某個(gè)類,創(chuàng)建一個(gè)新的對象,請看代碼
Fantastic fantastic = (FantasticInfo) null;
fantastic.Foo();
這里的 FantasticInfo 和 Fantastic 沒有任何繼承關(guān)系,而且調(diào)用 Foo 不會(huì)出現(xiàn)空引用,也就是 fantastic 是從一個(gè)空的對象創(chuàng)建出來的是不是覺得上面的科技很黑,實(shí)際原理沒有任何黑的科技,請看代碼
public class Fantastic
{
private Fantastic()
{
}
public static implicit operator Fantastic(FantasticInfo value) => new Fantastic();
public void Foo()
{
}
}
public class FantasticInfo
{
}
通過這個(gè)方式可以讓開發(fā)者無法直接創(chuàng)建 Fantastic 類,而且在不知道 FantasticInfo 的情況無法創(chuàng)建 Fantastic 也就是讓大家需要了解 FantasticInfo 才可以通過上面的方法創(chuàng)建,具體請看只有你能 new 出來!.NET 隱藏構(gòu)造函數(shù)的 n 種方法(Builder Pattern / 構(gòu)造器模式) - walterlv課件鏈接:https://r302.cc/J4gxOX當(dāng)然還有新的 C# 7.0 和 C# 8.0 的新的語法有一天我看到了下面的代碼,你猜小伙伴用什么代碼定義了 Foo 這個(gè)代碼?
Foo()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()();
其實(shí)只需要定義一個(gè)委托,用內(nèi)部方法實(shí)現(xiàn)委托,因?yàn)閮?nèi)部方法是可以返回自身,于是就可以使用5行代碼寫出 Foo 的定義
delegate Foo Foo(); // 定義委托
static void Main(string[] args)
{
Foo Foo() // 定義內(nèi)部方法
{
return Foo;
}
}
不過括號(hào)還不可以無限使用,因?yàn)榫幾g器有一個(gè)表達(dá)式的長度限制試試這個(gè)代碼,也許你可以無限寫下去,只要 Roslyn 不會(huì)炸就可以
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;
以下部分準(zhǔn)確來說是 .NET 提供的功能,請問 C# 和 .NET 是什么關(guān)系?其實(shí)我也無法用一兩句話說清,扔掉了 .NET 依然可以用 C# 寫程序,反過來扔掉 C# 也依然能用 .NET 寫程序定義一個(gè)類,下面通過表達(dá)式樹從類獲得函數(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);
}
}
如果想要在調(diào)試的時(shí)候,鼠標(biāo)移動(dòng)到變量顯示他的信息,可以重寫類的 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)
如果需要獲得調(diào)用方法的堆棧,可以使用這個(gè)文章的方法
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);
}
}
}
調(diào)用堆棧判斷是存在一個(gè)小坑的,請看 dotnet 警惕使用 StackTrace 加獲取方法標(biāo)記 Attribute 特性在 Release 下被內(nèi)聯(lián)歡迎加入 dotnet 職業(yè)技術(shù)學(xué)院 https://t.me/dotnet_campus 使用 Telegram 方法請看 如何使用 Telegram24、在 try 和 finally 拋異常會(huì)發(fā)生什么
private void F1()
{
try
{
A();
}
catch (Exception e)
{
}
}
private void A()
{
try
{
throw new ArgumentException("lindexi is doubi");
}
finally
{
throw new FileNotFoundException("lsj is doubi");
}
}
請問在 F1 的 catch 里面收到的 e 是什么類型,會(huì)觸發(fā)幾次?答案請看 dotnet C# 在 finally 拋異常會(huì)發(fā)生什么25、如果在構(gòu)造函數(shù)拋出異常 析構(gòu)函數(shù)是否會(huì)執(zhí)行
private void F1()
{
try
{
_ = new Foo();
}
catch
{
// 忽略
}
}
class Foo
{
public Foo()
{
throw new Exception("lindexi is doubi");
}
~Foo()
{
}
}
請問以上代碼的 ~Foo
是否可以在垃圾回收執(zhí)行,或者說在構(gòu)造函數(shù)里面拋出異常,是否這個(gè)對象可以被垃圾回收答案請看 dotnet C# 如果在構(gòu)造函數(shù)拋出異常 析構(gòu)函數(shù)是否會(huì)執(zhí)行26、只創(chuàng)建對象不調(diào)用構(gòu)造函數(shù)方法通過 FormatterServices.GetUninitializedObject 方法可以只創(chuàng)建對象不調(diào)用構(gòu)造函數(shù)方法
Foo foo = null;
try
{
foo = (Foo) FormatterServices.GetUninitializedObject(typeof(Foo));
var constructorInfo = typeof(Foo).GetConstructor(new Type[0]);
constructorInfo!.Invoke(foo, null);
}
catch
{
}
class Foo
{
}
詳細(xì)請看 dotnet C# 只創(chuàng)建對象不調(diào)用構(gòu)造函數(shù)方法如果你覺得這篇文章對你有幫助,不妨點(diǎn)個(gè)贊支持一下!你的支持是我繼續(xù)分享知識(shí)的動(dòng)力。如果有任何疑問或需要進(jìn)一步的幫助,歡迎隨時(shí)留言。作者:lindexi
出處:https://blog.lindexi.com/post/C-很少人知道的科技.html聲明:網(wǎng)絡(luò)內(nèi)容,僅供學(xué)習(xí),尊重版權(quán),侵權(quán)速刪,歉意致謝!
該文章在 2024/9/19 11:39:51 編輯過