原文地址:http://www.dotnetcurry.com/csharp/1411/csharp-favorite-features

在写这篇文章的时候,C# 已经有了 17 年的历史了,可以肯定地说它并没有去任何地方。C# 语言团队不断致力于开发新特性,改善开发人员的体验。

在这篇文章中,我在介绍 C# 历史版本的同时分享我最喜欢的特性,在强调实用性的同时展示其优点。

C# 1.0

C#1.0 (ISO-1) 确实算是语言,却没有什么令人兴奋的,缺少许多开发人员喜欢的特性。仔细一想,我能说得出喜欢的只有一个特别的特性 - 隐式和显式接口实现 。

接口在现今开发 C# 的过程中仍然流行使用,以下面的 IDateProvider 接口为例。

public interface IDateProvider{    DateTime GetDate();
}

没有什么特别的,现在着手两种实现方式 - 其中第一种是隐式实现,如下:

public class DefaultDateProvider : IDateProvider{    public DateTime GetDate()    {        return DateTime.Now;}
}

第二种实现是如下的显式实现方式:

public class MinDateProvider : IDateProvider{    DateTime IDateProvider.GetDate(){        return DateTime.MinValue;}
}

注意显式实现如何省略访问修饰符。此外,方法名称被写为 IDateProvider.GetDate() ,它将接口名称作为限定符的前缀。
这两件事情使得调用更明确的。

显式接口实现的一个很好的方面是它强制消费者依赖于接口。显式实现接口的实例对象必须使用接口本身,而没有其他可用的接口成员!

但是,当您将其声明为接口或将此实现作为期望接口的参数传递时,成员将如预期可用。

这是特别有用的方面,因为它强制使用接口。通过直接使用接口,不会将代码耦合到底层实现。同样,明确的接口实现避免命名或方法签名歧义 - 并使单个类可以实现具有相同成员的多个接口。

Jeffery Richter 在他 CLR via C# 一书中提醒了我们显式的接口实现两个主要问题是值类型实例在投射到一个接口和明确实现的方法时将被装箱,同时不能被派生类调用。

请记住,装箱和拆箱会影响性能。任何编程中,你应该评估用例来确保善用工具。

C# 2.0

作为参考,我将列出C# 2.0 (ISO-2) 的所有特性。

  • 匿名方法

  • 协变和逆变

  • 泛型

  • 迭代器

  • 可空类型

  • 部分类型

我最在最喜欢 泛型 还是 迭代器 之间的摇摆,对我来说这是一个非常困难的选择,最终还是更喜欢泛型,顺便说说其中缘由。

因为相比于写迭代器,我更频繁地使用泛型。在 C# 中很多 SOLID 编程原则都是使用泛型来强化的,同样它也有助于保持代码的 干爽。不要误解我的意思,我同时也写了一些迭代器,在 C# 同样中值得采用!

让我们更详细地看看泛型。

编者注:学习如何 在 C# 中 使用泛型来提高应用程序的可维护性

泛型向.NET Framework引入了类型参数的概念,这使得可以设计类和方法来推迟一个或多个类型的规范,直到类或方法被客户端代码声明和实例化为止。

让我们想象一下,我们有一个名为 DataBag 的类,作为一个数据包。它可能看起来像这样:

