*:first-child {
margin-top: 0 !important;
}

body>*:last-child {
margin-bottom: 0 !important;
}

/* BLOCKS
=============================================================================*/

p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0;
}

/* HEADERS
=============================================================================*/

h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}

h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}

h1 {
font-size: 28px;
color: #000;
}

h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
color: #000;
}

h3 {
font-size: 18px;
}

h4 {
font-size: 16px;
}

h5 {
font-size: 14px;
}

h6 {
color: #777;
font-size: 14px;
}

body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}

h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}

/* LINKS
=============================================================================*/

a {
color: #4183C4;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

/* LISTS
=============================================================================*/

ul, ol {
padding-left: 30px;
}

ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}

ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}

dl {
padding: 0;
}

dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}

dl dt:first-child {
padding: 0;
}

dl dt>:first-child {
margin-top: 0px;
}

dl dt>:last-child {
margin-bottom: 0px;
}

dl dd {
margin: 0 0 15px;
padding: 0 15px;
}

dl dd>:first-child {
margin-top: 0px;
}

dl dd>:last-child {
margin-bottom: 0px;
}

/* CODE
=============================================================================*/

pre, code, tt {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}

code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}

pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}

pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}

pre code, pre tt {
background-color: transparent;
border: none;
}

kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}

/* QUOTES
=============================================================================*/

blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}

blockquote>:first-child {
margin-top: 0px;
}

blockquote>:last-child {
margin-bottom: 0px;
}

/* HORIZONTAL RULES
=============================================================================*/

hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}

/* TABLES
=============================================================================*/

table {
font-size :12.5px;
border-spacing: 0;
border-collapse: collapse;
}

table th {
font-weight: bold;
}

table th, table td {
border: 1px solid #ccc;
padding: 6px 13px;
}

table tr {
border-top: 1px solid #ccc;
background-color: #fff;
}

table tr:nth-child(2n) {
background-color: #f8f8f8;
}

/* IMAGES
=============================================================================*/

img {
max-width: 100%
}
-->

C#6.0随着.NET Framework 4.6而来,.NET Framework 4.6相较于.NET Framework 4.5(包括4.5.1、4.5.2等)变化不是太大,C#6.0也不像之前版本升级时总有几个吸引眼球的变化,而只是一些语法糖般的变化,不过这个版本在编译器方面做了许多工作。每次升级总会有一些侧重点吧,而且C#发展这么多年,这么多特性已经使其位于顶级编程语言行列(从编码舒适度来看甩Java好几条街),实在是也很难再有什么突破性变化了吧。

本文整理下C# 6的新变化,希望对看到的园友有一定帮助。

C#6开始,C#和C++有了很大的不同,本文也不再继续介绍C#6新特性对应的C++特性。

自动属性改进

1.只读自动属性 可以声明真正的只读(不可变)属性(自动属性),在之前版本中如果要让自动属性不可写,唯一的方法就是将set设置为private。下面的示例提供了新旧两种代码的对比。

// 以前
public string Name {get; private set; } // 当前
public string Name {get; }

对于这种只有get的只读自动属性,可以在构造函数中进行赋值,或者使用下面这种新的初始化语法进行赋值。

2.自动属性初始化
可以使用如下的语法对自动属性或自动只读属性进行初始化。

public string Name {get; set;} = "World";

public string Name {get; } = "World";

注意赋值语句最后有个分号,当然少了这个分号,VS立马就给错误提示了。

表达式体作为函数实现

对于很多只有一行代码的函数,使用这个新特性可以减少一对括号,使代码看起来更简洁。如下面两种方法是等价的。

public string Hello(string name)
{
return $"Hello, {name}";
} public string Hello(string name) => $"Hello, {name}";

对于只读属性也可以使用这个特性,如:

// .NET Core由Configuration中读取配置
public string DefaultFileName => this._configuration["DefaultFileName"];

null条件运算符

这个运算符有两种形式,分别为?.?[]。在C#支持这个运算符之前,我们访问引用类型对象的属性或索引器都需要首先判断该对象是否为空以免发生“空引用”异常。常见写法如:

if (section != null)
{
var path = section[name];
}

而使用null条件运算符可以将上面的语句简化为:

var path = section?[name];

