C#的隐藏特性是什么?

1473

在我从这个问题中学到以下知识后,这个想法浮现在我的脑海中:

where T : struct

我们这些C#开发者都知道C#的基础,比如声明、条件语句、循环、运算符等等。
有些人甚至掌握了泛型, 匿名类型, Lambda表达式, LINQ等高级用法。
但是C#还有哪些隐藏的特性或技巧,即使是C#的粉丝、狂热者、专家也很少知道呢?
以下是目前已经揭示出来的特性:


关键词

属性

语法

  • ?? (合并空值) 操作符由 kokos 实现
  • 数字标记由 Nick Berardi 实现
  • where T:newLars Mæhlum 实现
  • 隐式泛型由 Keith 实现
  • 单参数 lambda 表达式由 Keith 实现
  • 自动属性由 Keith 实现
  • 命名空间别名由 Keith 实现
  • 使用 @ 的逐字字符串字面量由 Patrick 实现
  • enum 值由 lfoust 实现
  • @variablenames 由 marxidad 实现
  • event 操作符由 marxidad 实现
  • 格式化字符串括号由 Portman 实现
  • 属性访问器可访问性修饰符由 xanadont 实现
  • 条件 (三元) 操作符 (?:) 由 JasonS 实现
  • checkedunchecked 操作符由 Binoj Antony 实现
  • implicitexplicit 操作符由 Flory 实现

语言特性

Visual Studio 特性

框架

方法和属性

  • String.IsNullOrEmpty()方法由KiwiBastard提供
  • List.ForEach()方法由KiwiBastard提供
  • BeginInvoke()EndInvoke()方法由Will Dean提供
  • Nullable<T>.HasValueNullable<T>.Value属性由Rismo提供
  • GetValueOrDefault方法由John Sheehan提供

提示与技巧

  • 通过Andreas H.R. Nilsson提供的良好方法处理事件处理程序
  • John提供的大写字母比较
  • 无需反射即可访问匿名类型,由dp提供
  • Will提供的快速懒惰实例化集合属性的方法
  • roosteronacid提供的类似JavaScript的匿名内联函数

其他

296个回答

254
泛型中的“default”关键字:
T t = default(T);

如果T是引用类型,则结果为“null”;如果是int,则结果为0;如果是boolean,则结果为false,以此类推。


4
Plus:type?可作为Nullable<type>的快捷方式。default(int) == 0,但default(int?) == null。 - sunside

225

属性通常很重要,但最重要的是DebuggerDisplay。让你省下多年时间。


9
使用 DebuggerDisplay 属性(MSDN):http://msdn.microsoft.com/zh-cn/library/x810d419.aspx - Jon Adams

220

@ 告诉编译器在字符串中忽略任何转义字符。

只是想澄清一下,@ 并不是告诉编译器忽略转义字符,而是实际上告诉编译器将字符串解释为字面量。

如果您有

string s = @"cat
             dog
             fish"

它实际上会打印出来(注意,它甚至包括缩进所使用的空格):

cat
             dog
             fish

字符串不会包括您用于缩进的所有空格吗? - andy
18
是的,它被称为文字串。 - Joan Venge
4
如果输出结果同时显示出将要打印的空格,会更加清晰明了。目前似乎只打印换行符而忽略了空格。 - aleemb
非常有用,可以用于转义正则表达式和长的SQL查询。 - ashes999
它还将 {{ 映射到 {,将 }} 映射到 },使其对于 string.Format() 很有用。 - Ferruccio

219
我认为C#(.NET 3.5)中最被低估和不太知名的功能之一是表达式树,尤其是与泛型和Lambda结合使用时。这是像NInject和Moq这样的新库正在使用的API创建方法。
例如,假设我想向API注册一个方法,而该API需要获取该方法的名称。
给定这个类:
public class MyClass
{
     public void SomeMethod() { /* Do Something */ }
}

以前,开发人员经常使用字符串和类型(或其他大部分基于字符串的内容)进行以下操作:

RegisterMethod(typeof(MyClass), "SomeMethod");

很遗憾,由于缺乏强类型,这很糟糕。如果我重命名"SomeMethod"会怎样呢?然而,在3.5中,我可以以强类型的方式做到这一点:

RegisterMethod<MyClass>(cl => cl.SomeMethod());

RegisterMethod类使用Expression<Action<T>>如下:

