C# 7.0 中的新增功能

来源:Mark Michaelis

链接:msdn.microsoft.com/magazine/mt790184

 

解构函数

从 C# 1.0 开始,就能调用函数,就是将参数组合起来并封装到一个类中的构造函数。但是,从来没有一种简便的方式可将对象解构回其各个组成部分。例如,假设有一个 PathInfo 类,它采用文件名的每个元素(目录名、文件名、扩展名),并将它们组合成一个对象,然后支持操作对象的不同元素。现在,假设你需要将该对象提取(解构)回其各个组成部分。

在 C# 7.0 中,通过解构函数完成这项任务将变得轻而易举,解构函数可返回对象的具体确定组件。注意,不要将解构函数 (deconstructor) 与析构函数 (destructor)(确定性对象解除分配和清除)或终结器 (itl.tc/CSharpFinalizers) 混淆。

我们来看看图 1 中的 PathInfo 类。

图 1 具有解构函数的 PathInfo 类及相关测试

 

public class PathInfo

{

public string DirectoryName { get; }

public string FileName { get; }

public string Extension { get; }

public string Path

{

get

{

return System.IO.Path.Combine(

DirectoryName, FileName, Extension);

}

}

public PathInfo(string path)

{

DirectoryName = System.IO.Path.GetDirectoryName(path);

FileName = System.IO.Path.GetFileNameWithoutExtension(path);

Extension = System.IO.Path.GetExtension(path);

}

public void Deconstruct(

out string directoryName, out string fileName, out string extension)

{

directoryName = DirectoryName;

fileName = FileName;

extension = Extension;

}

// ...

}

显然,可以和在 C# 1.0 一样调用 Deconstruct 方法。但是,C# 7.0 提供了可以显著简化调用的语法糖。如果存在解构函数的声明,则可以使用新的 C# 7.0“类似元组”的语法调用它(参见图 2)。

图 2 解构函数调用和赋值

 

PathInfo pathInfo = new PathInfo(@"\\test\unc\path\to\something.ext");

{

// Example 1: Deconstructing declaration and assignment.

(string directoryName, string fileName, string extension) = pathInfo;

VerifyExpectedValue(directoryName, fileName, extension);

}

{

string directoryName, fileName, extension = null;

// Example 2: Deconstructing assignment.

(directoryName, fileName, extension) = pathInfo;

VerifyExpectedValue(directoryName, fileName, extension);

}

{

// Example 3: Deconstructing declaration and assignment with var.

var (directoryName, fileName, extension) = pathInfo;

VerifyExpectedValue(directoryName, fileName, extension);

}

请注意,C# 第一次如何允许同时向不同值的多个变量赋值。这与将所有变量都初始化为同一值 (null) 的空赋值声明不同:

string directoryName, filename, extension = null;

通过新的类似元组的语法,赋予每个变量一个不同的值,该值与其名称不对应,但与它出现在声明和解构语句中的顺序相对应。

正如你所期望的,out 参数的类型必须与被分配的变量类型相匹配,并且允许使用 var,因为此类型可以从 Deconstruct 参数类型中推断出来。但是,请注意,虽然可以在圆括号外面放置一个 var(如图 2 中的示例 3 所示),但此时即使所有变量的类型均相同,也不能拉出字符串。

请注意,此时 C# 7.0 类似元组的语法要求圆括号内至少出现两个变量。例如,即使存在类似如下的解构函数,也不允许使用 (FileInfo path) = pathInfo;:

public void Deconstruct(out FileInfo file)

换句话说,不能对仅有一个 out 参数的 Deconstruct 方法使用 C# 7.0 解构函数。

使用元组

正如我所说过的,前面的每个示例都利用了 C# 7.0 类似元组的语法。此类语法的特点就是用圆括号括住分配的多个变量(或属性)。我之所以使用术语“类似元组的”,是因为所有这些解构函数示例实际上在内部均未使用任何元组类型。(实际上,由于已分配的对象是表示封装的组成部分的实例,因此,不允许通过解构函数语法分配元组,也可以说这样做不太必要。)

