本文来告诉你们在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也是国产化信息系统开发的重要选项 - 张善友 - 博客园 博客。本文开头为了更准确的描述,因而抄了队长的博客内容