C#新功能
一、C#历史演变
C# 1,Visual Studio .NET 2002:
- C# 初版。
C# 1.1,Visual Studio .NET 2003:
- /#line 杂注和 xml 文档注释。
C# 2,Visual Studio .NET 2005:
- 匿名方法、泛型、可空类型、迭代器/yield、static 类、委托的协变和逆变。
C# 3,Visual Studio .NET 2008:
- 对象和集合初始值设定项、lambda 表达式、扩展方法、匿名类型、自动属性、本地 var 类型推理和语言集成查询 (LINQ)。
C# 4,Visual Studio .NET 2010:
- Dynamic、命名实参和可选实参、泛型协变和逆变。
C# 5,Visual Studio .NET 2012:
- Async / await 和调用方信息特性
二、C# 6 的新功能 (Visual Studio .NET 2015)
1. 扩展自动属性语法
自动属性初始化表达式。
public class Example
{
// 6.0新增语法糖:属性初始化表达式
public string FirstName { get; set; } = "Monkey";
// 6.0之前的写法
private string _firstName = "Monkey";
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
}
只读自动属性(Read-only auto-properties)
只能在<font color=red >构造函数</font>或<font color=red >属性初始化表达式</font>中设置属性
public class Example
{
public string FirstName { get; } = "Monkey";
public string LastName { get; }
public Example(string lastName)
{
LastName = lastName;
}
}
2. 表达式主体(Expression-bodied function members)
通过表达式主体定义,可采用非常简洁的可读形式提供成员的实现
语法:
member => expression;
C#6支持:属性Get、方法
//方法的表达式主体定义
private static string SayHello() => "Hello World";
//属性Get的表达式主体定义
public string FullName => $"{FirstName} {LastName}";
3.导入静态类 (using static)
允许访问类型的静态成员,而无需限定使用类型名称进行访问:
r
using staticSystem.String
if (IsNullOrWhiteSpace(lastName))
throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
注意:扩展方法<font color=red >不能</font>使用此方式
using static System.Linq.Enumerable;
public bool MakesDeansList()
{
return Grades.All(g => g > 3.5) && Grades.Any();
// Code below generates CS0103:
// The name 'All' does not exist in the current context.
//return All(Grades, g => g > 3.5) && Grades.Any();
}
4.Null 条件运算符
成员访问 (?.) :
如果value==null,则返回null
public static string Truncate(string value, int length)
{
/
return value?.Substring(0, Math.Min(value.Length, length));
// C# 6.0 之前的写法
//string result = value;
//if (value != null)
//{
// result = value.Substring(0, Math.Min(value.Length, length));
//}
//return result;
}
索引 (?[) 操作:
List<Example> examples = null;
Example example = examples?[0];
// 上述相当于 Example? item = (examples != null) ? examples[0] : null
Console.WriteLine(example == null); // 输出 True
5.字符串插值 (String Interpolation)
public string GetFormattedGradePoint() =>
$"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average()}";
public string GetGradePointPercentage() =>
$"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";
public string GetGradePointPercentages() =>
$"Name: {LastName}, {FirstName}. G.P.A: {Grades.Any() ? Grades.Average() : double.NaN:F2}";
public string GetGradePointPercentages() =>
$"Name: {LastName}, {FirstName}. G.P.A: {(Grades.Any() ? Grades.Average() : double.NaN):F2}";
public string GetAllGrades() =>
$@"All Grades: {Grades.OrderByDescending(g => g)
.Select(s => s.ToString("F2")).Aggregate((partial, element) => $"{partial}, {element}")}";
6.异常过滤器(Exception Filters)
catch (ArgumentNullException e) when (e.ParamName == “…”)
{
}
如果括号表达式(when)的结果为 true 时,才执行对应 catch 块中的语句,否则继续搜索处理程序。
使用场景:http处理
public static async Task<string> MakeRequestWithNotModifiedSupport()
{
var client = new System.Net.Http.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 (System.Net.Http.HttpRequestException e) when (e.Message.Contains("304"))
{
return "Use the Cache";
}
}
7.nameof表达式
使用场景:获取错误参数名
if (IsNullOrWhiteSpace(lastName))
throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
使用场景:使用INotifyPropertyChanged接口监听属性变化。
public string LastName
{
get { return lastName; }
set
{
if (value != lastName)
{
lastName = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(LastName)));
}
}
}
private string lastName;
注意: NameOf只会返回Member的字符串,如果前面有对象或者命名空间,NameOf只会返回 . 的最后一部分, 另外NameOf有很多情况是不支持的,比如方法,关键字,对象的实例以及字符串和表达式
8.在 catch 和 finally 块使用关键字 await
public static async Task<string> MakeRequestAndLogFailures()
{
await logMethodEntrance();
var client = new System.Net.Http.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"))
{
await logError("Recovered from redirect", e);
return "Site Moved";
}
finally
{
await logMethodExit();
client.Dispose();
}
}
9.索引初始化器(Index Initializers)
private List<string> messages = new List<string>
{
"Page not Found",
"Page moved, but left a forwarding address.",
"The web server can't come out to play today."
};
private Dictionary<int, string> webErrors = new Dictionary<int, string>
{
[404] = "Page not Found",
[302] = "Page moved, but left a forwarding address.",
[500] = "The web server can't come out to play today."
};
三、C# 7 的新功能(Visual Studio .NET 2017)
1.out 变量
//之前版本
int numericResult;
if (int.TryParse(input, out numericResult))
WriteLine(numericResult);
else
WriteLine("Could not parse input");
C# 7在参数列表中声明变量
if (int.TryParse(input, out int result))
WriteLine(result);
else
WriteLine("Could not parse input");
2.元组(Tuples)
需要引用包:System.ValueTuple
元组是轻量级数据结构,包含多个字段来表示数据成员
var unnamed = ("one", "two");//字段名称为 Item1、Item2、Item3......
var named = (first: "one", second: "two");
(string Alpha, string Beta) namedLetters = ("a", "b");
var alphabetStart = (Alpha: "a", Beta: "b");
元组最适合作为private和internal方法的返回类型,如 分页结果集中的数据和总行数
private static (int Max, int Min) Range(IEnumerable<int> numbers)
{
int min = int.MaxValue;
int max = int.MinValue;
foreach(var n in numbers)
{
min = (n < min) ? n : min;
max = (n > max) ? n : max;
}
return (max, min);
}
参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/tuples
3.丢弃(Discards)
从 C# 7 开始,C# 支持放弃,这是一种在应用程序代码中人为取消使用的临时虚拟变量。 放弃相当于未赋值的变量;它们没有值。 因为只有一个放弃变量,并且甚至不为该变量分配存储空间,所以放弃可减少内存分配。
场景1:元组和对象析1
var (_, _, area) = city.GetCityInformation(cityName);
public class Example
{
public static void Main()
{
Person p = new Person("John", "Quincy", "Adams", "Boston", "MA");
// <Snippet1>
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
// </Snippet1>
}
}
场景2:使用 out 参数
if (DateTime.TryParse(dateString, out _))
Console.WriteLine($"'{dateString}': valid");
场景3:独立丢弃
using System;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
ExecuteAsyncMethods().Wait();
}
private static async Task ExecuteAsyncMethods()
{
Console.WriteLine("About to launch a task...");
_ = Task.Run(() => { var iterations = 0;
for (int ctr = 0; ctr < int.MaxValue; ctr++)
iterations++;
Console.WriteLine("Completed looping operation...");
throw new InvalidOperationException();
});
await Task.Delay(5000);
Console.WriteLine("Exiting after 5 second delay");
}
}
// The example displays output like the following:
// About to launch a task...
// Completed looping operation...
// Exiting after 5 second delay
场景4:使用 switch 和 is 的模式匹配
参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/discards
4.模式匹配(Pattern Matching)
is 类型模式表达式:优化原有语法
public static double ComputeAreaModernIs(object shape)
{
if (shape is Square s)
return s.Side * s.Side;
else if (shape is Circle c)
return c.Radius * c.Radius * Math.PI;
else if (shape is Rectangle r)
return r.Height * r.Length;
// elided
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
编写 if (!(shape is Square s)) 来反转逻辑,变量 s 会只在 false 分支中进行明确赋值。
使用模式匹配 switch 语句
public static double ComputeAreaModernSwitch(object shape)
{
switch (shape)
{
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Rectangle r:
return r.Height * r.Length;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
case 表达式中的 when 语句
public static double ComputeArea_Version5(object shape)
{
switch (shape)
{
case Square s when s.Side == 0:
case Circle c when c.Radius == 0:
case Triangle t when t.Base == 0 || t.Height == 0:
case Rectangle r when r.Length == 0 || r.Height == 0:
return 0;
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Triangle t:
return t.Base * t.Height * 2;
case Rectangle r:
return r.Length * r.Height;
//重点
case null:
throw new ArgumentNullException(paramName: nameof(shape), message: "Shape must not be null");
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/pattern-matching
5.ref 返回值和局部变量(ref locals and returns)
引用返回值允许方法将对象的引用(而不是值)返回给调用方。 然后,调用方可以选择将返回的对象视为按值返回或按引用返回。 如果按引用返回的值被调用方处理为引用(而非值),则该值即为 ref 局部变量。
示例
FindNumber 方法按引用返回第一个大于或等于作为参数传递的数字的数字。如果没有大于或等于该参数的数字,则方法返回索引 0 中的数字。
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
public ref int FindNumber(int target)
{
for (int ctr = 0; ctr < numbers.Length; ctr++) {
if (target == numbers[ctr]) {
return ref numbers[ctr];
}
else if (ctr == numbers.Length - 1) {
return ref numbers[ctr];
}
else if (target < numbers[ctr]) {
if (ctr > 0 && target > numbers[ctr - 1])
return ref numbers[ctr];
else if (ctr == 0)
return ref numbers[0];
}
}
return ref numbers[0];
}
public override string ToString()
{
string retval = "";
for (int ctr = 0; ctr < numbers.Length; ctr++) {
retval += $"{numbers[ctr]} ";
}
return retval.Trim();
}
}
如示例中的输出所示,此更改将反映在 NumberStore 实例的数组元素的值中。
using System;
public class Example
{
static void Main(string[] args)
{
var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
int number = 16;
ref var value = ref store.FindNumber(number);
value*=2;
Console.WriteLine($"New sequence: {store.ToString()}");
}
}
// The example displays the following output:
// Original sequence: 1 3 7 15 31 63 127 255 511 1023
// New sequence: 1 3 7 15 62 63 127 255 511 1023
参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/ref-returns
6.Local Functions
从 C# 7 开始,C# 支持本地函数。 本地函数是一种嵌套在另一成员中的类型的私有方法。 仅能从其包含成员中调用它们。 可以在以下位置中声明和调用本地函数:
- 方法(尤其是迭代器方法和异步方法)
- 构造函数
- 属性访问器
- 事件访问器
- 匿名方法
- Lambda 表达式
- 终结器
- 其他本地函数
using System;
using System.IO;
class Example
{
static void Main()
{
string contents = GetText(@"C:\temp", "example.txt");
Console.WriteLine("Contents of the file:\n" + contents);
}
private static string GetText(string path, string filename)
{
var sr = File.OpenText(AppendPathSeparator(path) + filename);
var text = sr.ReadToEnd();
return text;
// Declare a local function.
string AppendPathSeparator(string filepath)
{
if (! filepath.EndsWith(@"\"))
filepath += @"\";
return filepath;
}
}
}
参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/local-functions
7.更多的表达式主体成员(More expression-bodied members)
C#7支持:构造函数,终结器,属性 Set 语句,索引器
构造函数
public class Location
{
private string locationName;
public Location(string name) => locationName = name;
}
终结器
using System;
public class Destroyer
{
public override string ToString() => GetType().Name;
~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");
}
属性 Set 语句,
public class Location
{
public string Name
{
get => locationName;
set => locationName = value;
}
}
索引器
using System;
using System.Collections.Generic;
public class Sports
{
private string[] types = { "Baseball", "Basketball", "Football",
"Hockey", "Soccer", "Tennis",
"Volleyball" };
public string this[int i]
{
get => types[i];
set => types[i] = value;
}
}
8.throw 表达式(throw Expressions)
从 C# 7 开始,throw 可以用作表达式和语句。 这允许在以前不支持的上下文中引发异常。
条件运算符
private static void DisplayFirstNumber(string[] args)
{
string arg = args.Length >= 1 ? args[0] :
throw new ArgumentException("You must supply an argument");
if (Int64.TryParse(arg, out var number))
Console.WriteLine($"You entered {number:F0}");
else
Console.WriteLine($"{arg} is not a number.");
}
null 合并运算符
public string Name
{
get => name;
set => name = value ??
throw new ArgumentNullException("Name cannot be null", nameof(value));
}
expression-bodied lambda 或方法。
DateTime ToDateTime(IFormatProvider provider) =>
throw new InvalidCastException("Conversion to a DateTime is not supported.");
9.Generalized async return types
10.数字语法改进(Numeric literal syntax improvements)
二进制
public const int One = 0b0001;
public const int Two = 0b0010;
public const int Four = 0b0100;
public const int Eight = 0b1000;
数字分隔符
public const long BillionsAndBillions = 100_000_000_000;
public const double AvogadroConstant = 6.022_140_857_747_474e23;
public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;