借助 C# 7.0,现在有了一种特别简化的语法,可以使用元组,如图 3 所示。只要允许使用类型说明符,就可以使用这种语法,其中包括声明、强制转换运算符和类型参数。

图 3 声明、实例化并使用 C# 7.0 元组语法

 

[TestMethod]

public void Constructor_CreateTuple()

{

(string DirectoryName, string FileName, string Extension) pathData =

(DirectoryName: @"\\test\unc\path\to",

FileName: "something",

Extension: ".ext");

Assert.AreEqual<string>(

@"\\test\unc\path\to", pathData.DirectoryName);

Assert.AreEqual<string>(

"something", pathData.FileName);

Assert.AreEqual<string>(

".ext", pathData.Extension);

Assert.AreEqual<(string DirectoryName, string FileName, string Extension)>(

(DirectoryName: @"\\test\unc\path\to",

FileName: "something", Extension: ".ext"),

(pathData));

Assert.AreEqual<(string DirectoryName, string FileName, string Extension)>(

(@"\\test\unc\path\to", "something", ".ext"),

(pathData));

Assert.AreEqual<(string, string, string)>(

(@"\\test\unc\path\to", "something", ".ext"), (pathData));

Assert.AreEqual<Type>(

typeof(ValueTuple<string, string, string>), pathData.GetType());

}

[TestMethod]

public void ValueTuple_GivenNamedTuple_ItemXHasSameValuesAsNames()

{

var normalizedPath =

(DirectoryName: @"\\test\unc\path\to", FileName: "something",

Extension: ".ext");

Assert.AreEqual<string>(normalizedPath.Item1, normalizedPath.DirectoryName);

Assert.AreEqual<string>(normalizedPath.Item2, normalizedPath.FileName);

Assert.AreEqual<string>(normalizedPath.Item3, normalizedPath.Extension);

}

static public (string DirectoryName, string FileName, string Extension)

SplitPath(string path)

{

// See http://bit.ly/2dmJIMm Normalize method for full implementation.

return (

System.IO.Path.GetDirectoryName(path),

System.IO.Path.GetFileNameWithoutExtension(path),

System.IO.Path.GetExtension(path)

);

}

如果你不太熟悉元组,可以在轻量级语法中将多个类型组合成一个包含类型,然后在对其进行实例化的方法外面使用。之所以说是轻量级,是因为和定义类/结构不同,元组可通过内联和动态方式“声明”。

但是,与也支持内联声明和实例化的动态类型不同,元组可以从其包含成员的外部访问,它们实际上可以包含在 API 中。虽然外部 API 支持,但元组没有兼容版本的扩展(除非类型参数本身正好支持推导),因此,在公共 API 中应谨慎使用。因此,更好的办法是对公共 API 中的返回内容使用标准类。

在 C# 7.0 之前,该框架已有元组类 System.Tuple<…>(在 Microsoft .NET Framework 4 中引入)。但 C# 7.0 与之前的解决方案不同,因为它将语义意图嵌入到声明中并引入一个元组值类型:  System.ValueTuple<…>。

我们现在来看看语义意图。请注意,在图 3 中,C# 7.0 元组语法可让你为元组包含的每个 ItemX 元素声明别名。例如,图 3 中的 pathData 元组实例已定义强类型 DirectoryName: string、FileName: string 和 Extension: string 属性,因此,可以调用(例如)pathData.DirectoryName。这是一项重大改进,因为在 C# 7.0 之前,唯一可用的名称是 ItemX 名称,其中 X 将针对每个元素增加。

现在,虽然 C# 7.0 元组的元素属于强类型,但这些名称本身在类型定义中并未区分。因此,可以分配两个使用不同别名的元组,你将得到一条警告,通知你将忽略右边的名称:

