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

751

这并不是C#本身的内容,但我还没有见过真正充分利用 System.IO.Path.Combine() 的人。实际上,整个Path类非常有用,但没有人使用它!

我敢打赌每个生产应用程序都会有以下代码,尽管不应该:

string path = dir + "\\" + fileName;

583

lambda表达式和类型推断是被低估的。 Lambda表达式可以有多个语句,并且它们可以自动地(只要确保签名匹配)同时充当兼容的委托对象,例如:

Console.CancelKeyPress +=
    (sender, e) => {
        Console.WriteLine("CTRL+C detected!\n");
        e.Cancel = true;
    };
请注意,我没有一个new CancellationEventHandler,也不需要指定sendere的类型,它们可以从事件中推断出来。这就是为什么这种方式比编写整个delegate (blah blah)要简便,后者还需要指定参数的类型。 Lambdas 不需要返回任何内容,在这种情境下类型推断非常强大。
顺便说一下,您总是可以返回在函数式编程中创建 Lambdas 的 Lambdas。例如,这里有一个创建处理 Button.Click 事件的 lambda 的 lambda:
Func<int, int, EventHandler> makeHandler =
    (dx, dy) => (sender, e) => {
        var btn = (Button) sender;
        btn.Top += dy;
        btn.Left += dx;
    };

btnUp.Click += makeHandler(0, -1);
btnDown.Click += makeHandler(0, 1);
btnLeft.Click += makeHandler(-1, 0);
btnRight.Click += makeHandler(1, 0);

注意链式结构:(dx, dy) => (sender, e) =>

这就是为什么我很高兴学习了函数式编程课程 :-)

除了C语言中的指针,我认为这是你应该学习的另一个基本概念 :-)


527

根据Rick Strahl的说法:

你可以使用??操作符来链接多个null值比较。

string result = value1 ?? value2 ?? value3 ?? String.Empty;

453

别名泛型:

using ASimpleName = Dictionary<string, Dictionary<string, List<string>>>;

它允许您使用ASimpleName,而不是Dictionary<string, Dictionary<string, List<string>>>

在您需要在很多地方使用相同的通用大型复杂结构时,请使用它。


437

来自CLR via C#:

在规范化字符串时,强烈建议使用 ToUpperInvariant 而不是 ToLowerInvariant ,因为Microsoft已经针对执行大写字母比较进行了优化。

我记得有一次我的同事总是在比较之前将字符串转换为大写。我一直想知道他为什么这样做,因为我感觉先转换成小写更加“自然”。读过这本书后,现在我知道原因了。


254
当你“将一个字符串转换为大写”时,会创建第二个临时字符串对象。我认为这种比较方式不太受欢迎,最好的方法是使用String.Equals(stringA, stringB, StringComparison.CurrentCultureIgnoreCase),这样根本不会创建临时字符串。 - Anthony
32
在比较大写字符串时,有哪些优化措施是在小写字符串上无法实现的?我不理解为什么一个比另一个更优。 - Parappa
36
使用大写字母而不是小写字母也可以防止在某些文化中出现错误行为。例如,在土耳其语中,两个小写字母"i"映射到同一个大写字母"I"。搜索谷歌上的“turkish i”获取更多细节信息。 - Neil
34
我尝试了对ToUpperInvariant和ToLowerInvariant进行基准测试,发现它们在.NET 2.0或3.5下的性能没有任何区别。因此,并没有什么理由强烈推荐一个而不是另一个。请注意,我的翻译尽力保持与原文意思一致,同时使其更通俗易懂。 - Rasmus Faber
19
使用ToUpperInvariant更受青睐,因为它可以使所有字符往返无误。 请参考http://msdn.microsoft.com/en-us/library/bb386042.aspx。若要进行比较,请写"a".Equals("A", StringComparison.OrdinalIgnoreCase) - SLaks
显示剩余10条评论

407

我最喜欢的技巧是使用空值合并运算符和括号,自动实例化集合。

private IList<Foo> _foo;

public IList<Foo> ListOfFoo 
    { get { return _foo ?? (_foo = new List<Foo>()); } }

23
你不觉得阅读很难吗? - Riri
72
对于新手来说,这可能有点难读,但它很简洁,包含了几个程序员应该知道和理解的模式和语言特性。因此,虽然一开始可能有些困难,但它提供了一个学习的理由。 - user1228
38
延迟实例化有些不良行为,因为它是一个避免思考类不变量的低效做法。此外,它还存在并发问题。 - John Leidegren
17
我一直听说这是不好的做法,调用一个属性不应该像这样执行某些操作。如果你在那里设置了一个空值,对于使用你的 API 的人来说,这将非常奇怪。 - Ian
8
除了将ListOfFoo设置为null既不是该类的契约的一部分,也不是一个好的实践方法之外,这就是没有setter的原因。同时,ListOfFoo保证返回一个集合而不是null。如果你做了两件坏事(创建一个setter并将集合设置为null),这只是一个非常奇怪的抱怨,会导致你的期望变得错误。我也不建议在getter中使用Environment.Exit(),但这与本答案无关。 - user1228
显示剩余18条评论

314

避免检查空事件处理程序

在声明事件时添加一个空委托,可以避免在调用事件之前总是需要检查事件是否为空,这是非常好的。例如:

public delegate void MyClickHandler(object sender, string myValue);
public event MyClickHandler Click = delegate {}; // add empty delegate!

让你做这件事

public void DoSomething()
{
    Click(this, "foo");
}

不要这样做

public void DoSomething()
{
    // Unnecessary!
    MyClickHandler click = Click;
    if (click != null) // Unnecessary! 
    {
        click(this, "foo");
    }
}

