前言
本文來告訴大家在C#很少有人會發現的科技。即使是工作了好多年的老司機也不一定會知道這些科技,如果覺得我是在騙你,那么請看看本文的內容。
原本最初 C# 的設計是簡單和高效開發的,在經過了這么多年眾多公司和開發者的努力下,整個 C# 里面包含了大量有趣的功能。其中一部分功能是針對于某些特殊需求設計的,例如高性能或高并發或無內存回收等。在經過了 10 多年的迭代,很少人能完全了解整個 C# 語言和框架級做了哪些有趣的功能。
在網上找了很多大神的博客,然后和很多大神聊天,知道了一些科技,于是就在本文和大家分享一下。如果大家有了解本博客里面沒有收藏的科技,還請告訴我。
現在整個 C# 從編譯器到運行時都是開源的,所有權在 dotnet 基金會上,全部開源的項目都基于最友好的 MIT 協議和 Apache 2 開源協議,文檔協議遵循CC-BY協議。這將允許任何人任何組織和企業任意處置,包括使用,復制,修改,合并,發表,分發,再授權,或者銷售。唯一的限制是,軟件中必須包含上述版 權和許可提示,后者協議將會除了為用戶提供版權許可之外,還有專利許可,并且授權是免費,無排他性的(任何個人和企業都能獲得授權)并且永久不可撤銷,用戶使用.NET 和 C# 完全不用擔心收費問題和版權問題,以及后續無法維護問題。而 dotnet 基金會是一個開放的平臺,我也是 dotnet 基金會的成員之一。微軟在 2020 的時候依然是 dotnet 基金會最大的支持組織
現在最火的 dotnet 倉庫是 dotnet csharplang 倉庫,當前的 C# 語言特性由整個社區決定,這是一個官方開放用來討論 C# 語言未來的倉庫,天天都有大佬們在討論語言的特性,歡迎大家加入。
接下來讓我告訴大家一些很少有人會發現的科技
1、無限級判斷空
在 C# 6.0 可以使用??
判斷空,那么就可以使用下面代碼
var v1 = "123";
string v2 = null;
string v3 = null;
var v = v1 ?? v2 ?? v3;
實際上可以無限的使用??
判斷前面一個函數為空,那么問題來了,下面的代碼輸出的是多少?
上面代碼的 foo 就是空的,那么 n 是多少?是 1 還是 2 還是 3 還是空?想要了解這道題的推導過程請看C# 高級面試題 里面寫了很多老司機都不一定能解出例如有下面這個代碼,在這個代碼里面使用了很多的 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如果使用了兩個不同的程序集放在兩個不同的 dll 文件里面,這兩個程序集都有相同命名空間和類型,那么如何使用指定的庫如下面代碼所示,在兩個 dll 里面都定義了 F.Foo
類型
//a.dll
namespace F
{
public class Foo
{
}
}
//b.dll
namespace F
{
public class Foo
{
}
}
大家看到了 C# 6.0 的$
,是不是可以和@
一起?
var str = "kktpqfThiq";
string foo = $@"換行
{str}";
此知識點不再適用,因為在 C# 8.0 的時候,可以按照任意的順序使用 $
和 @
標記。實際上有下面幾個關鍵字是沒有詳細的文檔,可能只有微軟的編譯器才知道
__makeref
__reftype
__refvalue
__arglist
不過在 C# 7.2 可以使用其他的關鍵字做到一些功能,詳細請看我的 C# 7.0 博客如果看到 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# 中使用默認接口方法安全地更新接口很多人都不知道這個科技,這是不安全代碼,從棧申請空間
int* block = stackalloc int[100];
這個是一個有趣的特性實現的功能,是一個編譯器技術,寫給編譯器看的特性。使用 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 不會被執行
var foo = new Foo(10);
if (foo)
{
Console.WriteLine("我的類沒有繼承 bool ,居然可以這樣寫");
}
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 "林德熙逗比";
實際上在C#支持所有 Unicode 字符,這是編譯器支持的,所以變量名使用中文也是可以的,而且可以使用特殊的字符
public string H\u00e5rf?ner()
{
return "可以編譯";
}
甚至以下的奇俠代碼后續也許還能編過,當然現在還是不行的哦
public class ?? : Exception { }
public static bool ??(this string value) => string.IsNullOrWhitespace(value);
string s = null;
if (s.??())
throw new ??();
16、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);
}
}
如果想要在調試的時候,鼠標移動到變量顯示他的信息,可以重寫類的 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);
}
}
}
調用堆棧判斷是存在一個小坑的,請看 dotnet 警惕使用 StackTrace 加獲取方法標記 Attribute 特性在 Release 下被內聯歡迎加入 dotnet 職業技術學院 https://t.me/dotnet_campus 使用 Telegram 方法請看 如何使用 Telegram24、在 try 和 finally 拋異常會發生什么
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 是什么類型,會觸發幾次?答案請看 dotnet C# 在 finally 拋異常會發生什么
private void F1()
{
try
{
_ = new Foo();
}
catch
{
// 忽略
}
}
class Foo
{
public Foo()
{
throw new Exception("lindexi is doubi");
}
~Foo()
{
}
}
請問以上代碼的 ~Foo
是否可以在垃圾回收執行,或者說在構造函數里面拋出異常,是否這個對象可以被垃圾回收答案請看 dotnet C# 如果在構造函數拋出異常 析構函數是否會執行通過 FormatterServices.GetUninitializedObject 方法可以只創建對象不調用構造函數方法
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
{
}
詳細請看 dotnet C# 只創建對象不調用構造函數方法如果你覺得這篇文章對你有幫助,不妨點個贊支持一下!你的支持是我繼續分享知識的動力。如果有任何疑問或需要進一步的幫助,歡迎隨時留言。作者:lindexi
出處:https://blog.lindexi.com/post/C-很少人知道的科技.html聲明:網絡內容,僅供學習,尊重版權,侵權速刪,歉意致謝!
該文章在 2024/9/19 11:39:51 編輯過