// Warning: The tuple element name ‘AltDirectoryName1‘ is ignored

// because a different name is specified by the target type...

(string DirectoryName, string FileName, string Extension) pathData =

(AltDirectoryName1: @"\\test\unc\path\to",

FileName: "something", Extension: ".ext");

同样,可以将元组分配到尚未定义部分别名元素名称的其他元组:

// Warning: The tuple element name ‘directoryName‘, ‘FileNAme‘ and ‘Extension‘

// are ignored because a different name is specified by the target type...

(string, string, string) pathData =

(DirectoryName: @"\\test\unc\path\to", FileName: "something", Extension: ".ext");

必须确定,每个元素的类型和顺序都定义类型兼容性。仅忽略元素名称。然而,即使在名称不同时被忽略,它们仍然在 IDE 中提供 IntelliSense。

请注意,无论是否定义元素名称的别名,所有元组均有 ItemX 名称,其中 X 对应于元素的数量。ItemX 名称很重要,因为它们是元组从 C# 6.0 开始起可用,即使没有别名元素的名称也是如此。

需要注意的另一点就是,基础 C# 7.0 元组类型是 System.ValueTuple。如果正针对其进行编译的框架中未提供此类型,可以通过 NuGet 包访问它。

有关元组内部元素的详细信息,请参阅 intellitect.com/csharp7tupleiinternals。

具有 Is 表达式的模式匹配

有时会存在基类(例如 Storage),以及一系列的派生类、DVD、UsbKey、HardDrive、FloppyDrive 等。要对每个类实施 Eject 方法,请使用以下多个选项:

As 运算符

 

  • 使用 As 运算符转换并赋值
  • 检查结果是否为 null
  • 执行 eject 操作

Is 运算符

 

  • 使用 Is 运算符检查类型
  • 转换类型并为其赋值
  • 执行 eject 操作

Cast

 

  • 显式转换并赋值
  • 捕获可能的异常
  • 执行操作
  • 看起来不怎么样啊!

还有第四种、效果更好的方法,即使用你通过虚拟函数分派的多形性。但是,仅在具有 Storage 类的源代码并且可以添加 Eject 方法时,才可以使用这种方法。我假设的选项不适用于这个讨论,因此需要模式匹配。

上述这些方法存在的问题都是语法相当冗长,总是要求为需要转换的每个类提供多个语句。C# 7.0 提供模式匹配,用作一种将测试和赋值合并为单个操作的方法。因此,图 4 中的代码简化为如图 5 中所示的代码。

图 4 无模式匹配的类型转换

 

// Eject without pattern matching.

public void Eject(Storage storage)

{

if (storage == null)

{

throw new ArgumentNullException();

}

if (storage is UsbKey)

{

UsbKey usbKey = (UsbKey)storage;

if (usbKey.IsPluggedIn)

{

usbKey.Unload();

Console.WriteLine("USB Drive Unloaded.");

}

else throw new NotImplementedException();    }

else if(storage is DVD)

// ...

else throw new NotImplementedException();

}

图 5 有模式匹配的类型转换

// Eject with pattern matching.

public void Eject(Storage storage)