请参阅此相关讨论和此Eric Lippert 的博客文章,关于该主题(以及可能的缺点)。


87
如果您依赖这种技术,然后必须对类进行序列化,我相信会出现一个问题。您将消除事件,然后在反序列化时将获得 NullReference。因此,可以继续坚持“旧方式”来做事情。这样更安全。 - sirrocco
16
你仍然可以将事件处理程序设置为 null,这样你仍然可能会得到空引用,并且仍然存在竞态条件。 - Robert Paulson
64
快速的性能测试结果显示,没有进行空值检查的虚拟订阅事件处理程序所需时间大约是进行了空值检查的未订阅事件处理程序的2倍。没有进行空值检查的多播事件处理程序所需时间大约是进行了空值检查的单播事件处理程序的3.5倍。 - P Daddy
54
通过始终具有自我订阅者,避免了空指针检查的需要。即使作为空事件,这也会带来不必要的开销。如果没有订阅者,您不希望触发事件,也不希望始终先触发一个空的虚拟事件。我认为这是糟糕的代码。 - Keith
56
基于上面评论的原因,这是一个糟糕的建议。如果你必须让你的代码看起来“干净”,可以使用扩展方法来检查空值然后调用事件。有修改权限的人应该确实将其缺点添加到此答案中。 - Greg
显示剩余21条评论

304

除此之外,还有以下内容:

1)隐式泛型(为什么只在方法上使用而不是类上?)

void GenericMethod<T>( T input ) { ... }

//Infer type, so
GenericMethod<int>(23); //You don't need the <>.
GenericMethod(23);      //Is enough.

2) 只有一个参数的简单lambda表达式:

x => x.ToString() //simplify so many calls

3) 匿名类型和初始化器:

//Duck-typed: works with any .Add method.
var colours = new Dictionary<string, string> {
    { "red", "#ff0000" },
    { "green", "#00ff00" },
    { "blue", "#0000ff" }
};

int[] arrayOfInt = { 1, 2, 3, 4, 5 };

另一个例子:

4) 自动属性可以有不同的作用域:

public int MyId { get; private set; }

感谢 @pzycoman 的提醒:

5) 命名空间别名(虽然你可能不需要这个特定的区分):

using web = System.Web.UI.WebControls;
using win = System.Windows.Forms;

web::Control aWebControl = new web::Control();
win::Control aFormControl = new win::Control();

14
在#3中,您可以使用Enumerable.Range(1,5)。 - Echostorm
18
我认为自从 1.0 版本以来,您已能够使用 int[] nums = {1,2,3}; 初始化数组,甚至不需要使用 "new" 关键字。 - Lucas
13
没有参数的lambda表达式: ()=> DoSomething(); - Pablo Retyk
5
我曾使用过 { get; internal set; } 和 { get; protected set; },因此这种模式是一致的。 - Keith
7
@Kirk Broadhurst - 你是对的 - 在这个例子中,new web.Control() 也可以工作。 :: 语法强制将前缀视为命名空间别名,因此您可以拥有名为 web 的类,并且 web::Control 语法仍将起作用,而 web.Control 语法无法确定是要检查类还是命名空间。 因此,当进行命名空间别名时,我倾向于始终使用 :: - Keith
显示剩余21条评论

285

我很长一段时间都不知道 "as" 关键字的用法。

MyClass myObject = (MyClass) obj;

对比

MyClass myObject = obj as MyClass;

如果obj不是MyClass类型,则第二个将返回null,而不是抛出类转换异常。


42
不要过度使用“as”。许多人似乎使用它是因为他们喜欢其语法,尽管他们想要(ToType)x的语义。 - Scott Langham
4
我不相信它提供更好的性能。你进行了分析吗?(显然,如果转换失败时会提供更好的性能...但是当你使用(MyClass)转换时,失败是例外情况...非常罕见(如果它们真的发生了),所以这没有任何区别。) - Scott Langham
7
只有在通常情况下转换失败时,这种方法才会更有效率。否则,直接转换 (type)object 更快。不过,相比于返回 null,直接转换抛出异常需要更长的时间。 - Spence
15
和使用 "as" 关键字类似,使用 "is" 关键字同样非常有用。 - dkpatt
28
如果你滥用它,可能会在后面导致空引用异常,而如果早些时候采用无效转换异常就可以避免这种情况。 - Andrei Rînea
显示剩余7条评论

261

我喜欢的两个东西是自动属性,这样你就可以将代码进一步折叠:

private string _name;
public string Name
{
    get
    {
        return _name;
    }
    set
    {
        _name = value;
    }
}

变成

public string Name { get; set;}

还有对象初始化器:

Employee emp = new Employee();
emp.Name = "John Smith";
emp.StartDate = DateTime.Now();
成为
Employee emp = new Employee {Name="John Smith", StartDate=DateTime.Now()}

6
需要注意的是,自动属性是仅在 C# 3.0 中出现的功能。 - Jared Updike
21
自动属性是在3.0编译器中引入的。但由于编译器可以设置为输出2.0代码,所以它们可以正常工作。只是不要试图在旧编译器中编译带有自动属性的2.0代码! - Joshua Shannon
74
许多人没有意识到的是,获取和设置可以具有不同的可访问性,例如:public string Name { get; private set;} - Nader Shirazie
7
自动属性的唯一问题在于似乎无法提供默认的初始化值。 - Stein Åsmul
7
@ANeves: 不是这样的。从那个页面来看:DefaultValueAttribute不会自动使用属性值初始化成员变量,你需要在代码中设置初始值。 [DefaultValue]用于设计器,以便它知道是否以粗体显示属性(表示非默认值)。 - Roger Lipscombe
显示剩余8条评论

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