虽然看似只是消灭了两行括号,但看过周爱民老师的《JavaScript语言精粹》后了解到这是由过程式语言到函数式语言一种转变,即由命令语句转变为表达式。
如果访问的属性为引用类型,通过null条件运算符得到的结果的类型不变,而如果访问的属性为值类型,则通过null条件运算符得到的结果的类型为该值类型对于的可空类型的包装。
即如果name为string类型,则path依然为string类型。而如果name为int类型,则path会变成int?类型。可以通过??使path的类型和name的类型一致:

var path = section?[name]??0;

虽然null条件运算符可以大大减少运行时错误(忘了检查引用是否为空)的发生。但不能忽视由于引用类型对象为空而导致属性取默认值所带来的结果错误。

null可空运算符不只对访问属性、索引器等,也可以用来调用方法或触发事件。如下面两种写法:

if (_mysqlConn != null)
{
MysqlConn.Close();
} _mysqlConn?.Close();

调用事件也是同理:

PropertyChanged?.Invoke(e);

using导入静态类型

在这个特性出现之前,我们使用using指令只能引入命名空间。这个特性出现后,也可以将类型导入,从而可以直接调用类型中的静态方法。下面的例子可以很好的展示这个语法的使用:

using static System.String;

return !IsNullOrEmpty(path);

至今位置在实际编码过程中没发现这个新功能有啥太明显的作用。可能唯一能少码一些代码的地方就像控制台应用程序中可以通过导入Console类,来减少调用频繁调用Console.WriteLine()方法时的输入量。

字符串插值

在这个特性出现之前,我们用的最多的的字符串插值方法就是string.Format(),如:

var str = string.Format("{0}-{1}",No,Name);

string.Format()的主要缺点就是很容易弄乱参数与占位符的位置,导致拼出错误的字符串。
现在有了这个特性,string.Format()方法基本可以退役了。之前的代码可以直接改写为下面的样子:

var str = $"{No}-{Name}";

以前用于string.Format()占位符的格式化字符串对于字符串插值语法也有效:

var str = string.Format("{0:00}",No);
var str = $"{No:00}";

对于时间格式化也可以按如下简化,并且这种写法可以更容易的把时间“融入”到字符串中:

var dateStr = DateTime.Now.ToString("yyyy-MM-dd");
var dataStr = $"{DateTime.Now:yyyy-MM-dd}";

更强大的是$可以和@结合使用,这样遇到多行字符串,使用@表示的字符串字面量可以直接写成多行,同时可以使用$来实现的字符串插值。对于在代码中嵌入SQL来说这是一个非常方便的特性。

var sql = $@"insert into {table} (fromid,toid,strength)
values ({fromid},{toid},{strength})";

如上面这个字符串,我们既不需要用+做多行连接,又不用写string.Format(),整个代码看上去干净、整洁。
只要是C#表达式,即使包含非常复杂的计算也都可以用于字符串插值。

nameof关键字

nameof的功能很单一,就是获取一个符号的名称,这个“符号”可以是参数,成员,属性等。nameof的一个用途通过下面的例子来展示:

如下方法是一个常见的检查参数是否为空的方法(这段代码自己的项目用了很久,但忘记最初是从哪“借鉴”的了):

public static void CheckNotNull<T>(this T value, string paramName) where T : class
{
Require<ArgumentNullException>(value != null, string.Format(Resources.ParameterCheck_NotNull, paramName));
}
// Require方法的实现省略,其功能是检查参数值是否为空,如果为空记录一条含有参数名称的日志

调用这个方法也很简单:

public void Process(int no, string name)
{
no.CheckNotNull(nameof(no));
name.CheckNotNull(nameof(name));
// ...省略
}

在nameof关键字出现之前,我们只能写常量字符串。

no.CheckNotNull("no");

如果参数名一直不变,这样的常量字符串写法就不会有问题。但现实情况是项目重构经常会发生,一但参数名改变,我们可能会忘记修改常量字符串。而如果我们使用nameof关键字,我们在更改参数名的同时VS这样的IDE都是自动帮我们进行重构,把所有用到此参数的地方都进行重命名操作。

另外一个nameof常用的场景是如WPF这种的XAML应用中,当属性需要触发PropertyChanged时便利性会有很大提升。在nameof关键出现之前,MVVMLight库的做法是要求传入一个lambda表达式,通过解析Lambda表达式体来使调用强类型话,并保证传给PropertyChanged的参数名是正确的。代码如下:

