前言
本文来告诉大家在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 编辑过