C# 7.0 中的新增功能

时间:2021-11-10 05:36:51

链接: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 Normalize method for full implementation.

return (          

System.IO.Path.GetDirectoryName(path),

System.IO.Path.GetFileNameWithoutExtension(path),

System.IO.Path.GetExtension(path)

);

}