public class DataBag{    public void Add(object data)    {        // omitted for brevity...}
}

起初看起来这似乎是一个很棒的想法,因为你可以在这个 DataBag 的实例中添加任何东西。但是当你真正想到这意味着什么的时候,会觉得相当骇人。

所有添加的内容都隐式地包装为 System.Object 。此外,如果添加了值类型,则会发生装箱。这些是您应该注意的性能考虑事项。

泛型解决了这一切,同时也增加了类型安全性。让我们修改前面的例子,在类中包含一个类型参数 T ,并注意方法签名的变化。

public class DataBag{    public void Add(T data)    {        // omitted for brevity...}
}

例如现在一个 DataBag 实例将只允许调用者添加 DateTime 实例。要类型安全,没有装箱或拆箱 ... 让更美好的事情发生。

泛型类型参数也可以被约束。通用约束是强有力的,因为它们必须遵守相应的约束条件,只允许有限范围的可用类型参数。

有几种编写泛型类型参数约束的方法,请考虑以下语法:

public class DataBag where T : struct { /* T 值类型 */ }public class DataBag where T : class { /* T 类、接口、委托、数组 */ }public class DataBag where T : new() { /* T 有无参构造函数 */ }public class DataBag where T : IPerson { /* T 继承 IPerson */ }public class DataBag where T : BaseClass { /* T 派生自 BaseClass */ }public class DataBag where T : U { /* T 继承 U, U 也是一个泛型参数 */ }

多个约束是允许的,用逗号分隔。类型参数约束立即生效,即编译错误阻止程序员犯错。考虑下面的DataBag约束。

public class DataBag where T : class{    public void Add(T value)    {        // omitted for brevity...}
}

现在,如果我试图实例化DataBag,C#编译器会让我知道我做错了什么。更具体地说,它要求类型 'DateTime' 必须是一个引用类型,以便将其作为 'T' 参数用于泛型类型或 'Program.DataBag' 方法中。

C# 3.0

下面是C#3.0的主要特性列表。

  • 匿名类型

  • 自动实现的属性

  • 表达树

  • 扩展方法

  • Lambda表达

  • 查询表达式

我徘徊于选择 Lambda表达式 还是 扩展方法 。但是,联系我目前的 C# 编程,相对于任何其他的 C# 运算符,我更多地使用lambda 操作符。我无法表达对它的喜爱。
在C#中有很多机会来利用 lambda 表达式和 lambda 运算符。=> lambda 运算符用于将左侧的输入与右侧的 lambda 表达式体隔离开来。

一些开发人员喜欢将 lambda 表达式看作是表达委托调用的一种较为冗长的方式。Action、Func 类型只是 System 名称空间中的预定义的一般委托。

让我们从解决一个假设的问题开始,使用 lambda 表达式来帮助我们编写一些富有表现力和简洁的 C# 代码。

想象一下,我们有大量代表趋势天气信息的记录。我们可能希望对这些数据执行一些操作,不是在一个典型的循环中遍历它,而是在某个时候,我们可以采用不同的方式。

public class WeatherData{    public DateTime TimeStampUtc { get; set; }    public decimal Temperature { get; set; }
}
private IEnumerable GetWeatherByZipCode(string zipCode) { /* ... */ }

由于 GetWeatherByZipCode 的调用返回一个 IEnumerable,它可能看起来想让你循环迭代。假设我们有一个方法来计算平均温度。

private static decimal CalculateAverageTemperature(IEnumerable weather, DateTime startUtc, DateTime endUtc){    var sumTemp = 0m;    var total = 0;    foreach (var weatherData in weather){        if (weatherData.TimeStampUtc > startUtc &&weatherData.TimeStampUtc < endUtc){++ total;sumTemp += weatherData.Temperature;}}    return sumTemp / total;
}

我们声明一些局部变量来存储所有过滤日期范围内的温度总和及其总和,以便稍后计算平均值。在迭代内是一个 if 逻辑块,用于检查天气数据是否在特定的日期范围内。可以重写如下:

private static decimal CalculateAverageTempatureLambda(IEnumerable weather,DateTime startUtc,DateTime endUtc){    return weather.Where(w => w.TimeStampUtc > startUtc &&w.TimeStampUtc  w.Temperature).Average();
}

正如你所看到的那样,极大地简化了代码。if 逻辑块实际上只是一个谓词,如果天气日期在范围内,我们将继续进行一些额外的处理 - 就像一个过滤器。然后就像调用 Average 一样,当我们需要合计温度时,我们只需要投射 (或选择) IEnumerable 的温度过滤列表。

在 IEnumerable 接口上的 Where 和 Select 扩展方法中,使用 lambd a 表达式作为参数。Where 方法需要一个 Func

C# 4.0

相比之前的版本,C# 4.0 新增的主要特性较少。

  • 动态绑定

  • 嵌入式互操作类型

  • 泛型中的协变和逆变

