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个回答

153
从方法中返回匿名类型并且不使用反射访问成员。
// Useful? probably not.
private void foo()
{
    var user = AnonCast(GetUserTuple(), new { Name = default(string), Badges = default(int) });
    Console.WriteLine("Name: {0} Badges: {1}", user.Name, user.Badges);
}

object GetUserTuple()
{
    return new { Name = "dp", Badges = 5 };
}    

// Using the magic of Type Inference...
static T AnonCast<T>(object obj, T t)
{
   return (T) obj;
}

42
那真的没有什么帮助,实际上很危险。如果GetUserTuple被修改以返回多种类型,那么强制转换将在运行时失败。C#/.Net的一个伟大之处就是编译时检查。最好还是创建一个新类型。 - Jason Jackson
9
@Jason,我确实说过这可能没什么用,但它还是让人惊讶的(而且我认为它是隐藏的)。 - denis phillips
31
虽然看起来很酷,但这似乎是一个相当糟糕的设计选择。基本上你在两个地方定义了匿名类型。此时,只需声明一个真正的结构体并直接使用它即可。 - Paul Alexander
6
@George:这样的约定惯例会被称为... 结构体(struct)? - R. Martinho Fernandes
2
这个技巧被称为“样例转换”,如果返回匿名类型的方法位于另一个程序集中,则不起作用。 - desco
显示剩余7条评论

146

这是一个关于正则表达式和文件路径的有用技巧:

"c:\\program files\\oldway"
@"c:\program file\newway"

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


27
@常量允许换行符。当将多行脚本分配给字符串时非常完美。 - Tor Haugen
11
不要忘记在引号前加上转义符号,也就是说将引号重复一遍。[代码]var candy = @"我喜欢""红色""糖果手杖。";[/代码] - David
5
我倾向于使用 Path.Combine 来构建路径。我肯定会在正则表达式中使用 @ 符号! - Dan McClain
6
@new也是一个变量,而不是关键词:就像@this、@int、@return、@interface等一样。 :) - IAbstract
这个距离太远了:但是,哪些是C#最隐藏的功能或技巧,即使对于C#的粉丝、狂热者和专家也很少知道呢? - publicgk

141

混合(Mixins)即,如果您想要向多个类添加一个功能,但不能对所有类使用一个基类,则可以让每个类实现一个接口(没有成员)。然后,为该接口编写扩展方法,即

public static DeepCopy(this IPrototype p) { ... }
当然,有些清晰度会被牺牲掉。但这样做有效!

4
我认为这就是扩展方法的真正威力。它们基本上允许实现接口方法。 - John B
如果你正在使用NHibernate(或Castle ActiveRecord),并且必须为你的集合使用接口,那么这也非常方便。这样,你就可以为集合接口赋予行为。 - Ryan Lundy
这基本上就是所有LINQ方法的实现方式,记录一下。 - Isabelle Wedin
这是一个链接,讲述了我之前提到的,在 NHibernate 或 Castle ActiveRecord 中使用扩展方法与集合接口的内容:http://devlicio.us/blogs/billy_mccafferty/archive/2008/09/03/custom-collections-with-nhibernate-part-iv-extensions.aspx - Ryan Lundy
7
要是他们允许扩展属性就好了!!!我很讨厌要编写一个明显应该是只读属性的扩展方法。 - John Gibb
除非您可以在事后使用接口来装饰类,否则它并不严格属于mixin - 但仍然很酷 :) - MattDavey

130

不确定为什么有人会想使用 Nullable<bool>。

是,真,假,找不到文件


87
如果期望用户回答是或否的问题,那么如果该问题尚未被回答,则使用 null 是合适的。 - Omar Kooheji
22
可空类型在与数据库进行交互时非常方便,因为表格列通常是可空的。 - tuinstoel
11
是,不是,可能? - Dan Blair
21
保存一个三态复选框的值。 - Shimmy Weitzhandler
8
在 SQL 中,是/否/未知。 - erikkallen
显示剩余18条评论

116

这个方法并不是真正的“隐藏”,只是名称起得有些不妥。

很多人都在关注算法“map”、“reduce”和“filter”。但大多数人没有意识到,.NET 3.5添加了这三个算法,但它们的名称非常类似于SQL,基于它们是LINQ的一部分。

"map" => Select
将数据 从一种形式转换为另一种形式

"reduce" => Aggregate
将值 聚合成单一结果

"filter" => Where
根据条件过滤 数据

利用LINQ在集合中进行内联工作,以前需要迭代和条件语句,现在可以变得非常有价值。学习所有LINQ扩展方法如何帮助使您的代码更紧凑、可维护是值得的。


1
Select函数在单子中的作用类似于“return”函数。请参见https://dev59.com/b-o6XIcBkEYKwwoYTzRZ。 - Mark Cidade
1
只有在使用类似SQL的语法时才需要使用“select”。如果您使用扩展方法语法--someCollection.Where(item => item.Price > 5.99M)--则不需要使用select语句。 - Brad Wilson
7
@Brad,那是一个过滤操作。试着使用不带选择的map操作。 - Eloff
2
在我看来,LINQ 是发生在 C# 中的大事情:https://dev59.com/XkzSa4cB1Zd3GeqPmWvU - Leniel Maccaferri
1
聚合函数的最小签名是“reduce”函数,聚合函数的中间签名是更加强大的“fold”函数! - Brett Widmeier
显示剩余3条评论

115
Environment.NewLine

用于实现系统无关的换行符。


10
这个事情让人烦恼的是,它没有被包含在紧凑框架中。 - Stormenet
14
值得指出的是,这是针对应用程序所在的主机平台而言的 - 因此,如果您正在创建供其他系统使用的数据,则应适当使用 \n 或 \r\n。 - Mesh
这是 .NET 的 BCL 的一部分,不是 C# 本身的特性。 - Abel

