百科问答小站 logo
百科问答小站 font logo



C# 有什么奇技淫巧? 第1页

  

user avatar   Sunnie-Shine 网友的相关建议: 
      

说点奇怪的。

1、小括号加不加有区别的表达式(C# 7)

C# 出了一个叫做 ref struct 的机制,它使得一个对象只可以放在栈内存里(注意是“只可以”,和 struct 不一样,struct 可以因为装箱机制存到堆内存里)。这个 ref struct 由于是在栈内存里分配的连续空间,所以更类似于 C 语言最纯粹的数组。

ref struct 可以由自己实现,不过官方给了一个东西叫 Span<T>。为了引入下面的内容,我们先给出一个 stackalloc 这个指针才用得上的语法:

       unsafe {     // 栈内分配三个连续 int 内存块,并反馈这个内存块的首地址,所以 ptr 是一个指针。     var ptr = stackalloc int[3]; }      

后来有了 Span<T> 后就可以这么写了:

       Span<int> span = stackalloc int[3]; // 隐式转换。 var span = (Span<int>)stackalloc int[3]; // 因为机制的问题,必须指定一种转换。                                          // 否则 span 还是被看成指针。      

可是有个骚操作是

       var span = (stackalloc int[3]); // Span<int> 类型。      

你只需要加一个小括号在 stackalloc 运算符表达式的两侧,span 就自己成 Span<int> 类型了。


2、左值条件运算符(C# 7)

条件运算符从来不能放在赋值号的左侧,不过我们可以用 ref 来做到这一点。

       int a = 2, b = 3, c = default, d = default; (a > b ? ref c : ref d) = 1;      

这样,你说到底是 c 赋值为 1,还是 d 赋值为 1 了呢?

我尝试了 SharpLab,不过老实说这个写法确实有点奇妙。在编译器生成的代码里,第二行的代码实际上被变成了这样:

       (a > b) ? ref c : ref d = 1;      

但我们手写这一行代码,却会引起编译器错误,提示语法不对(括号没写对位置)。这就很骚了。

不过,官方给的解释是,条件表达式在 C# 7 里才引入可以带 ref 的情况,所以写法其实应该是这样的:

       ref int a = ref (x > y ? ref x : ref y);      

注意自己看,右侧的条件表达式有三个 ref,其中后面两个 ref 表示取的 xy 应以引用传递,而不是数值传递;而为什么 x > y 条件左边还得要一个 ref 呢?很简单,因为左侧是 ref 的局部变量,所以为了配套语法所以才需要配对出现 ref 的,所以右边有三个 ref

所以小括号应该把表达式括起来。至于为什么编译器生成的代码括号在条件这一点,始终说不清楚。

另外,ref 这个关键字除了可以用在这里,还可以用到返回值上,指示函数的返回值是带引用的。如果我们这样干了,调用的时候就可以把函数调用作为左值使用。在官方的 Unsafe 类里,有个 As 函数,这个函数可以以不拆箱的形式对引用类型和值类型之间做转换:

       Unsafe.As<object, int>(ref a) = 25;      

由于它的返回值是 ref 的,所以你可以这么用。

不过这个 Unsafe 类有点神奇的是,它并不是 C# 写的,而用的是 IL 直接写的。你敢信?!如果你想看代码:

       /// <summary> /// Reinterprets the given reference as a reference to a value of type <typeparamref name="TTo"/>. /// </summary> [Intrinsic] [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TTo As<TFrom, TTo>(ref TFrom source) {     throw new PlatformNotSupportedException();      // ldarg.0     // ret }      

你就会发现,你根本看不到源代码,而是抛异常。真正的代码其实是被注释的这一坨东西,也就是 IL 代码了。


3、复合空合赋值运算符 ??= (C# 8)

经常我们要写这个代码:

       if (a is null) {     a = b; }      

不过现在可以简化成 a ??= b 了。这是因为语法使得这个写法等价于 a = a ?? b,当 anull 时候,会得到 b 的值作为赋值结果;否则不变。


4、true switch 表达式(C# 8)

switch 表达式是 C# 8 提出的一种概念,把 switch 一大块语句变为一个表达式,这样可以直接拿来赋值。

不过,你可以使用 true switch 表达式,来对一组条件挨个判断和赋值:

       int b = 36; int a = true switch {     _ when b < 0 => 1,     _ when b < 10 => 2,     _ when b < 30 => 3,     _ when b < 60 => 4,     _ => 5 };      

当然,这个是搞笑的,因为其中的 true 可以替换为任意变量。反正你下面的表达式里也没用到。


5、truefalse 运算符

这个是最初 C# 就有的机制。叫 truefalse 运算符,我感觉有一点容易和他们自己本身的用法混淆,所以我更喜欢叫他“布尔算子”(Boolean Functor)。注意这个名字是我自己取的,不要当成术语词汇了。

这个运算符是专门用来重载 &&|| 这两个看起来根本没办法重载的运算符的。不过并不是说 && 就对应 true,而 || 就对应 false。这个说法是错的。实际上,重载运算符的语法是这样:

       public static bool operator true(Instance a) {     // ... }  public static bool operator false(Instance a) {     // ... }      

而实际上,你只重载了他们还不行,你还需要重载三个运算符(其中有一个是可选):&|^。说对了,^ 是可以不重载的。因为它可能有别的用,但最好配合逻辑运算符还是一起重载了吧。

如果你要表达一个自己定义的对象,使得可以使用 a && ba || b 的话,需要照着这个公式来:

  • a && b 等价于 false a ? a : a & b
  • a || b 等价于 true a ? a : a | b

所以,只要你照着这个写法来重载上面说的 truefalse&| 后,就可以使用 && 运算符和 || 运算符对自己自定义的对象了。一定要注意,false 算子配套的是 & 而不是 |


6、巧妙使用构造器 + 初始化器(C# 3)

我们有时候会这么用到代码:

       var map = new Map(); map.A = ...; map.B = ...;      

可是后来有了初始化器之后,语法就变得简单了:

       var map = new Map { A = ..., B = ... };      

这样确实带来了很多方便的地方。不过还是需要这里的 AB 是一个同时有 getset 的属性才行。

不过,如果一个对象并不是 new 出来的,而是其它的对象了呢?

       var map = tempMap;      

在此基础上我们想要给 map 新添加一些内容,这怎么办呢?初始化器语法是不支持这样的。

换个思路,我们去写一个构造器,让这个类(结构)支持自己类型的参数进行初始化:

       public Map(Map another) {     // ... }      

接着,你就可以用这个构造器来初始化对象了:

       var map = new Map(tempMap) { A = ..., B = ... };      


7、传递的弃元(C# 7)

自从弃元(Discard)这玩意儿出来了之后,就有各种高端玩法。比如下面这种:

       int a = 3, b; b = _ = a; // What the hell...      

弃元是可以不声明就可以书写的一个特殊变量,所以你可以放在两个变量赋值之间。甚至……

       int a = 3, b; b = _ = _ = _ = _ = _ = a;      

这个也是用来搞笑的。


8、返回值不是 void 类型的方法,可以不在所有路径返回

有些时候,我们不得不使用死循环。死循环是一种无法跳出循环体的一种循环,比如下面这样的方法,带了一个死循环:

       static int GetValue() {     while (true)     {         // ...         return 3;     }      // Here does not need any return clause. }      

比如这样的组合,因为死循环无法跳出,所以我们无需在 while (true) 外部添加任何的返回语句,因为是无法执行到那里去的,除非死循环里有 break,不过这样已经不是死循环了。


9、隐式转换 null 也不报错

什么?null 什么时候用都要异常的啊。实际上不是。比如这个例子:

       public static implicit operator object(Hell hell) => new object();      

假设我们的 Hell 类型是一个引用类型,然后你就可以优雅的使用这个隐式转换对 Hell 类型作为转换了:

       object o = (Hell)null; o.ToString();      

呃,这样不会报错,你敢信?


10、__arglist 关键字兼容早期编程语言的可变长参数序列

这个不必多说了,大概是这么用的:

       [DllImport(...)] static int printf(string format, __arglist);      


11、我估计你知道扩展方法,但你应该不知道扩展方法还可以引用传递

我们考虑简单一些的情况。翻转一个 bool 变量,我们应该怎么写呢:

       static void Flip(this ref bool @this) {     @this = !@this; }      

应该就可以了。这样调用的方式和之前调用方法的模式完全一样,不过这样的写法可以影响到 @this 变量本身,所以不需要写其他的东西:variable.Flip()


user avatar   iyomumx 网友的相关建议: 
      

基本上看SO的这个问题就够了:

tips and tricks

给__arglist等关键字添个示例吧:

       class Program {     [DllImport("msvcrt.dll")]     public static extern int printf(string format, __arglist);          [DllImport("msvcrt.dll")]     public static extern int scanf(string format, __arglist);          public static void WriteTypes(__arglist)     {         ArgIterator ai = new ArgIterator(__arglist);         while(ai.GetRemainingCount() >0)         {             TypedReference tr = ai.GetNextArg();             Console.WriteLine("Type:{0}", __reftype(tr));             Console.WriteLine("Value:{0}", __refvalue(tr, int));         }     }          static void Main(string[] args)     {         int x, y;         scanf("%d%d", __arglist(out x, out y));         printf("hello %d
hello %x
", __arglist(x, y));         WriteTypes(__arglist(x, y));     } }       

user avatar   hen-hao-34-44 网友的相关建议: 
      

SIMD支持

居然没有提到最新的SIMD指令支持啊,这个貌似碉堡:

       // 两个整数数组相加的常规方法 for (int i = 0; i < size; i++)  {      C[i] = A[i] + B[i];  }  // SIMD 方法, 每次几个元素同时相加,Vector<int>.Count是每个SSE/AVX寄存器容纳int的个数 using System.Numerics.Vectors; for (int i = 0; i < size; i += Vector<int>.Count)  {      Vector<int> v = new Vector<int>(A,i) + new Vector<int>(B,i);      v.CopyTo(C,i);  }       

更碉堡的是,Vector方法是硬件自适应的。也就是说,如果你的硬件只支持SSE2,就每四个int相加,如果支持AVX2,就每8个相加。

无GC模式

调用GC.TryStartNoGCRegion(int64)函数,传入一个内存大小(比如1G)。CLR会开辟一个指定大小的内存区域,然后进入无GC模式。适用于critical path部分的业务逻辑。


user avatar   Ivony 网友的相关建议: 
      

1、泛型类型字典,这个已经基本不算奇技淫巧了,因为大家都在用:

           private static class ConverterCache<T>     {       public static IDbValueConverter<T> ConverterInstance { get; set; }     }      

上面是随便从我一个项目里面摘出来的最简单的类型字典的例子,一个最简单的类型字典只需要一个泛型类,和一个静态字段就够了。

详细请参考:

泛型技巧系列:类型字典和Type Traits

2、利用using来做Scope,其实我个人不是很喜欢这个技巧,在

ASP.NET

MVC里面广泛使用:

       using( Html.BeginForm() ) {   //... }      

3、其实扩展方法可以做很多好玩的东西:

       public static T CastTo<T>( this object obj ) {   return (T) obj; }      
       public static T IfNull<T>( this T value, T defaultValue ) {   if ( value == null || Convert.IsDBNull( value ) )     return defaultValue;   else     return value; }      

我还有一个扩展方法把一个类型所有属性和属性值转换成一个Dictionary的,代码就不贴了,除了一些常规用途,有时候初始化一个Dictionary很麻烦的时候,还可以直接new一个匿名对象,再用这个扩展方法转成Dictionary就完了。

4、运算符重载

运算符重载理论上不算什么奇技淫巧,是个标准特性,但不知道为什么用的人特别少。

       var logger = new ConsoleLogger() + new TextFileLogger( @"C:TempLogs1.log" );      

我的LogUtility,可以将多个Logger用+连接起来,一并记录日志。


5、dynamic绑定

dynamic说白了就是运行时绑定,而且绑定到什么逻辑上是可以在运行时再根据上下文重新定义的。劣势是没有智能提示,但有些地方根本不需要智能提示,或者没有强类型,例如数据绑定。

我们现在的页面数据绑定已经开始倾向于大量使用dynamic,这样一来页面数据绑定便可以先行,根本用不着更新组件接口神马的,需要什么东西直接绑,反正不到运行时不会检测这个东西是不是存在。

而运行时真正执行这个绑定的时候,因为是代码逻辑,所以可以玩出很多花样。

最简单的花样像是大小写不敏感,绑定name属性,找到了一个叫做Name的属性,把值给他就好了。

复杂一点的花样,例如找不到Name属性,就输出一个字符串"[Name]"然后页面上就看到:

姓名:[Name]

这样一来,前端页面原型完全可以演示,根本不用等后面的数据对接,啥时候对接完了,自然变成最终的数据。

再复杂一点的,譬如有个属性改名了,原来的名字不存在了,没事,映射到新的名字,维护一个改名兼容表就完了。


其他的想到再补充。


user avatar   WaynebabyWang 网友的相关建议: 
      

我觉得 泛基是一种很棒的技巧 这个名字是和 @赵劼 一同起的。

       public abstract class MyBase<T> where T :MyBase <T> //被人肉编译器 @vczh 发现少写了<T>  {   public static string DataForThisType {get;set;}  public static T Instance{get;protected set;}  //老赵说的邪恶的部分:让父类操作子类出现了  public static readonly IReadOnlyDictionary<string,MemberInfo> Members   =typeof(T)    .GetMembers()    .ToDictionary(x=>x.Name);  }    public class MyClass:MyBase<MyClass> {   static MyClass()  {    DataForThisType =string.Format(    "MyClass got {0} members",Members.Count);   Instance = new MyClass();  }  }       

子类的static 成员互相不干涉


@赵劼
简单说就是让父类可以静态化地知道当前子类…

------------

补充用途:

1 你有时候希望在父类规定一些行为,让子类无法修改,但是这些实现是依赖一个子类才能获取的值,你又不可能知道所有的子类 ,没办法替它在父类里面初始化,这时候就需要在父类里面定义一个每个子类一个的,但又是静态的空间。

2 你需要每个子类都有一些公开的静态成员,这些成员的类型是子类类型

3 在不知道子类具体类型的情况下,让父类利用泛型参数先替未来的子类做一些事情。


-----

貌似能实现这样功能的其他语言不多。 可是我没有挨个试验过 有人能有反馈告诉我自己熟悉的语言能否做到的话,感激不尽。




  

相关话题

  学了 3 年 Java,包括自学和大学学习,如今工作一开始需要我学 C#,觉得不甘心怎么办? 
  .Net的垃圾回收机制是定时执行还是事件触发? 
  如何由 C# 的「拖控件」阶段继续深入学习? 
  C#是如何做到闪电编译时? 
  C#中函数中参数问题? 
  为什么一些程序员连 Visual Studio 怎么卸载都不会? 
  在C#中 String.Empty和 "" 有什么区别? 
  C++ 和 Java 同样是静态语言,为什么 Java 的代码提示可以做的十分强大? 
  Java为什么选择interface来作为"接口"这个功能的关键字? 
  vs2015导入失败,提示未能正确加载解决方案中一个或多个项目,怎么解决? 

前一个讨论
资本家们是否正在对我国产生伤害?
下一个讨论
为什么人们对科比·布莱恩特和刘强东出轨的态度不一致?





© 2024-12-26 - tinynew.org. All Rights Reserved.
© 2024-12-26 - tinynew.org. 保留所有权利