  • 命名/可选参数

所有这些特性都是非常有用的。但是对于我来说,更倾向于命名可选参数,而不是泛型中的协变和逆变。这两者的取舍,取决于哪个是我最常用的,以及近年来最令 C# 开发人员受益的那个特性。

命名可选参数实至名归,尽管这是一个非常简单的特性,其实用性却很高。我就想问,谁没有写过重载或者带有可选参数的方法?

当您编写可选参数时,您必须为其提供一个默认值。如果你的参数是一个值类型,那么它必须是一个文字或者常数值,或者你可以使用 default 关键字。同样,您可以将值类型声明为 Nullable ,并将其赋值为 null 。假设我们有一个带有 GetData 方法的仓储。

public class Repository{    public DataTable GetData(string storedProcedure,DateTime start = default(DateTime),DateTime? end = null,         int? rows = 50,         int? offSet = null){        // omitted for brevity...        }
}

正如我们所看到的,这个方法的参数列表相当长 - 好在有好几个可选参数。因此,调用者可以忽略它们,并使用默认值。正如你声明的那样,我们可以通过只传递 storedProcedure 参数来调用它。

var repo = new Repository();var sales = repo.GetData("sp_GetHistoricalSales");

现在我们已经熟悉了可选参数特性以及这些特性如何工作,顺便使用一下命名参数。以上面的示例为例,假设我们只希望我们的数据表返回 100 行而不是默认的 50 行。我们可以将我们的调用改为包含一个命名参数,并传递所需的重写值。

var repo = new Repository();var sales = repo.GetData("sp_GetHistoricalSales", rows: 100);

C# 5.0

像C#4.0版本一样,C#5.0版本中没有太多特性 - 但是其中有一个特性非常强大。

  • 异步/等待

  • 调用方信息

当 C# 5.0 发布时,它实际上改变了 C# 开发人员编写异步代码的方式。今天仍然有很多困惑,我在这里向您保证,这比大多数人想象的要简单得多。这是 C# 的一个重大飞跃 - 它引入了一个语言级别的异步模型,它极大地赋予了开发人员编写外观和感觉同步 (或者至少是连续的) 的“异步”代码。

异步编程在处理 I/O 相关(如与数据库、网络、文件系统等进行交互)时非常强大。异步编程通过使用非阻塞方法帮助处理吞吐量。这种机制在透明的异步状态机中代以使用暂停点和相应的延续的方式。

同样,如果 CPU 负载计算的工作量很大,则可能需要考虑异步执行此项工作。这将有助于用户体验,因为UI线程不会被阻塞,而是可以自由地响应其他UI交互。

***编者注:关于 C# 异步编程中使用异步等待的最佳实践,http://www.dotnetcurry.com/csharp/1307/async-await-asynchronous-programming-examples。***

在 C# 5.0 中,当语言添加了两个新的关键字async和await时,异步编程被简化了。这些关键字适用于 Task 和 Task 类型。下表将作为参考:

Task 和 Task 类型表示异步操作。这些操作既可以通过返回一个 Task ,也可以返回void Task。当您使用 async 关键字修改返回方法时,它将使方法主体能够使用await 关键字。在评估 await 关键字时,控制流将返回给调用者,并在该方法中的那一点暂停执行。当等待的操作完成时,会同时恢复执行。

class IOBoundAsyncExample{    // Yes, this is the internet Chuck Norris Database of jokes!private const string Url = "http://api.icndb.com/jokes/random?limitTo=[nerdy]"; internal async Task GetJokeAsync()    {        using (var client = new HttpClient()){               var response = await client.GetStringAsync(Url);                 var result = JsonConvert.DeserializeObject(response); return result.Value.Joke;}}
}public class Result{[JsonProperty("type")] public string Type { get; set; }[JsonProperty("value")] public Value Value { get; set; }
}
public class Value{[JsonProperty("id")] public int Id { get; set; }[JsonProperty("joke")] public string Joke { get; set; }
}

我们用一个名为 GetJokeAsync 的方法定义一个简单的类,当我们调用方法时,该方法返回一个 Task 。对于调用者,GetJokeAsync 方法最终会给你一个字符串 - 或可能出错。

当响应返回时,从被暂停的地方恢复延续执行。然后,将结果 JSON 反序列化到 Result类的实例中,并返回 Joke 属性。

C# 6.0

C# 6.0 有很多很不错的改进,很难选择我最喜欢的特性。