public string Name
{
get { return _name; }
// Set方法会最终调用下面的RaisePropertyChanging方法
set { Set(() => Name, ref _name, value); }
} // MvvmLight源代码(部分,来自ObservableObject.cs文见)
protected virtual void RaisePropertyChanging<T>(Expression<Func<T>> propertyExpression)
{
var handler = PropertyChanging;
if (handler != null)
{
var propertyName = GetPropertyName(propertyExpression);
handler(this, new PropertyChangingEventArgs(propertyName));
}
}

现在有了nameof关键字,上面的Name属性可以实现为:

public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(UXComponents.ViewModel.Name)));
}
}
}

节省的代码和运算复杂度都是很多的。

异常过滤器

异常过滤去用于在catch捕获异常前进行一次过滤。博主还没有在项目中用到过这个特性,这里用MSDN上的一段代码来说明。

public static async Task<string> MakeRequest()
{
var client = new HttpClient();
var streamTask = client.GetStringAsync("https://localHost:10000");
try {
var responseText = await streamTask;
return responseText;
} catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
{
return "Site Moved";
}
}

catch语句中when开始那部分就是新增的异常过滤器。
如果when后面语句(即异常过滤器)执行结果为truecatch段中的代码会正常执行,而如果异常过滤器执行结果为falsecatch段会被跳过。
在异常过滤器出现之前,类似功能的代码要实现为:

try {
var responseText = await streamTask;
return responseText;
} catch (System.Net.Http.HttpRequestException e)
{
if (e.Message.Contains("301"))
return "Site Moved";
else
throw;
}

但是之前这种实现方式中通过throw来重新抛出异常会导致一些异常信息丢失。而使用异常过滤器返回false跳过的异常会保留所有原始的异常信息。
异常过滤器也是叠加使用,如:

try {
var responseText = await streamTask;
return responseText;
} catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
{
return "Site Moved";
} catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("304"))
{
return "Use the Cache";
}

第二个推荐的异常过滤器的使用模式是需要将一个更泛化的异常catch放在具体的catch之前的。
比如,记录日志这种需求,我们需要在一个泛化的异常catch中记录日志,但不处理异常,异常可以继续向下传递,并被更具体的catch进行处理。

可以实现一个这样的记录异常的扩展方法。

public static bool LogException(this Exception e)
{
Console.Error.WriteLine(@"Exceptions happen: {e}");
return false;
}

然后可以像如下这样进行使用:

try {
PerformFailingOperation();
} catch (Exception e) when (e.LogException())
{
// This is never reached!
}
catch (RecoverableException ex)
{
Console.WriteLine(ex.ToString());
}

由于上面的LogException方法返回false,所以第一个catch不会处理异常,异常会向下传播并被第二个catch所捕获。

第三个异常过滤器的使用场景是用于区分在调试模式下和生产模式下的异常的处理。

try {
PerformFailingOperation();
}
catch (RecoverableException ex) when (!System.Diagnostics.Debugger.IsAttached)
{
Console.WriteLine(ex.ToString());
}

如上代码,在附加调试器的情况下catch将不被执行,异常向下抛出并被调试器捕获从而进入调试状态。而在生产模式,异常会被捕获并处理。
在这个特性出现之前,如果我们想方便的调试出现的异常最常见的方法就是在catch段的第一行打上断点。而现在有了异常过滤器,只需要添加这样一个when子句就可以了。

C#异常过滤器的语法有点支持模式匹配的语言的影子,据说C#7会全面支持模式匹配。期待一下。

索引初始化器

在C#3起出现的集合初始化器可以使我们用如下这样的方式去初始化ListDictionary。代码例子之前的博文

List<Plant> plants = new List<Plant> {
new Plant { Name = "牡丹", Category = "芍药科", ImageId =6},
new Plant { Name = "莲", Category = "莲科", ImageId =10 },
new Plant { Name = "柳", Category = "杨柳科", ImageId = 12 }
}; Dictionary<int, Plant> plantsDic = new Dictionary<int, Plant>
{
{ 11, new Plant { Name = "牡丹", Category = "芍药科", ImageId =6}},
{ 12, new Plant { Name = "莲", Category = "莲科", ImageId =10 }},
{ 13, new Plant { Name = "柳", Category = "杨柳科", ImageId = 12 }}
};

