C#6的主要特性
C#6于2015年7月发布,并且集成到 .NET Framework 4.6和Visual Studio2015中,它的大部分特性都是语法糖,以下列出部分主要特性
1.自动属性优化
针对熟悉的get、set对于不变性(即在创建之后就不可以改变它的值)实现的优化
过去的方式
如果我们想创建一个具有不变性的字段,通过如下方式完成:
class Program { static void Main(string[] args) { var a = new ImmutableClass(100); // 只读的值不能修改 // a.a = 5; Console.ReadKey(); } } class ImmutableClass { private readonly int _a; public int a { get { return _a; } private set; } public ImmutableClass(int a) { _a = a; } }
如果需要某个属性是只读的,则需要指定setter为私有的。此时我们在外部将无法改变a的值(只能在创建时指定),但其实这是一种有问题的不变性
有问题的不变性
我们可以通过复写一些方法在内部改变成员变量的值
class Program { static void Main(string[] args) { var a = new ImmutableClass(100); a.ToString(); Console.WriteLine(a.a); // 99 Console.ReadKey(); } } class ImmutableClass { private readonly int _a; public int a { get { return _a; } private set; } public ImmutableClass(int a) { _a = a; } public override string ToString() { a = 99; return string.Empty; } }
我们看到,a的初始值为100,但在类的内部,a仍然可以修改,如果我们需要实现一个仅可以在构造函数中set值的属性,需要这样写
class ImmutableClass { private readonly int _a; public int a { get{ return _a; } } public ImmutableClass(int a) { _a = a; } public override string ToString() { // a = 99; 此行代码将不会通过编译 return string.Empty; } }
我们仅能够在构造函数中改变属性的值,使得属性的值更加安全
更好的不变性
C#6中,如果只想在构造函数中修改属性的,可以这样写:
public int a { get; }
此时,这个字段就真正地具有了不变性,当你初始化了这个字段的值之后,就再也无法在构造函数以外的地方更改它的值,例如:
public DateTime DateOfBrith { get; } = new DateTime(2000, 1, 1);
而在之前的版本,只能将默认值写在构造函数中,实际上结合下面的简易函数表达式写法,可将代码进一步简化
2.简易函数表达式写法
应用于方法
public void SayHi() { Console.WriteLine("Hi"); }
改成了如下方式
public void SayHi() => Console.WriteLine("Hi");
应用与属性:赋予不变性
将表达式应用于属性会有两个效果:
- 赋予属性不变性
- 每次表达式都会运行
对于以下的代码:
public TimeSpan Age { get { return DateTime.UtcNow - DateOfBirth; } }
可以改进为:
public TimeSpan Age => DateTime.UtcNow - DateOfBirth;
此时的Age相当于后面的表达式的运算结果,对属性使用表达式相当于给力它一个默认值,要注意的一点是:通过表达式创建的属性具有不变性
public Program { public DateTime DataOfBirth { get; set; } public TimeSpan Age => DateTime.UtcNow - DateOfBirth;static void Main(string[] args) { var a = new Program(); // 不能通过编译,属性具有不变性 // a.Age = new TimeSpan......; } }
另外,当每次访问到对象时,表达式都会运行,如下:
private int count; public int Count => count++;
其原型是:
private int count; public int Count { get { return count++; } }
所以每次访问到这个对象时,都会使用上一次更新过的count值,使用方法如下:
public class Person { private int count; public int Count => count++;} class Program { static void Main(string[] args) { var p = new Person(); Console.WriteLine(p.Count); Console.WriteLine(p.Count); Console.WriteLine(p.Count); Console.WriteLine(p.Count); Console.ReadKey(); } }
输出0,1,2,3
最后需要注意一点:这个特性不能用于构造函数、析构函数等,例如不能有如下写法:
public Person(string name) => this.Name = name
3.字符串插值
新的字符串插值用来代替过去的string.Format,使得代码更加易读,代码如下:
var result = String.Fromat("Hello {0}", Name); object SubjectHomeTown = null; var result2 = String.Format("You {0} must be the pride and joy of {1}", SubjectName, SubjectHomeTown);
如果字符串过长,那么查找每个字符串的{x}对应哪个变量将是一个痛苦的事情,而新的特性中,变量就在字符串之中,需要字符串前面添加"$"符号,提醒编译器使用C#6的字符串插值:
var result = $"Hello {Name}"; var result2 = $"You {SubjectName} must be the pride and joy of {SubjectHomeTown}";
变量还可以是表达式,不过这样的效果并不好:
var result = $"Hello {((Func<int>)(() => { int x = 10; return x; }))()}"
4.使用static using调用静态类方法
为了降低代码的冗余,我们希望在调用静态类的方法时,可以省去类的名称,例如:
internal class StaticUsing { private void BrokenFlow() { Console.WriteLine(Math.Cos(5), * Math.Tan(20) + Math.PI); } }
我们看到这里出现了3次Math。可以用过C#6的static using,可以进一步提高代码的可读性
using System; using static System.Math; internal class StaticUsing { private void BrokenFlow() { Console.WriteLine(Cos(5), * Tan(20) + PI); } }
当静态的加载了System.Math库之后,代码中就可以省略Math前缀了,不过对于已经习惯了使用静态类调用方法的开发者来说,这个特性可能未必是一个好的特性,有时它反而会使得代码的可读性下降,例如我们的Hello World程序代码变成如下:
using System; using static System.Console; class Program { static void Main(string[] args) { WriteLine("Hello World"); } }
总之如果你在使用时如果感觉很别扭,例如此处没有通过Console来调用WriteLine,就不要使用该特性
5.判定null的简写操作符?.
利用新的操作符"?."(称为nullet)可以简化代码。我们都写过这样的代码,它会判断一连串的对象是否为null:
if (track != null && track.Band != null && track.Band.FrontMan ! = null) { Console.WriteLine("HI! " + track.Band.FrontMan.Name); }
利用nullet操作符,可以简化为:
Console.WriteLine("HI! " + track?.Band?.FrontMan?.Name);
其意义为,对于A?.B,判断如果A为null,就返回,并且不调用方法,否则,就返回A.B,因此上面的代码当track或track.Band为null时,不会调用WriteLine方法
6.nameof运算符
这个改动主要是用于消除魔法字符串的,通过nameof(something)获取something的名称,通常这些魔法字符串都是类、对象或者方法的名称,它们可能会在反射时用到,例如:
class MyClass { public string BeforeName { get; set; } } class Program { static void Main(string[] args) { var c = new MyClass(); c.BeforeName = "123"; var t = typeof(MyClass); // 过去的做法 var n1 = t.GetProperty("BeforeName"); Console.WriteLine(n1.GetValue(c)); // 加入nameof var n2 = t.GetProperty(nameof(c.BeforeName)); Console.WriteLine(n2.GetValue(c)); ReadKey(); } }
上面的代码中,在使用反射时,可以使用字符串或者nameof。如果将来将字段名BeforeName修改为AfterName,那么通过VS等IDE的重构功能,nameof中的BeforeName会自动修改为AfterName,而如果是魔法字符串的话则不会跟着被修改,于是造成错误
- C#语言主要特性总结
- C#语言主要特性
- C#2.0中主要的语法新特性
- MATLAB帮助文档_强调产品的主要新特性和增强功能_Highlights of Products with Maior New Features and Enhancements
- C#228课的主要内容
- C# 6.0 新特性 (三)
- C#6.0的新特性之内插字符串
- C# 2.0&3.0新特性总结
- C# 在列表中按特性查找
- 一些经验记录,主要是C# ToString()和 DateTime 之类的
- LINQ体验(3)——C# 3.0新语言特性和改进(下篇)
- 步步为营VS 2008 + .NET 3.5(3) - C# 3.0新特性之Automatic Properties(自动属性)
- 【转】关于C# 中的Attribute 特性
- C# 7.0 新特性1之基于Tuple的“多”返回值方法
- TCP的主要特性
- C#2.0的新特性
- 闲谈Kubernetes 的主要特性和经验分享
- C# 3.0 之新特性总结
- LINQ体验(2)——C# 3.0新语言特性和改进(上篇)
- C#与.NET程序员面试宝典 2.1.3 面试题3:说明ASP.NET的Application特性