C#6于2015年7月发布,并且集成到 .NET Framework 4.6和Visual Studio2015中,它的大部分特性都是语法糖,以下列出部分主要特性
针对熟悉的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);
而在之前的版本,只能将默认值写在构造函数中,实际上结合下面的简易函数表达式写法,可将代码进一步简化
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
新的字符串插值用来代替过去的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 x = 10; return x; }))()}"
为了降低代码的冗余,我们希望在调用静态类的方法时,可以省去类的名称,例如:
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,就不要使用该特性
利用新的操作符"?."(称为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方法
这个改动主要是用于消除魔法字符串的,通过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,而如果是魔法字符串的话则不会跟着被修改,于是造成错误