111

如果你试图在String.Format表达式内使用花括号...

int foo = 3;
string bar = "blind mice";
String.Format("{{I am in brackets!}} {0} {1}", foo, bar);
//Outputs "{I am in brackets!} 3 blind mice"

19
@Kyralessa:实际上,是花括号,但“curly brackets”也是它们的另一个名称。方括号 [] 是“square brackets”,尖括号 <> 是“angle brackets”。请参见 http://en.wikipedia.org/wiki/Bracket。 - icktoofay
4
你是正确的。但在我发表评论时,它没有说“花括号”。 - Ryan Lundy
2
非常好的指出。我记得我的第一个String.Format看起来像这样: String.Format("{0}我在花括号内{1} {2} {3}", "{", "}", 3, "blind mice"); 一旦我发现通过使用{{和}}进行转义,我感到非常高兴:) - Gertjan
@Kyralessa,@icktoofay:我们在交谈中应该尽量避免成为程序员 :) 参见 jwz vs. Branden - sehe
请参阅http://www.stanford.edu/~pgbovine/geek-behaviors.htm。 - Ryan Lundy
显示剩余2条评论

104
  1. ?? - 合并运算符
  2. using (语句 / 指令) - 一个很好的关键字,不只是用来调用Dispose方法
  3. readonly - 应该更加普及使用
  4. netmodules - 很遗憾 Visual Studio 不支持

6
使用using语句还可以将冗长的命名空间别名为更方便的字符串,例如:using ZipEncode = MyCompany.UtilityCode.Compression.Zip.Encoding;更多信息请查看此处:http://msdn.microsoft.com/en-us/library/sf0df423.aspx - Dave R.
2
这两件事情真的不一样。当调用Dispose时,你可以使用using语句,而当别名类型时,你使用的是using指令。 - Øyvind Skaar
2
如果你想给 #1(就像你在三元运算符中所做的那样)起一个名字,那么 ?? 被称为 null 合并运算符。 - J. Steen
4
正如J Steen所提到的,它被称为null合并运算符。搜索这个! - kokos
1
要在 Google 上搜索 ?? 运算符,请尝试: http://www.google.com/search?q=c%23+%3F%3F+operator - backslash17
显示剩余3条评论

103
@Ed,我不太愿意发布这个内容因为它只是琐事。但我想指出你代码示例中的一个问题:
MyClass c;
  if (obj is MyClass)
    c = obj as MyClass

如果您已经确定 obj 是 MyClass 类型,为什么还要使用 'as' 安全转换?直接使用标准的强制类型转换即可:
c = (MyClass)obj

...永远不会失败。

同样地,你可以简单地说:

MyClass c = obj as MyClass;
if(c != null)
{
   ...
}

我对.NET内部的了解不够充分,但本能告诉我这会将最多两个类型转换操作减少到一个。无论如何都不太可能拖垮处理速度;就个人而言,我认为后一种形式看起来更加简洁。

16
如果将对象转换为精确类型(例如将对象从“ A”转换为其本身而非派生类型),直接强制转换速度比“as”快3倍左右。当转换为派生类型时(例如将对象从“ B”转换为其基类“A”),直接强制转换速度比“as”慢大约0.1倍。使用“is”,然后再使用“as”就有些愚蠢了。 - P Daddy
2
不过,你可以写成 "if ((c = obj as MyClass) != null)"。 - Dave Van den Eynde
10
isas操作符不支持用户类型转换。因此,上述代码使用is操作符来判断obj是否是MyClass的子类(或具有隐式系统定义的转换)。此外,is操作符在null时会失败。这两种边界情况可能对您的代码很重要。例如,您可能想编写以下代码:if( obj == null || obj is MyClass ) c = (MyClass)obj; 但这与 try { c = (MyClass)obj; } catch { } 是严格不同的,因为前者不执行任何用户定义的转换,而后者则会执行。没有null检查,前者也不会在objnull时设置c - Adam Luter
2
在IL中,强制转换使用CASTCLASS指令,而as/is则使用ISINST指令。 - John Gibb
5
我对此进行了测试,将IEnumerable<int>转换为List<int>,并将object(new object())转换为IEnumerable<int>,以确保没有错误:直接转换:5.43纳秒,is->as转换:6.75纳秒,as转换:5.69纳秒。然后测试无效的转换:直接转换:3125000纳秒,as转换:5.41纳秒。结论是:不要担心1%的因素,只需确保在转换可能无效时使用is/as,因为异常(即使已处理)与转换相比非常慢,我们谈论的是一个578000倍的因素。请记住最后一部分,其他部分不重要(.Net FW 4.0,发布版本)。 - Aidiakapi
显示剩余7条评论

98

也许不是一种高级技术,但我经常看到这样的做法,让我感到非常烦恼:

if (x == 1)
{
   x = 2;
}
else
{
   x = 3;
}

可以简化为:

x = (x==1) ? 2 : 3;

10
我的团队已经禁止使用三目运算符了。尽管我从未理解为什么,但有些人就是不喜欢它。 - Justin R.
13
我猜是因为它们不是同一种东西,一个是运算符而不是语句。我个人更喜欢第一种方法。它更加一致并且更容易扩展。运算符不能包含语句,所以一旦你需要扩展代码块,你就得把三目运算符转换成 if 语句。 - kervin
16
可以简化为 x++; 好的,这毫无意义 ^^ - François
5
为考虑所有的 x 值:x = 2 + System.Math.Min(1,System.Math.Abs(x-1)); - mbeckish
38
它实际上被称为“条件运算符” - 它是一个三元运算符,因为它需要三个参数。http://en.wikipedia.org/wiki/Conditional_operator - Blorgbeard
显示剩余12条评论

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