  • 字典初始化

  • 异常过滤器

  • 表达式体成员

  • nameof 操作符

  • 空合并运算符

  • 属性初始化

  • 静态引用

  • 字符串插值

我把范围缩小到三个突出的特性:字符串插值,空合并运算符和 nameof 操作符。

尽管 nameof 操作符很棒,而且我经常用,但是显然另外两个特性更具影响力。又是一个两难的选择,最终还是字符串插值获胜出。

空合并运算符很有用,它能让我少写代码,但不一定防止我的代码中的错误。而使用字符串插值时,可以防止运行时出错。

使用 $ 符号插入字符串文字时,将启用 C# 中的字符串插值语法。相当于告诉 C# 编译器,我们要用到各种 C# 变量、逻辑或表达式来插入到此字符串。这对于手动拼接字符串、甚至是 string.Format 方法来说是一个重要的升级。先看一看如下代码:

class Person{    public string FirstName { get; set; }    public string LastName { get; set; } public override string ToString()        => string.Format("{0} {1}", FirstName);
}

我们有一个简单的 Person 类,具有两个属性,表示名字和姓氏。我们使用 string.Format 重写 ToString 方法。问题是,编译时,开发人员在希望将姓氏也作为结果字符串的一部分时,使用 “{0} {1} ”参数很容易出错。如上述代码中,他们忘了加姓氏。同样,开发人员可以很容易地交换参数位置,在混乱的格式文字只传递了第一个索引,等等...现在考虑用字符串插值实现。

class Person{    public string FirstName { get; set; } = "David";    public string LastName { get; set; } = "Pine";    public DateTime DateOfBirth { get; set; } = new DateTime(1984, 7, 7); public override string ToString()        => $"{FirstName} {LastName} (Born {DateOfBirth:MMMM dd, yyyy})";
}

我冒昧添加 DateOfBirth 属性和一些默认的属性值。另外,我们现在使用字符串插值重写 ToString 方法。作为一名开发人员,犯上述错误要困难得多。最后,我也可以在插值表达式中进行格式化。注意第三次插值,DateOfBirth 是 DateTime 类型 - 因此我们可以使用习惯的所有标准格式。只需使用 :运算符来分隔变量和格式化。

示例输出:

  • David Pine (Born July 7, 1984)

***编者注:有关C#6.0新特性的详细内容,请阅读 http://www.dotnetcurry.com/csharp/1042/csharp-6-new-features***

C# 7.0

  • 表达式体成员

  • 局部方法

  • Out 变量

  • 模式匹配

  • 局部引用和引用返回

