*: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. SSH实战 · 唯唯乐购项目(下)

    后台模块 一:后台用户模块 引入后台管理页面 创建adminuser表: CREATE TABLE `adminuser` (   `uid` int(11) NOT NULL AUTO_INCREM ...

  2. TODO:Laravel增加验证码

    TODO:Laravel增加验证码1. 先聊聊验证码是什么,有什么作用?验证码(CAPTCHA)是"Completely Automated Public Turing test to te ...

  3. 05.LoT.UI 前后台通用框架分解系列之——漂亮的时间选择器

    LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...

  4. UWP开发之Mvvmlight实践八:为什么事件注销处理要写在OnNavigatingFrom中

    前一段开发UWP应用的时候因为系统返回按钮事件(SystemNavigationManager.GetForCurrentView().BackRequested)浪费了不少时间.现象就是在手机版的详 ...

  5. 马里奥AI实现方式探索 ——神经网络+增强学习

    [TOC] 马里奥AI实现方式探索 --神经网络+增强学习 儿时我们都曾有过一个经典游戏的体验,就是马里奥(顶蘑菇^v^),这次里约奥运会闭幕式,日本作为2020年东京奥运会的东道主,安倍最后也已经典 ...

  6. EC笔记:第4部分:22、所有成员都应该是private的

    EC笔记:第4部分:22.所有成员都应该是private的 更简单的访问 用户不用记得什么时候该带上括号,什么时候不用带上括号(因为很确定的就要带上括号) 访问限制 对于public的成员变量,我们可 ...

  7. 【JS基础】循环

    for 循环的语法: for (语句 1; 语句 2; 语句 3) { 被执行的代码块 } 语句 1 在循环(代码块)开始前执行 语句 2 定义运行循环(代码块)的条件 语句 3 在循环(代码块)已被 ...

  8. 手把手教你做个人 app

    我们都知道,开发一个app很大程度依赖服务端:服务端提供接口数据,然后我们展示:另外,开发一个app,还需要美工协助切图.没了接口,没了美工,app似乎只能做成单机版或工具类app,真的是这样的吗?先 ...

  9. Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define ...

    Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define ... 这个错误是因为有两个相 ...

  10. linux下安装Redis以及phpredis模块

    一:redis的安装 1. 首先上官网下载Redis 压缩包,地址:http://redis.io/download 下载 2. 通过远程管理工具,将压缩包拷贝到Linux服务器中,执行解压操作 3. ...