C#6中新增了一种索引初始化器,可以使Dictionary的初始化更直观:

Dictionary<int, Plant> plantsDic = new Dictionary<int, Plant>
{
[11] = new Plant { Name = "牡丹", Category = "芍药科", ImageId =6},
[12] = new Plant { Name = "莲", Category = "莲科", ImageId =10 },
[13] = new Plant { Name = "柳", Category = "杨柳科", ImageId = 12 }
};

添加Add扩展方法使类支持集合初始化去

我们按如下方式实现一个集合类:

public class Enrollment : IEnumerable<Plant>
{
private List<Plant> allPlants = new List<Plant>(); public void Add(Plant s)
{
allPlants.Add(s);
} public IEnumerator<Plant> GetEnumerator()
{
return ((IEnumerable<Plant>)allPlants).GetEnumerator();
} IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<Plant>)allPlants).GetEnumerator();
}
}

由于这个类的实现有符合要求的Add方法,我们可以使用集合初始化器来给类的成员变量添加对象。

Plantation plantation = new Plantation()
{
new Plant {Name = "牡丹", Category = "芍药科", ImageId = 6},
new Plant {Name = "莲", Category = "莲科", ImageId = 10},
new Plant {Name = "柳", Category = "杨柳科", ImageId = 12}
};

但如果由于各种原因,我们的Add方法被命名为其它名称,如:

public void Plant(Plant s)
{
allPlants.Add(s);
}

则集合初始化器方式不再可用。为了让集合初始化其继续可用,可以添加下面这样的扩展方法:

public static class PlantExtensions
{
public static void Add(this Plantation e, Plant s) => e.Plant(s);
}

这样集合初始化器就又可以用了。

其它

  1. struct中可以声明无参构造函数。在之前版本的C#中,struct只能包含有参构造函数。
  2. 可以在catch/finally使用await语句了,一个典型的作用就是需要在catch中使用异步的logger方法这样的情况。
  3. C#6新的编译器会更智能的区分Task.Run(Action)Task.Run(Func<Task>())这种的重载,再遇到Task DoThings(){ }这种签名的重载时会智能的选择后者。

提示:
C#语言的编译与项目所依赖的.Net Framework版本无关。虽然VS在2015版本才内置支持C#6.0的编译器,但我们仍然可以使用VS2015编写基于.Net Framework 3.5甚至更早版本Framework的项目并享受C#6带来的如字符串插值等便利特性。
如果想脱离VS编译C#6的项目,需要使用随VS2015安装的Microsoft Build Tools 2015(也可以单独下载安装,安装位置在%SystemDrive%\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe),而不能使用位于%SystemDrive%\Windows\Microsoft.NET\Framework64\v4.0.30319中的Build Tool。

展望

C#7应该年底就会到来,对C#7比较期待的几点包括“外观”很简单的值类型元组,对象展开功能。有了这些C#7就能达到比Python还要流畅的代码编写感受了。与各位C#er共勉。