{

if (storage is null)

{

throw new ArgumentNullException();

}

if (((storage is UsbKey usbDrive) && usbDrive.IsPluggedIn)

{

usbKey.Unload();

Console.WriteLine("USB Drive Unloaded.");

}

else if (storage is DVD dvd && dvd.IsInserted)

// ...

else throw new NotImplementedException();  // Default

}

这两种转换方式的区别并不重要,但如果要经常执行(例如,针对每个派生类型),则前一种语法存在一种繁琐的 C# 特性。C# 7.0 的改进之处是将类型测试、声明和赋值组合为一个操作,呈现早期的语法,但不推荐使用。在前一种语法中,检查类型而不分配标识符会导致失败而恢复“默认设置”,否则会很麻烦。相比之下,除了类型检查,分配还考虑到其他条件。

请注意,图 5 中的代码开始模式匹配 is 运算符,也支持 null 比较运算符:

if (storage is null) { ... }

使用 Switch 语句的模式匹配

虽然支持使用 is 运算符的模式匹配实现了改进,但 switch 语句的模式匹配支持无疑更重要,至少在有多个可转换的兼容类型时如此。这是因为 C# 7.0 包括 case 语句和模式匹配,此外,如果满足 case 语句中的类型模式,就可以在 case 语句中提供、分配和访问标识符。图 6 提供了一个示例。

图 6 Switch 语句中的模式匹配

public void Eject(Storage storage)

{

switch(storage)

{

case UsbKey usbKey when usbKey.IsPluggedIn:

usbKey.Unload();

Console.WriteLine("USB Drive Unloaded.");

break;

case DVD dvd when dvd.IsInserted:

dvd.Eject();

break;

case HardDrive hardDrive:

throw new InvalidOperationException();

case null:

default:

throw new ArgumentNullException();

}

}

在该示例中,请注意如何在 case 语句中自动声明和分配如 usbKey 和 dvd 的局部变量。正如你所期望的,范围仅限于 case 语句中。

但也许与变量声明和赋值一样重要的是附加条件,可以用一个 when 子句附加到 case 语句。结果是 case 语句完全可以筛选无效的方案,无需在 case 语句内部使用额外的筛选器。这带来额外的好处是:如果事实上没有完全满足前一个 case 语句,也允许计算下一个 case 语句。这也意味着 case 语句不再仅限于常量,此外,switch 表达式可以是任何类型,不再仅限于 bool、char、string、integral 和 enum。

新的 C# 7.0 模式匹配 switch 语句功能引入的另一个重要特征就是,case 语句顺序很重要并在编译时验证。(这与该语言的早期版本形成对比,早期版本中没有模式匹配,case 语句顺序也不重要。) 例如,如果我在派生自 Storage 的模式匹配 case 语句之前引入了 Storage 的 case 语句(UsbKey、DVD 和 HardDrive),则 case Storage 会隐藏所有其他的类型模式匹配(派生自 Storage)。如果 case 语句来自隐藏计算结果中的其他派生类型 case 语句的基类,将导致隐藏的 case 语句中出现编译错误。这样,case 语句顺序要求就类似于 catch 语句。

读者将会记得 null 值中的 is 运算符返回 false。因此,对于值 null 的 switch 表达式,类型模式匹配 case 语句不匹配。为此,null case 语句的顺序无关紧要;此行为在模式匹配之前与 switch 语句匹配。

此外,为了支持与 C# 7.0 之前的 switch 语句的兼容性,默认总是最后评估 case,而不考虑它出现在 case 语句顺序中的位置。(也就是说,由于 case 总是在最后评估,可读性通常也会将它放在最后。) 此外,goto case 语句仍仅适用于常量 case 标签,不适用于模式匹配。

本地函数

虽然已经可以声明委托并为其分配一个表达式,但是 C# 7.0 通过允许在另一个成员内部完全声明本地函数,做出了进一步改进。请考虑图 7 中的 IsPalindrome 函数。

图 7 本地函数示例

 

bool IsPalindrome(string text)

{

if (string.IsNullOrWhiteSpace(text)) return false;

bool LocalIsPalindrome(string target)

{

target = target.Trim();  // Start by removing any surrounding whitespace.

if (target.Length <= 1) return true;

else

{

return char.ToLower(target[0]) ==

char.ToLower(target[target.Length - 1]) &&

LocalIsPalindrome(

target.Substring(1, target.Length - 2));

}

}

return LocalIsPalindrome(text);

}

在该实现中,我先检查传递到 IsPalindrome 的参数不是 null 或仅为空格。(我已使用模式匹配与 “text is null” 进行 null 检查。) 接下来,我声明函数 LocalIsPalindrome,其中,我以递归方式将第一个和最后一个字符进行比较。这种方法的好处是,我不在可能会错误调用的类范围内声明 LocalIsPalindrome,进而绕过 IsNullOrWhiteSpace 检查。换句话说,本地函数提供其他的范围限制,但仅在周围函数内部。

图 7 中的参数验证方案是一种通用的本地函数用例。我经常遇到的另一个方案发生在单元测试内,例如在测试 IsPalindrome 函数时(参见图 8)。

图 8 单元测试通常使用本地函数

 

[TestMethod]

public void IsPalindrome_GivenPalindrome_ReturnsTrue()

{

void AssertIsPalindrome(string text)

{

Assert.IsTrue(IsPalindrome(text),

$"‘{text}‘ was not a Palindrome.");

}

AssertIsPalindrome("7");

AssertIsPalindrome("4X4");

AssertIsPalindrome("   tnt");

AssertIsPalindrome("Was it a car or a cat I saw");

AssertIsPalindrome("Never odd or even");

}

返回 IEnumerable<T> 的 Iterator 函数以及 yield 返回元素是另一种通用的本地函数用例。

作为对该主题的总结,以下列出了大家需要注意的有关本地函数的几个要点:

本地函数不允许使用可访问性修饰符(public、private、protected)。

本地函数不支持重载。即使签名未重叠,也不能在名称相同的同一种方法中使用两个本地函数。

编译器将针对永不调用的本地函数发出警告。

本地函数可以访问封闭范围内的所有变量,包括局部变量。此行为与本地定义的 lambda 表达式相同,除了本地函数不分配表示结束的对象外,其他方面都与本地定义的 lambda 表达式相同。

本地函数存在于整个方法的范围内,而不考虑是在声明之前还是之后调用它们。

通过引用返回

从 C# 1.0 开始,可以通过引用 (ref) 将参数传递给函数。结果就是对参数本身的任何改变都将传回给调用方。请考虑以下 Swap 功能:

static void Swap(ref string x, ref string y)

在这种情况下,被调用方法可以用新值更新原始调用方的变量,从而交换第一和第二参数中存储的内容。

从 C# 7.0 开始,除了 ref 参数,还可以通过函数返回传回一个引用。例如,考虑返回图像中与红眼相关联的第一像素的函数,如图 9 所示。

图 9 Ref 返回和 Ref 局部声明

 

public ref byte FindFirstRedEyePixel(byte[] image)

{

//// Do fancy image detection perhaps with machine learning.

for (int counter = 0; counter < image.Length; counter++)

{

if(image[counter] == (byte)ConsoleColor.Red)

{

return ref image[counter];

}

}

throw new InvalidOperationException("No pixels are red.");

}

[TestMethod]

public void FindFirstRedEyePixel_GivenRedPixels_ReturnFirst()

{

byte[] image;

// Load image.

// ...

// Obtain a reference to the first red pixel.

ref byte redPixel = ref FindFirstRedEyePixel(image);

// Update it to be Black.

redPixel = (byte)ConsoleColor.Black;

Assert.AreEqual<byte>((byte)ConsoleColor.Black, image[redItems[0]]);

}

通过返回图像引用,调用方然后能够将像素更新为不同的颜色。通过数组检查更新时发现,该值现在为 black。使用 by reference 参数的替代方法如下所示,有人可能会说这种方法不太明显、可读性较低:

public bool FindFirstRedEyePixel(ref byte pixel);

通过引用返回有两个重要的限制,并且这两个限制都由对象生命周期造成。对象引用不应被视为垃圾收集,因为对象仍然被引用,当它们不再有任何引用时,不应消耗内存。首先,只能返回以下内容的引用:字段、其他引用返回属性或函数,或作为参数传递到引用返回函数的对象。例如,FindFirst-RedEyePixel 返回对图像数组中项目的引用,它是函数的参数。同样,如果图像存储为类中的字段,则可以通过引用返回该字段:

byte[] _Image;

public ref byte[] Image { get {  return ref _Image; } }

其次,ref 局部变量初始化为内存中的某个存储位置,且不能修改为指向不同的位置。(不能具有指向一个引用的指针和修改引用 - 对于那些有 C++ 背景的人,是指向指针的指针。)

以下是需要了解的几个按引用返回特征:

如果你要返回一个引用,则显然必须返回。因此,这意味着在图 9 的示例中,即使没有红眼像素存在,仍需要返回 ref 字节。唯一的解决方法是引发一个异常。相比之下,by reference 参数方法可让你保持参数不变,并返回一个指示成功的布尔值。在许多情况下,这种方法可能更可取。

声明一个引用局部变量时,需要初始化。这涉及到为它分配从函数或引用返回到变量的 ref:

ref string text;  // Error

虽然可以在 C# 7.0 中声明引用局部变量,但不允许声明 ref 类型的字段:

class Thing { ref string _Text;  /* Error */ }

不能为自动实施的属性声明 by reference 类型:

class Thing { ref string Text { get;set; }  /* Error */ }

允许使用返回引用的属性:

class Thing { string _Text = "Inigo Montoya";

ref string Text { get { return ref _Text; } } }

不能使用值(如 null 或常量)初始化引用局部变量。它必须通过返回成员或局部变量/字段的 by reference 分配:

ref int number = null; ref int number = 42;  // ERROR

输出变量

从 C# 的第一个版本开始,调用包含输出参数的方法时,始终要求在调用方法之前预先声明输出参数标识符。但 C# 7.0 删除了这个特性,并且允许以内联方式声明输出参数以及方法调用。图 10 显示了一个例子。

图 10 输出参数的内联声明

public long DivideWithRemainder(

long numerator, long denominator, out long remainder)

{

remainder = numerator % denominator;

return (numerator / denominator);

}

[TestMethod]

public void DivideTest()

{

Assert.AreEqual<long>(21,

DivideWithRemainder(42, 2, out long remainder));

Assert.AreEqual<long>(0, remainder);

}

请注意,在 DivideTest 方法中,从测试中对 DivideWithRemainder 的调用如何在 out 修饰符之后包含一个类型说明符。此外,了解剩余部分如何自动继续包含在方法的范围内,如第二个 Assert.AreEqual 调用证明。很好!

文本改进

与以前的版本不同,C# 7.0 包含数字二进制文本格式,如下例所示:

long LargestSquareNumberUsingAllDigits =

0b0010_0100_1000_1111_0110_1101_1100_0010_0100;  // 9,814,072,356

long MaxInt64 { get; } =

9_223_372_036_854_775_807;  // Equivalent to long.MaxValue

还要注意对下划线 “_” 用作数字分隔符的支持。它只是用来提高可读性,可以放在数字位数(二进制、十进制或十六进制数字)之间的任何位置。

通用的异步返回类型

有时在实施异步方法时,能够同步返回结果,缩短一个长时间运行的操作,因为结果几乎是瞬时的,甚至是已知的。例如,考虑一个异步方法,用于确定目录 (bit.ly/2dExeDG) 中文件的总大小。事实上,如果该目录中没有文件,则该方法可以立即返回,而不执行长时间运行的操作。直到 C# 7.0,异步语法的要求规定此类方法的返回结果应当是 Task<long>,因此,即使不需要这样的 Task 实例,也要实例化 Task。(要实现这一点,通用模式是从 Task.FromResult<T> 返回结果。)

在 C# 7.0 中,编译器不再限制异步方法返回到 void、Task 或 Task<T>。现在可以定义自定义类型,例如 .NET Core Framework 提供的 System.Threading.Tasks.ValueTask<T> struct,它们与异步方法返回值兼容。有关更多信息,请参阅 itl.tc/GeneralizedAsyncReturnTypes。

更多的 Expression-Bodied 成员

C# 6.0 引入了函数和属性的 expression-bodied 成员,从而简化了实现琐碎的方法和属性的语法。在 C# 7.0 中,将 expression-bodied 实现添加到了构造函数、访问器(get 和 set 属性实现),甚至终结器中(请参见图 11)。

图 11 在访问器和构造函数中使用 Expression-Bodied 成员

class TemporaryFile  // Full IDisposible implementation

// left off for elucidation.

{

public TemporaryFile(string fileName) =>

File = new FileInfo(fileName);

~TemporaryFile() => Dispose();

Fileinfo _File;

public FileInfo File

{

get => _File;

private set => _File = value;

}

void Dispose() => File?.Delete();

}

我希望使用 expression-bodied 成员,这对于终结器特别常见,因为最常见的实现是调用 Dispose 方法,如上图所示。

我很高兴地在此说明,对 expression-bodied 成员的额外支持是由 C# 社区实施的,而不是 Microsoft C# 团队。而且还是开源,耶!

警告: 此功能在 Visual Studio 15 Preview 5 中尚未实现。

 

Throw 表达式:

图 11 中的临时类可以得到增强,在 expression-bodied 成员内包括参数验证;因此,我可以将构造函数更新为:

public TemporaryFile(string fileName) =>

File = new FileInfo(filename ?? throw new ArgumentNullException());

如果没有 throw 表达式,C# 对 expression-bodied 成员的支持就不能进行任何参数验证。但是,通过 C# 7.0 支持 throw 作为一个表达式,而不仅仅是一个语句,因此,可以在更大的包含表达式中报告错误内联。

警告: 此功能在 Visual Studio 15 Preview 5 中尚未实现。

总结

我承认,当我开始写这篇文章,以为它会短得多。然而,由于我花了更多的时间编程和测试这些功能,因此,我发现有更多的方式实现 C# 7.0,而不仅仅是通过阅读功能标题和遵照语言开发。在许多情况下,声明变量、二进制文本、throw表达式等等,没有太多地涉及理解和使用功能。但有几种情况(例如,按引用返回、解构函数和元组)需要比最初所预期的更多地了解功能。在后一种情况下,不仅要了解语法,还要知道功能何时是相关的。

C# 7.0 继续在快速减少的特性列表(预先声明的输出标识符和缺少 throw 表达式)中削弱,而与此同时进行扩展,以包括对之前在语言级别看不到的功能的支持(元组和模式匹配)。

希望这个介绍可以帮助你快速进入 C# 7.0 编程领域。有关本文内容之后的 C# 7.0 开发的更多信息,请查看我在 intellitect.com/csharp7 上的博客,以及我的《Essential C# 7.0》书的更新(预计将在 Visual Studio 15 投入生产后不久面世)。

时间: 2024-12-15 07:10:55

C# 7.0 中的新增功能的相关文章

C# 2.0 中的新增功能03 匿名方法

连载目录    [已更新最新开发文章,点击查看详细] 在 2.0 之前的 C# 版本中,声明委托的唯一方式是使用命名方法. C# 2.0 引入匿名方法,在 C# 3.0 及更高版本中,Lambda 表达式取代匿名方法作为编写内联代码的首选方式. 但是,本主题中有关匿名方法的信息也适用于 Lambda 表达式. 在有一种情况下,匿名方法提供 Lambda 表达式中没有的功能. 使用匿名方法可省略参数列表. 这意味着匿名方法可转换为具有多种签名的委托. Lambda 表达式无法实现这一点. 有关 L

C#3.0中的新增功能09 LINQ 04 基本 LINQ 查询操作

连载目录    [已更新最新开发文章,点击查看详细] 本篇介绍 LINQ 查询表达式和一些在查询中执行的典型操作. 获取数据源 在 LINQ 查询中,第一步是指定数据源. 和大多数编程语言相同,在使用 C# 时也必须先声明变量,然后才能使用它. 在 LINQ 查询中,先使用 from 子句引入数据源 (customers) 和范围变量 (cust) . // 从 IEnumerable<Customer> 中查询所有的客户信息 var queryAllCustomers = from cust

C#3.0中的新增功能02 匿名类型

连载目录    [已更新最新开发文章,点击查看详细] 匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型. 类型名由编译器生成,并且不能在源代码级使用. 每个属性的类型由编译器推断. 可通过使用 new 运算符和对象初始值创建匿名类型. 有关对象初始值设定项的详细信息,请参阅对象和集合初始值设定项. 以下示例显示了用两个名为 Amount 和 Message 的属性进行初始化的匿名类型. var v = new { Amount = 108, Mess

C# 3.0中的新增功能10 表达式树 02 说明

连载目录    [已更新最新开发文章,点击查看详细] 表达式树是定义代码的数据结构. 它们基于编译器用于分析代码和生成已编译输出的相同结构.表达式树和 Roslyn API 中用于生成分析器和 CodeFixes 的类型之间存在很多相似之处. (分析器和 CodeFixes 是 NuGet 包,用于对代码执行静态分析,并可为开发人员建议可能的修补程序.)两者概念相似,且最终结果是一种数据结构,该结构允许以有意义的方式对源代码进行检查. 但是,表达式树基于一组与 Roslyn API 完全不同的类

ADO.NET 中的新增功能

ADO.NET 中的新增功能: .NET Framework (current version) 以下是 .NET Framework 4.5 中 ADO.NET 的新增功能. SqlClient Data Provider 以下是 .NET Framework 4.5 中用于 SQL Server 的 .NET Framework 数据提供程序的新增功能: ConnectRetryCount 和 ConnectRetryInterval 连接字符串关键字 (ConnectionString)

.NET Framework 1.1、2.0、3.0、3.5、4.0各版本新增功能

一..NET Framework 1.1版本 1.ASP.NET移动控件 2.ADO.NET的改动 添加System.Data.Odbc命名空间 新增System.Data.OracleClient命名空间供Oracle使用 DataReader对象公开HasRows属性,判断是否有返回行 Connection对象具有EnlistDistributedTransaction,可以在分布式事务中启动手动登记. 3.并发执行 .NET Framework 1.1版本支持并行执行. 4..NET Fr

C# 8.0中的新功能

微信公众号:Fintech极客 作者为软件开发工程师,就职于金融信息科技类公司,通过CFA一级,分享计算机和金融相结合领域的技术和知识. C# 8.0中的新功能 C# 8.0已经推出来好长一段时间了, 由于公司目前主要使用的还是6.0版本,加上之前个人事情较多,一直没有总结,今天主要查看和测试微软官方文档中的内容:https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8 只读成员(Readonly members) 在st

hadoop2.6.0汇总:新增功能最新编译 32位、64位安装、源码包、API下载及部署文档

相关内容: hadoop2.5.2汇总:新增功能最新编译 32位.64位安装.源码包.API.eclipse插件下载Hadoop2.5 Eclipse插件制作.连接集群视频.及hadoop-eclipse-plugin-2.5.0插件下载hadoop2.5.1汇总:最新编译 32位.64位安装.源码包.API下载及新特性等 新手指导:hadoop官网介绍及如何下载hadoop(2.4)各个版本与查看hadoop API介绍 从零教你在Linux环境下(ubuntu 12.04)如何编译hadoo

Windows Server 2016- Windows Server 2016 上 HYPER-V 中的新增功能

本文介绍在 Windows Server 2016 和 Microsoft HYPER-V 服务器 2016年上的新功能和更改功能 HYPER-V. 若要在使用 Windows Server 2012 R2 创建和移动或导入到服务器,在 Windows Server 2016 上运行 HYPER-V 虚拟机上使用的新功能,你将需要手动将虚拟机配置版本升级. 有关说明,请参阅 升级的虚拟机版本. 以下是本文中包含的内容和功能是否是新的或更新. 与连接待机兼容 (新) 在使用始终 On/Always