  • 元组和解构

模式匹配、元组和 Out 变量之间,我选择了 Out 变量。
模式匹配是伟大的,但我真的不觉得自己经常使用它,至少现在还没有。也许我会在将来更多地使用它,但是到目前为止我所写的所有 C# 代码中,没有太多的地方可以运用。再次,这是一个了不起的特性,只不过不是我最喜欢的 C# 7.0 特性。

元组也是一个很好的改进,是服务于语言的这一重要部分,能成为一等公民真是值得庆祝。逃离了 .Item1,.Item2,.Item3等...的日子,但这么说不够准确,在反序列化中无法还原元组名称使这个公共 API 不太有用。

我同时不喜欢可变的 ValueTuple 类型。不明白这是谁设计的,希望有人能向我解释,感觉就像是一个疏忽。因此,只有 Out 变量合我心意。

从 C# 版本1.0以来,try-parse 模式已经在各种值类型中出现了。模式如下:

public boolean TryParse(string value, out DateTime date){    // omitted for brevity...}

该函数返回一个布尔值,指示给定的字符串值是否能够被解析。如果为 true,则将解析后的值分配给 data参数。它使用方式如下:

if (DateTime.TryParse(someDateString, out var date))
{    // date is now the parsed value}else{    // date is DateTime.MinValue, the default value}

这种模式尽管有用的,却有点麻烦。有时开发人员采取相同的模式,无论解析是否成功。有时可以使用默认值。C# 7.0中的 out变量使得这个更加复杂,尽管我不觉得复杂。

if (DateTime.TryParse(someDateString, out var date))
{    // date is now the parsed value}else{    // date is DateTime.MinValue, the default value}

现在我们移除了 if 语句块的外部声明,并把声明作为参数本身的一部分。使用 var 是合法的,因为类型是已知的。最后,date 变量的范围没有改变。它在声明中内联回 if 语句块之前。

你可能会问:“为什么这是我最喜欢的功能之一?”......这种看起来真的没有什么变化。

不要怀疑,它使我们的 C# 代码更具有表现力。每个人都喜欢扩展方法吧,那么请思考以下代码:

public static class StringExtensions{   

 private delegate bool TryParseDelegate(string s, out T result);   private static T To(string value, TryParseDelegate parse)        => parse(value, out T result) ? result : default;    public static int ToInt32(this string value)        => To(value, int.TryParse);    public static DateTime ToDateTime(this string value)        => To(value, DateTime.TryParse);    public static IPAddress ToIPAddress(this string value)        => To(value, IPAddress.TryParse);    public static TimeSpan ToTimeSpan(this string value)        => To(value, TimeSpan.TryParse);
}

这个扩展方法类看起来简洁、明确、强有力。在定义了一个遵循 try-parse 模式的私有委托之后,我们可以编写一个泛型复合方法,它可以传递泛型类型参数、字符串和 tryparse 泛型委托。现在我们可以放心地使用这些扩展方法,用法如下:

public class Program{    public static void Main(string[] args)    {        var str =            string.Join(                "",                new[] { "James", "Bond", " +7 " }.Select(s => s.ToInt32()));Console.WriteLine(str); // prints "007"}
}

***编辑注意:要了解C#7的所有新功能,请查看教程 http://www.dotnetcurry.com/csharp/1286/csharp-7-new-expected-features***

结论

这篇文章对我个人而言颇具挑战性。C# 的许多特性受我喜欢,因此在每个版本选出一个最喜欢的特性是非常困难的。

每个 C# 版本都包含了强大而有影响力的特性。C# 语言团队以无数的方式进行创新 - 其中之一就是迭代发布。在撰写本文时,C#7.1 和 7.2已正式发布。作为 C# 开发人员,我们正在生活在令人激动人心的语言进化时代!

排列出所有特性对我来说是非常有指示,有助于揭示哪些是实际有用的,哪些对我日常影响最大。我会一如既往的努力,成为务实的开发者!并非每一种特性对于手头的工作来说都是必要的,但了解什么是可用的是很有必要的。

当我们期待 C# 8 的提议和原型时,我对 C# 的未来感到兴奋,它正满怀信心、积极地试图减轻 “十亿美元的错误” (译者注: 图灵奖得主 Tony Hoare 曾指出空引用将造成十亿美元损失)

引用

  • https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history

  • https://en.wikipedia.org/wiki/C_Sharp_(programming_language)

  • https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

  • https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/langversion-compiler-option

  • https://www.wintellect.com/clr-via-c-by-jeffrey-richter/

原文地址:https://www.cnblogs.com/chenug/p/8324786.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

C# 这些年来受欢迎的特性相关推荐

  1. 机器学习算法易受攻击?阿里“安全基建”这样应对AI的不安全

    出品 | AI科技大本营(ID:rgznai100) 数字基建的浪潮之下,诸多行业领域都在加速融合5G.大数据中心.AI等新技术,向数字化转型.近日,多位全国政协委员.院士和安全行业专家提出,应尽快出 ...

  2. 工具的特性_16 个好用的 Code Review 工具

    作者 | Guru99 译者 | 刘雅梦 策划 | 小智 来源:GitHubDaily 原文链接:https://mp.weixin.qq.com/s/xwqVD69o6_qrgV0dxj_2aw C ...

  3. ABP Framework 5.0 RC.1 新特性和变更说明

    .Net 6.0 发布之后,ABP Framework 也在第一时间进行了升级,并在一个多星期后(2021-11-16)发布了 5.0 RC.1 ,新功能和重要变更基本已经确定. 5.0版本新特性 5 ...

  4. Oracle 11g重要特性

    Oracle 11g的重要特性: 修补和升级.RAC One Node 以及 Clusterware 仅限第 2 版:了解如何针对集群实现单一名称,针对单实例数据库实现高可用性,将 OCR 和表决磁盘 ...

  5. 软件架构师 第一部分 基础篇 第三章 架构特性的定义

    一家公司决定使用软件解决特定问题,因此它们收集了该系统的需求列表. 有很多种用于需求收集的技术,这些技术通常由团队在软件开发过程中定义. 但是,架构师在设计软件解决方案时还必须考虑许多其他因素,如图4 ...

  6. 开源Wiki系统:XWiki 2.0.2 发布

    XWiki是一个强大的Java开源的Wiki引擎.它支持一些受欢迎的特性如: * 内容管理(浏览/编辑/预览/保存), * 支持附件, * 版本控制, * 全文本搜索, * 权限管理 * 使用Hibe ...

  7. 深入SecureFile—新一代LOB揭秘000

    编写人:常伟俊 审核人:张陈亚 在Oracle8i 中,LOB 设计是基于下列假设做出的: (1) LOB 实例化的大小预计为几个兆字节. (2) LOB 通常被视为"单写多读"类 ...

  8. 这些个JAVA开源工具(那是相当地多啊)

    OpenCms [Java开源 CMS系统] OpenCms是一个J2EE的产品,它是用Java写成的.它和Tomcat捆绑在一起.但是也能够使用ATG Dynamo.WebLogic和WebSphe ...

  9. 通过分析来精简你的测试的6种方法(译)

    过去几年来,组织开发软件的方法已经发生重大的改变了.从敏捷开发到持续的一切,开发者们正越跑越快,并在更少的时间内开发出更多的内容. 作为一名测试,你需要保持跟进.你必须使业务运行更快,减少到市场的时间 ...

最新文章

  1. linux下大于2TB硬盘格式化及挂载,linux下大于2T的分区方法
  2. C#网页数据采集(一)HtmlAgilityPack
  3. 已经搭载华为鸿蒙,阿尔法S或将搭载华为鸿蒙OS , 4月17极狐带来真相!
  4. c语言的32个关键词
  5. 播放RTMP协议的流媒体的几种选择
  6. Struts2 简介
  7. 探讨LoadRunner的并发用户和集合点
  8. zynq tcp如何从网口发数据_基于TCP/IP协议的电口通信
  9. 桂林电子计算机与信息安全学院,桂林电子科技大学计算机与信息安全学院网络空间安全保研...
  10. IDEA中修改自动生成的Servlet模板,提高编码效率
  11. python怎么对数用log,python中的对数log函数表示及用法
  12. bzoj4816[SDOI2017]数字表格
  13. 轻松解决夜神逍遥模拟器模拟器等模拟器无法连接问题
  14. 部署到gcp_肿瘤内科成功举办基础研究及GCP云端学术论坛
  15. Python不使用科学计数法输出数据的实现方法
  16. sklearn 命令行安装_1.sklearn库的安装
  17. NETCore2.2/3.0+使用带有权限验证的Swagger
  18. 机器学习算法系列(一)- 感知器学习算法(PLA)
  19. 51单片机I/O端口的结构和工作原理
  20. Java异步编程Future应用

热门文章

  1. 7 种 JavaScript 技巧使你更聪明
  2. context:annotation-config/,mvc:annotation-driven/和context:component-scan之间的关系
  3. FZU Problem 2030 括号问题
  4. html页面音乐播放器_Embed代码
  5. .NET 6新特性试用 | 异步流
  6. 记一次 .NET 某新能源汽车锂电池检测程序 UI挂死分析
  7. dotnet中的counters说明(三)
  8. [翻译]在GC上加入DPAD
  9. 2021年,Azure云遇到. NET5,注定开启高光时刻,微软的心,真大!
  10. asp.net core 5.0的一些模块