C#与C++的发展历程第四 - C#6的新时代的更多相关文章

  1. DQN(Deep Reiforcement Learning) 发展历程(四)

    目录 不基于模型的控制 选取动作的方法 在策略上的学习(on-policy) 不在策略上的学习(off-policy) 参考 DQN发展历程(一) DQN发展历程(二) DQN发展历程(三) DQN发 ...

  2. C#的发展历程 -- 系列介绍

    C#的发展历程第五 - C# 7开始进入快速迭代道路 C#与C++的发展历程第四 - C#6的新时代 C#与C++的发展历程第三 - C#5.0异步编程巅峰 C#与C++的发展历程第二 - C#4.0 ...

  3. 为什么说 Python 是数据科学的发动机(一)发展历程(附视频中字)

    为什么说 Python 是数据科学的发动机(一)发展历程(附视频中字) 在PyData Seattle 2017中,Jake Vanderplas介绍了Python的发展历程以及最新动态.在这里我们把 ...

  4. C#与C++的发展历程第三 - C#5.0异步编程巅峰

    系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...

  5. Linux实战教学笔记03:操作系统发展历程及系统版本选择

    标签(空格分隔): Linux实战教学笔记-陈思齐 第1章 Linux简介 1.1 什么是操作系统? 简单讲:操作系统就是一个人与计算机硬件的中介. 操作系统,英文名称Operating System ...

  6. unix及Linux发展历程

    unix及Linux发展历程 版权申明:本文资料为网上收集,由本人整理而成,转载请注明 一,unix诞生: Ken Thompson肯·汤普森 -------- unix之父 在1969年到1970间 ...

  7. 自学工业控制网络之路1.1-工业控制系统发展历程CCS DCS FCS

    返回 自学工业控制网络之路 自学工业控制网络之路1.1-工业控制系统发展历程CCS DCS FCS 工业控制系统是对诸如图像.语音信号等大数据量.高速率传输的要求,又催生了当前在商业领域风靡的以太网与 ...

  8. DQN(Deep Reiforcement Learning) 发展历程(五)

    目录 值函数的近似 DQN Nature DQN DDQN Prioritized Replay DQN Dueling DQN 参考 DQN发展历程(一) DQN发展历程(二) DQN发展历程(三) ...

  9. DQN(Deep Reiforcement Learning) 发展历程(三)

    目录 不基于模型(Model-free)的预测 蒙特卡罗方法 时序差分方法 多步的时序差分方法 参考 DQN发展历程(一) DQN发展历程(二) DQN发展历程(三) DQN发展历程(四) DQN发展 ...

随机推荐

  1. ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第二章:利用模型类创建视图、控制器和数据库

    在这一章中,我们将直接进入项目,并且为产品和分类添加一些基本的模型类.我们将在Entity Framework的代码优先模式下,利用这些模型类创建一个数据库.我们还将学习如何在代码中创建数据库上下文类 ...

  2. 高性能Javascript--脚本的无阻塞加载策略

    Javascript在浏览器中的性能,可以说是前端开发者所要面对的最重要的可用性问题. 在Yahoo的Yslow23条规则当中,其中一条是将JS放在底部 .原因是,事实上,大多数浏览器使用单进程处理U ...

  3. UITextView 输入字数限制

    本文介绍了UITextView对中英文还有iOS自带表情输入的字数限制,由于中文输入会有联想导致字数限制不准确所以苦恼好久,所以参考一些大神的博客终于搞定,欢迎大家参考和指正. 对于限制UITextV ...

  4. xamarin DependencyService源码阅读

    xamarin在面对PCL无法实现的各平台特有功能时使用了一种叫[DependencyService]的方式来实现.它使得xamarin能像原生平台一样做平台能做到的事情!主要分四个部分 接口:定义功 ...

  5. MVC Core 网站开发(Ninesky) 2、栏目

    栏目是网站的常用功能,按照惯例栏目分常规栏目,单页栏目,链接栏目三种类型,这次主要做添加栏目控制器和栏目模型两个内容,控制器这里会用到特性路由,模型放入业务逻辑层中(网站计划分数据访问.业务逻辑和We ...

  6. 这些.NET开源项目你知道吗?.NET平台开源文档与报表处理组件集合(三)

    在前2篇文章这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧 和这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,大伙热情高涨.再次拿出自己的私货,在.NET平台 ...

  7. iOS逆向工程之KeyChain与Snoop-it

    今天博客的主题是Keychain, 在本篇博客中会通过一个登陆的Demo将用户名密码存入到KeyChain中,并且查看一下KeyChain中存的是什么东西,把这些内容给导出来.当然本篇博客的重点不是如 ...

  8. 6_Win7下Chrome主页被流氓网站hao123.com劫持后的解决方法。

    今天安装了一个PDF阅读器,免费的,你懂的,结果自己安装的时候没有将默认的选项取消,就被hao123流氓网站劫持啦. 说实话某免费PDF阅读器还算好的,有一个可以供你选择的项.不想某些软件直接就默认选 ...

  9. ILJMALL project过程中遇到Fragment嵌套问题:IllegalArgumentException: Binary XML file line #23: Duplicate id

    出现场景:当点击"分类"再返回"首页"时,发生error退出   BUG描述:Caused by: java.lang.IllegalArgumentExcep ...

  10. github免输用户名/密码SSH登录的配置

    从github上获取的,自己整理了下,以备后用. Generating an SSH key mac windows SSH keys are a way to identify trusted co ...