void RegisterMethod<T>(Expression<Action<T>> action) where T : class
{
    var expression = (action.Body as MethodCallExpression);

    if (expression != null)
    {
        // TODO: Register method
        Console.WriteLine(expression.Method.Name);
    }
}

这就是我现在热衷于Lambda表达式和表达式树的一个重要原因。


3
我有一个反射实用类,它可以对FieldInfo、PropertyInfo等内容执行相同的操作... - Olmo
是的,这很棒。我能够使用这样的方法编写业务逻辑代码,例如 EditValue(someEmployee, e => e.FirstName); 并自动生成所有与 ViewModel 和 View 编辑该属性相关的管道逻辑(因此,一个带有文本“名字”的标签和一个 TextBox,当用户编辑名称时调用 FirstName 属性的 setter,并使用 getter 更新 View)。这似乎是 C# 中大多数新内部 DSL 的基础。 - Scott Whitlock
4
我认为这些不是不太知名,而是不太被理解。 - Justin Morgan
为什么需要注册方法?我之前从未使用过这个 - 这会在何时何地被使用到? - MoonKnight

208
"yield"会让我想起来。一些属性,如[DefaultValue()]也是我喜欢的。
"var"这个关键字更为人所知,但不太为人所知的是,在.NET 2.0应用程序中也可以使用它(只要您使用.NET 3.5编译器并将其设置为输出2.0代码)。
编辑:kokos,感谢指出??操作符,确实非常有用。由于很难在Google中搜索它(因为??被忽略了),这里是该运算符的MSDN文档页面:?? Operator (C# Reference)

14
默认值的文档说明它并未真正设置属性的值,仅仅是对可视化工具和代码生成器的帮助。 - Boris Callens
2
关于 DefaultValue:与此同时,一些库使用它。ASP.net MVC 在控制器操作的参数上使用 DefaultValue(对于非空类型非常有用)。严格来说,这是一个代码生成器,因为该值不是由编译器设置的,而是由 MVC 的代码设置的。 - Michael Stum
6
"??运算符"的名称是 "Null Coalescing" 运算符。 - Armstrongest
yield 是我最喜欢的,不过 coalesce 运算符也很不错。我没有看到关于 CAS、汇编签名、强名称、GAC 的任何内容... 我想只有 CAS 是 C# 相关的... 但是很多开发人员对安全问题一无所知。 - BrainSlugs83

197

我发现大多数C#开发者不知道可空类型,即基元类型可以具有null值。

double? num1 = null; 
double num2 = num1 ?? -100;

将可为空的双精度浮点数num1设为null,然后将普通双精度浮点数num2设为num1或者-100(如果num1为null)。

http://msdn.microsoft.com/en-us/library/1t3y8s4s(VS.80).aspx

关于可空类型的一点事项:

DateTime? tmp = new DateTime();
tmp = null;
return tmp.ToString();

它会返回String.Empty。查看此链接获取更多详情。


1
日期时间不能设置为空。 - Jason Jackson
2
那么,“int”只是C#中表示System.Int32的语法糖吗?实际上,编译器支持Nullable类型并围绕其构建,例如使它们设置为null(这不能仅通过使用通用结构体完成),并将它们装箱为其基础类型。 - P Daddy
4
@P Daddy - 是的,int是System.Int32的语法糖。它们可以完全互换使用,就像int? === Int32? === Nullable<Int32> === Nullable<int>一样。 - cjk
6
@ck:是的,int是System.Int32的别名,而T?是Nullable<T>的别名,但它不仅仅是语法糖。它是语言中定义的一部分。这些别名不仅使代码更易读,也更符合我们的模型。将Nullable<Double>表达为double?强调了该值所包含的是(或可能是)double,而不仅仅是某个在Double类型上实例化的通用结构。 - P Daddy
3
无论如何,这场争论并不是关于别名(那只是一个比喻不恰当),而是可空类型——使用您喜欢的任何语法——确实是一种语言特性,而不仅仅是泛型的产物。您无法仅使用泛型就复制所有可空类型的功能(与/赋值为null的比较,操作符转发,基础类型或null的装箱,与null合并和“as”运算符的兼容性)。Nullable<T>本身很基本,远非卓越,但可空类型作为语言的概念非常出色。 - P Daddy
显示剩余5条评论

192

下面是一些有趣的隐藏C#功能,以未记录的C#关键字的形式呈现:

__makeref

__reftype

__refvalue

__arglist

这些是未记录的C#关键字(即使Visual Studio也认识它们!),在泛型之前添加了更高效的装箱/拆箱。它们与System.TypedReference结构协同工作。
还有一个__arglist,用于可变长度参数列表。
人们不太了解的一件事是System.WeakReference——一个非常有用的类,可以跟踪对象但仍允许垃圾回收器收集它。
最有用的“隐藏”功能是yield return关键字。它不是真正的隐藏,但很多人不知道它。LINQ是建立在这个基础上的;它通过在幕后生成状态机来实现延迟执行的查询。雷蒙德·陈最近发布了内部、细节

2
Peter Bromberg关于未记录的关键字的更多细节。我仍然不明白是否有理由使用它们。 - HuBeZa
随着泛型的出现,使用__refType、__makeref和__refvalue没有太多(或者干脆没有)好的理由。在.NET 2之前,这些主要用于避免装箱。 - Judah Gabriel Himango
随着.NET 2中引入泛型,使用这些关键字的理由已经很少了,因为它们的目的是在处理值类型时避免装箱。请参见HuBeZa提供的链接,以及http://www.codeproject.com/Articles/38695/UnCommon-C-keywords-A-Look#ud。 - Judah Gabriel Himango

184

使用纯粹、安全的 C# 实现(C++ 中的)共享内存 Unions

在不使用不安全模式和指针的情况下,你可以让类成员在一个类/结构中共享内存空间。给出以下类:

[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;
}

您可以通过操作Int32字段来修改字节字段的值,反之亦然。例如,以下程序:

    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

只需添加 using System.Runtime.InteropServices;


7
使用C联合声明在通过套接字与遗留应用程序通信时,@George的效果非常好。 - scottm
2
在偏移量0处使用int和float也是有意义的。如果您想将浮点数作为位掩码进行操作,则需要这样做,有时您确实需要这样做。特别是如果您想要了解有关浮点数的新知识。 - John Leidegren
2
关于这个问题的烦人之处在于,如果你要将其用作结构体,编译器将强制你在init函数中设置所有变量。因此,如果你有:public A(int int32) { Int32 = int32; } 它会抛出“在控制返回给调用者之前必须完全分配字段'One'”,所以你还需要设置One = Two = Three = Four = 0;。 - manixrock
2
这在处理二进制数据时非常有用。我使用一个名为“Pixel”的结构体,其中int32位于0号位置,四个组件分别位于0、1、2和3号位置。非常适合快速轻松地操作图像数据。 - snarf
57
警告:此方法未考虑字节序。这意味着你的C#代码将无法在所有设备上以相同的方式运行。在小端CPU上(最低有效字节存储在前面),将使用所展示的行为。但是,在大端CPU上,字节顺序将与您预期的相反。请注意在生产代码中如何使用此方法——您的代码可能无法移植到某些移动设备和其他硬件,并可能以非明显的方式出现故障(例如,两个表面上具有相同格式但实际上具有字节顺序相反的文件)。 - Ray Burns
显示剩余2条评论

175

在变量名为关键字时使用 @ 符号。

var @object = new object();
var @string = "";
var @if = IpsoFacto(); 

38
为什么要用关键词作为变量名?在我看来,这会使代码难以阅读和理解。 - Jon
41
它存在的原因是因为CLI需要它来与其他可能使用C#关键字作为成员名称的语言进行互操作。 - Mark Cidade
69
如果您曾经想使用asp.net MVC HTML辅助程序并定义HTML类,那么您会很高兴知道可以使用@class,这样它就不会被识别为关键字"class"。 - Boris Callens
31
非常适合扩展方法。public static void DoSomething(this Bar @this, string foo) { ... } - Jonathan C Dickinson
45
@zihotki: 错误。var a = 5; Console.WriteLine(@a);输出 5。 - SLaks
显示剩余15条评论

167

如果你想在不调用任何finally块或finalizer的情况下退出程序,请使用FailFast

Environment.FailFast()

12
请注意,这种方法还会创建一个内存转储并将一条消息写入Windows错误日志。 - RickNZ
1
当使用此方法时,应注意不会调用任何终结器或应用程序域卸载事件... - AwkwardCoder
1
+1 针对提示表示感谢,但是这不是C#,而是.NET的BCL。 - Abel
System.Diagnostics.Process.GetCurrentProcess().Kill() 更快 - Tolgahan Albayrak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接