C#7的新语法

C#7为C#语言添加了许多新功能:

  • out变量

    • 您可以将out内联值声明为使用它们的方法的参数。
  • 元组
    • 您可以创建包含多个公共字段的轻量级,未命名的类型。编译器和IDE工具可以理解这些类型的语义。
  • 模式匹配
    • 您可以根据这些类型的成员的任意类型和值创建分支逻辑。
  • ref 当地人和回报
    • 方法参数和局部变量可以是对其他存储的引用。
  • 本地功能
    • 您可以将功能嵌套在其他功能中,以限制其范围和可见性。
  • 更健康的成员
    • 可以使用表达式创作的成员列表已经增加。
  • throw 表达式
    • 您可以在以前不允许的代码结构中抛出异常,因为它throw是一个语句。
  • 广义异步返回类型
    • async修饰符声明的方法可以返回除了Task和之外的其他类型Task<T>
  • 数字文字语法改进
    • 新的令牌提高了数字常量的可读性。

本主题的其余部分讨论每个功能。对于每个功能,您将了解其背后的原因。你将学习语法。您将看到一些示例场景,其中使用新功能将使您作为开发人员更有效率。

out 变量

out在此版本中,支持参数的现有语法已得到改进。

以前,您需要将out变量及其初始化的声明分成两个不同的语句:

复制

C#

int numericResult;
if (int.TryParse(input, out numericResult))
    WriteLine(numericResult);
else
    WriteLine("Could not parse input");

您现在可以out在方法调用的参数列表中声明变量,而不是编写单独的声明语句:

复制

C#

if (int.TryParse(input, out int result))
    WriteLine(result);
else
    WriteLine("Could not parse input");

out为了清楚起见,您可能需要指定变量的类型,如上所示。但是,该语言支持使用隐式类型的本地变量:

复制

C#

if (int.TryParse(input, out var answer))
    WriteLine(answer);
else
    WriteLine("Could not parse input");
  • 代码更容易阅读。

    • 你声明out变量你使用它,而不是在上面的另一行。
  • 无需分配初始值。
    • 通过out在方法调用中声明变量在哪里使用,在分配之前不能意外使用它。

这个功能的最常见的用法就是Try模式。在此模式中,方法返回bool指示成功或失败,以及out如果方法成功则提供结果的 变量。

当使用out变量声明时,声明的变量“leaks”进入if语句的外部范围。这允许您以后使用该变量:

复制

C#

if (!int.TryParse(input, out int result))
{
    return null;
}

return result;

元组

C#为类和结构提供了丰富的语法,用于解释您的设计意图。但有时,丰富的语法需要额外的工作,而最小的收益。您可能经常编写需要包含多个数据元素的简单结构的方法。为了支持这些情况, 元组被添加到C#。元组是轻量级数据结构,包含多个字段来表示数据成员。字段未被验证,您无法定义自己的方法

注意

元组在C#7之前作为API可用,但有很多限制。最重要的是,这些元组的成员被命名Item1Item2等等。语言支持支持元组字段的语义名称。

您可以通过将每个成员分配给值来创建一个元组:

复制

C#

var letters = ("a", "b");

该赋值创建一个元组,其成员是Item1,并Item2遵循现有的元组语法。您可以修改该赋值来创建一个为元组的每个成员提供语义名称的元组:

复制

C#

(string Alpha, string Beta) namedLetters = ("a", "b");
注意

新的元组功能需要System.ValueTuple类型。对于Visual Studio 15 Preview 5及更早版本的预览版本,您必须添加NuGet软件包“System.ValueTuple”,可在预发布流中使用。

namedLetters元组包含称作场Alpha和 Beta。在元组赋值中,您还可以指定作业右侧的字段名称:

复制

C#

var alphabetStart = (Alpha: "a", Beta: "b");

该语言允许您在赋值的左侧和右侧指定字段的名称:

复制

C#

(string First, string Second) firstLetters = (Alpha: "a", Beta: "b");

上面的行生成一个警告,CS8123,告诉你的赋值右侧的名称,Alpha以及Beta被忽略,因为它们与左侧的名称冲突,FirstSecond

上面的例子显示了声明元组的基本语法。元组最适合作为返回类型privateinternal方法。元组为这些方法提供了一个简单的语法来返回多个离散值:您保存创建a classstruct定义返回类型的工作。不需要创建一个新的类型。

创建元组更有效率和更高效。它是一种更简单,轻量级的语法来定义一个带有多个值的数据结构。下面的示例方法返回在整数序列中找到的最小值和最大值:

复制

C#

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);
}

以这种方式使用元组提供了几个优点:

  • 您保存创作的作品classstruct定义返回的类型的作品。
  • 您不需要创建新的类型。
  • 语言增强功能无需调用Create <T1>(T1)方法。

该方法的声明提供了返回的元组的字段的名称。当你调用该方法,返回值是一个元组,其字段MaxMin

复制

C#

var range = Range(numbers);

可能有时候要解压缩从方法返回的元组的成员。您可以通过为元组中的每个值声明单独的变量来实现。这被称为解构元组:

复制

C#

(int max, int min) = Range(numbers);

您还可以为.NET中的任何类型提供类似的解构。这是通过写一个Deconstruct方法作为类的成员完成的。该 Deconstruct方法为out您要提取的每个属性提供一组参数。考虑这个Point类,它提供一个解构方法来提取XY坐标:

复制

C#

public class Point
{
    public Point(double x, double y)
    {
        this.X = x;
        this.Y = y;
    }

    public double X { get; }
    public double Y { get; }

    public void Deconstruct(out double x, out double y)
    {
        x = this.X;
        y = this.Y;
    }
}

您可以通过为一个元组分配一个元组来提取各个字段Point

复制

C#

var p = new Point(3.14, 2.71);
(double X, double Y) = p;

您不受Deconstruct方法中定义的名称的约束。您可以将提取变量重命名为作业的一部分:

复制

C#

(double horizontalDistance, double verticalDistance) = p;

您可以在元组主题中详细了解元 

模式匹配

模式匹配是一种功能,允许您在属性以外的对象的类型上实现方法分派。您可能已经熟悉基于对象类型的方法分派。在面向对象编程中,虚拟和覆盖方法提供语言语法来实现基于对象类型的方法调度。Base和Derived类提供不同的实现。模式匹配表达式扩展了这个概念,以便您可以轻松地实现与继承层次结构无关的类型和数据元素的类似的分派模式。

模式匹配支持is表达式和switch表达式。每个都可以检查对象及其属性来确定该对象是否满足所寻求的模式。您可以使用when关键字为模式指定其他规则。

is 表达

所述is图案表达延伸熟悉的is操作者查询一个对象超出其类型。

我们从一个简单的场景开始吧。我们将为此场景添加功能,演示了模式匹配表达式如何使不起作用的类型的算法变得容易。我们将从一个计算多个模具卷总和的方法开始:

复制

C#

public static int DiceSum(IEnumerable<int> values)
{
    return values.Sum();
}

您可能会很快发现,您需要找到一些模具的总和,其中一些卷筒由多个模具制成。输入序列的一部分可能是多个结果而不是单个数字:

复制

C#

public static int DiceSum2(IEnumerable<object> values)
{
    var sum = 0;
    foreach(var item in values)
    {
        if (item is int val)
            sum += val;
        else if (item is IEnumerable<object> subList)
            sum += DiceSum2(subList);
    }
    return sum;
}

is在这种情况下,模式表达式工作得很好。作为检查类型的一部分,您将编写一个变量初始化。这将创建验证的运行时类型的新变量。

当您不断扩展这些方案时,您可能会发现您构建更多的 语句ifelse if语句。一旦变得笨拙,您可能需要切换到switch模式表达式。

switch 语句更新

比赛的表达有一种熟悉的语法,基于该switch 声明的C#语言中有一部分。在添加新案例之前,让我们翻译现有的代码以使用匹配表达式:

复制

C#

public static int DiceSum3(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case int val:
                sum += val;
                break;
            case IEnumerable<object> subList:
                sum += DiceSum3(subList);
                break;
        }
    }
    return sum;
}

匹配表达式与表达式的语法略有不同is,您可以在表达式的开头声明类型和变量case

匹配表达式还支持常量。这可以通过分解简单的案例节省时间:

复制

C#

public static int DiceSum4(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case 0:
                break;
            case int val:
                sum += val;
                break;
            case IEnumerable<object> subList when subList.Any():
                sum += DiceSum4(subList);
                break;
            case IEnumerable<object> subList:
                break;
            case null:
                break;
            default:
                throw new InvalidOperationException("unknown item type");
        }
    }
    return sum;
}

上面的代码添加了0一个例子intnull 作为一个特殊情况,当没有输入的情况下。这表明了切换模式表达式中的一个重要的新功能:表达式的顺序case 现在很重要。的0情况下,必须在之前一般会出现int 的情况。否则,匹配的第一个模式是这种int情况,即使是值0。如果您不小心对匹配表达式进行排序,以便稍后的情况已经被处理,编译器会标记并产生错误。

同样的行为使特殊情况能够为空的输入序列。您可以看到IEnumerable具有元素的项目的情况必须出现在一般IEnumerable情况之前。

这个版本也增加了一个default例子。default始终对案件进行最后评估,无论其在源中显示的顺序如何。因此,约定是把default案件延续下去。

最后,我们来添加一个最后case一个新的死亡风格。一些游戏使用百分位数骰子代表更大范围的数字。

注意

两个10双面百分骰子可以通过99.一种模具已标记的边表示0每一个数001020,... 90。其他模具具有两侧标记012,... 9。将两个死亡值添加到一起,您可以从0到99获得每个数字。

要将这种模具添加到您的集合中,首先要定义一个类型来表示百分位数的模具:

复制

C#

public struct PercentileDie
{
    public int Value { get; }
    public int Multiplier { get; }

    public PercentileDie(int multiplier, int value)
    {
        this.Value = value;
        this.Multiplier = multiplier;
    }
}

然后,添加case新类型的匹配表达式:

复制

C#

public static int DiceSum5(IEnumerable<object> values)
{
    var sum = 0;
    foreach (var item in values)
    {
        switch (item)
        {
            case 0:
                break;
            case int val:
                sum += val;
                break;
            case PercentileDie die:
                sum += die.Multiplier * die.Value;
                break;
            case IEnumerable<object> subList when subList.Any():
                sum += DiceSum5(subList);
                break;
            case IEnumerable<object> subList:
                break;
            case null:
                break;
            default:
                throw new InvalidOperationException("unknown item type");
        }
    }
    return sum;
}

模式匹配表达式的新语法使得使用清晰简洁的语法可以更容易地创建基于对象类型或其他属性的调度算法。模式匹配表达式使得这些构造对于与继承无关的数据类型。

您可以在C#中专门用于模式匹配的主题中详细了解模式匹配。

参考当地人和回报

此功能启用使用和返回对其他地方定义的变量的引用的算法。一个例子是使用大型矩阵,并找到具有某些特征的单个位置。一种方法将返回矩阵中单个位置的两个索引:

复制

C#

public static (int i, int j) Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return (i, j);
    return (-1, -1); // Not found
}

这段代码有很多问题。首先,这是一个返回一个元组的公共方法。该语言支持此功能,但用户定义的类型(类或结构体)是公共API的首选。

其次,该方法将索引返回到矩阵中的项。这导致调用者编写使用这些索引的代码来取消引用矩阵并修改单个元素:

复制

C#

var indices = MatrixSearch.Find(matrix, (val) => val == 42);
Console.WriteLine(indices);
matrix[indices.i, indices.j] = 24;

您宁愿编写一个方法,返回对 要更改的矩阵元素的引用。您只能通过使用不安全的代码并int在以前的版本中返回一个指针来完成此操作。

我们来浏览一系列更改,以演示参考本地功能,并展示如何创建一个返回对内部存储的引用的方法。一路上,您将学习参考回报的规则和参考本地功能,保护您免受意外误用。

首先修改Find方法声明,使其返回ref int 而不是元组。然后,修改return语句,以便返回存储在矩阵中的值,而不是两个索引:

复制

C#

// Note that this won‘t compile.
// Method declaration indicates ref return,
// but return statement specifies a value return.
public static ref int Find2(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return matrix[i, j];
    throw new InvalidOperationException("Not found");
}

当您声明一个方法返回一个ref变量时,您还必须将ref关键字添加到每个返回语句。这通过引用返回,并帮助开发人员阅读代码,以后记住该方法返回参考:

复制

C#

public static ref int Find3(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

现在该方法返回对矩阵中整数值的引用,您需要修改它被调用的位置。该var声明意味着,valItem现在是int不是一个元组:

复制

C#

var valItem = MatrixSearch.Find3(matrix, (val) => val == 42);
Console.WriteLine(valItem);
valItem = 24;
Console.WriteLine(matrix[4, 2]);

WriteLine在上面的例子中的第二个声明打印出的值42,而不是24。变量valItem是一个int,而不是一个ref int。该var 关键字使编译器能够指定类型,但不会隐式地添加ref修饰符。相反,该值称为由ref return 被复制到变量的赋值的左手侧。该变量不是ref本地的。

为了获得所需的结果,您需要将ref修饰符添加到局部变量声明中,以使变量在返回值为引用时为引用:

复制

C#

ref var item = ref MatrixSearch.Find3(matrix, (val) => val == 42);
Console.WriteLine(item);
item = 24;
Console.WriteLine(matrix[4, 2]);

现在,WriteLine上面的例子中的第二个语句将打印出该值24,表明矩阵中的存储已被修改。局部变量已被声明为ref修饰符,它将ref返回。ref声明时必须初始化一个变量,不能拆分声明和初始化。

C#语言有另外两个规则可以保护您免受ref当地人的误用和返回:

  • 您不能为ref变量赋值。

    • 这不允许这样的声明 ref int i = sequence.Count();
  • 您不能返回ref到其生命周期不超出方法执行的变量。
    • 这意味着您不能返回对局部变量或类似范围的引用。

这些规则确保您不会意外混合价值变量和参考变量。他们还确保您不能有参考变量参考作为垃圾回收候选的存储。

引用本地和引用返回的添加使得通过避免复制值或执行解引用操作多次更有效的算法。

本地功能

类的许多设计包括仅从一个位置调用的方法。这些附加的私有方法保持每个方法的小小和集中。但是,第一次阅读时,他们可以更难理解课堂。必须在单个呼叫位置的上下文之外了解这些方法。

对于这些设计,本地函数使您能够在另一种方法的上下文中声明方法。这使得类的读者更容易看到本地方法只是从声明的上下文中调用。

本地函数有两个很常见的用例:public iterator方法和public异步方法。这两种类型的方法都会生成比程序员可能期望的错误报告错误的代码。在迭代器方法的情况下,只有在调用返回的序列的代码时才会观察到任何异常。在异步方法的情况下,只有Task等待返回时才会观察到任何异常 。

我们从迭代器方法开始:

复制

C#

public static IEnumerable<char> AlphabetSubset(char start, char end)
{
    if ((start < ‘a‘) || (start > ‘z‘))
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if ((end < ‘a‘) || (end > ‘z‘))
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    for (var c = start; c < end; c++)
        yield return c;
}

检查下面的错误调用迭代器方法的代码:

复制

C#

var resultSet = Iterator.AlphabetSubset(‘f‘, ‘a‘);
Console.WriteLine("iterator created");
foreach (var thing in resultSet)
    Console.Write($"{thing}, ");

resultSet迭代时抛出异常,而不是resultSet创建时。在这个例子中,大多数开发人员可以快速诊断问题。然而,在较大的代码库中,创建迭代器的代码通常不会与枚举结果的代码接近。您可以重构代码,使公共方法验证所有参数,并且私有方法生成枚举:

复制

C#

public static IEnumerable<char> AlphabetSubset2(char start, char end)
{
    if ((start < ‘a‘) || (start > ‘z‘))
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if ((end < ‘a‘) || (end > ‘z‘))
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    return alphabetSubsetImplementation(start, end);
}

private static IEnumerable<char> alphabetSubsetImplementation(char start, char end)
{
    for (var c = start; c < end; c++)
        yield return c;
}

这个重构版本会立即抛出异常,因为public方法不是迭代器方法; 只有私有方法使用 yield return语法。但是,这种重构存在潜在的问题。私有方法只能从公共接口方法调用,否则跳过所有参数验证。类的读者必须通过阅读整个类并搜索对该alphabetSubsetImplementation 方法的任何其他引用来发现这一事实。

您可以通过alphabetSubsetImplementation在公共API方法中声明为本地函数来使设计意图更加清晰 :

复制

C#

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if ((start < ‘a‘) || (start > ‘z‘))
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if ((end < ‘a‘) || (end > ‘z‘))
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

上面的版本清楚地表明,本地方法仅在外部方法的上下文中引用。本地函数的规则还可以确保开发人员无法从类中另一个位置意外地调用本地函数,并绕过参数验证。

同样的技术可以用于async确保在异步工作开始之前抛出由参数验证产生的异常的方法:

复制

C#

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}
注意

局部函数支持的一些设计也可以使用lambda表达式来完成。有兴趣的人可以阅读更多关于差异的信息

更健康的成员

C#6引入表达健全成员 成员函数,和只读属性。C#7扩展了可以实现为表达式的允许成员。在C#7,可以实现 构造函数终结器,并getset在存取性能 和索引。以下代码显示了每个的示例:

复制

C#

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}
注意

这个例子不需要一个finalizer,但它显示了演示语法。你不应该在你的类中实现一个finalizer,除非有必要释放非托管资源。您还应考虑使用 SafeHandle类,而不是直接管理非托管资源。

表达式成员的这些新位置代表了C#语言的重要里程碑:这些功能由在开源Roslyn项目上工作的社区成员实现 。

投掷表情

在C#中,throw一直是一个声明。因为throw是一个语句,而是一个表达式,那里有C#构造,你不能使用它。这些包括条件表达式,空合并表达式和一些lambda表达式。表达式成员的添加增加了更多的throw表达式将有用的位置。所以你可以编写任何这些结构,C#7引入了throw表达式

语法与您一直用于语句的语法相同throw。唯一的区别是,现在你可以将它们放在新的位置,例如条件表达式中:

复制

C#

public string Name
{
    get => name;
    set => name = value ??
        throw new ArgumentNullException(paramName: nameof(value), message: "New name must not be null");
}

此功能可在初始化表达式中使用throw表达式:

复制

C#

private ConfigResource loadedConfig = LoadConfigResourceOrDefault() ??
    throw new InvalidOperationException("Could not load config");

以前,这些初始化将需要在一个构造函数中,在构造函数的正文中使用throw语句:

复制

C#

public ApplicationOptions()
{
    loadedConfig = LoadConfigResourceOrDefault();
    if (loadedConfig == null)
        throw new InvalidOperationException("Could not load config");

}
注意

在构造对象期间,这两个前面的结构都将引发异常抛出。那些往往难以恢复。因此,不鼓励在施工期间抛出异常的设计。

广义异步返回类型

返回一个Task从异步方法对象可以引入在某些路径的性能瓶颈。Task是一个引用类型,所以使用它意味着分配一个对象。在使用async修饰符声明的方法返回缓存结果或同步完成的情况下,额外的分配可能会在性能关键部分代码中成为重要的时间成本。如果这些分配发生在严格的循环中,则可能会变得非常昂贵。

新的语言功能意味着异步方法可能除了返回其他类型TaskTask<T>void。返回的类型必须仍然满足async模式,这意味着一种GetAwaiter方法必须可访问。作为一个具体的例子,该ValueTask类型已经添加到.NET框架中,以利用这种新的语言功能:

复制

C#

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}
注意

您需要添加预发行版NuGet软件包System.Threading.Tasks.Extensions 才能ValueTask在Visual Studio 15 Preview 5中使用。

简单的优化将ValueTaskTask以前使用的地方 使用。但是,如果要手动执行额外的优化,可以从异步工作中缓存结果,并在随后的调用中重用结果。该ValueTask结构体具有一个带有参数的Task构造函数,以便可以ValueTask从任何现有异步方法的返回值构造一个:

复制

C#

public ValueTask<int> CachedFunc()
{
    return (cache) ? new ValueTask<int>(cacheResult) : new ValueTask<int>(loadCache());
}
private bool cache = false;
private int cacheResult;
private async Task<int> loadCache()
{
    // simulate async work:
    await Task.Delay(100);
    cache = true;
    cacheResult = 100;
    return cacheResult;
}

与所有性能建议一样,您应该对两个版本进行基准测试,然后对代码进行大规模更改。

数字文字语法改进

误读数字常量可能会使您在第一次阅读时难以理解代码。当这些数字用作位掩码或其他符号而不是数字值时,通常会发生这种情况。C#7包括两个新功能,可以更容易地以最可读的方式编写数字用于预期用途:二进制文字数字分隔符

在创建位掩码时,或者当数字的二进制表示形成最可读的代码时,请以二进制形式写入该数字:

复制

C#

public const int One =  0b0001;
public const int Two =  0b0010;
public const int Four = 0b0100;
public const int Eight = 0b1000;

0b在恒定的开头指示该数字被写成二进制数。

二进制数可以很长,所以通过引入_一个数位分隔符来看,更容易看到位模式:

复制

C#

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

数字分隔符可以出现在常量的任何位置。对于10号数字,通常将其用作千位分隔符:

复制

C#

public const long BillionsAndBillions = 100_000_000_000;

数字分离器可以与被使用decimalfloatdouble 类型以及:

复制

C#

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;

总而言之,您可以声明具有更多可读性的数字常量。

参考地址:https://docs.microsoft.com/zh-cn/dotnet/articles/csharp/whats-new/csharp-7

时间: 2024-10-14 05:33:20

C#7的新语法的相关文章

队列与DelphiXe新语法

好久没写代码了,更久没上博客园的博客了,无聊写几行试一下新语法. 1 unit Main; 2 3 interface 4 5 uses 6 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 7 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls, 8 9 iHome.Help

.NET中那些所谓的新语法之三:系统预定义委托与Lambda表达式

开篇:在上一篇中,我们了解了匿名类.匿名方法与扩展方法等所谓的新语法,这一篇我们继续征程,看看系统预定义委托(Action/Func/Predicate)和超爱的Lambda表达式.为了方便码农们,.Net基类库针对实际开发中最常用的情形提供了几个预定义好的委托,这些委托可以直接使用,无需再重头定义一个自己的委托类型.预定义委托在.Net基类库中使用的比较广泛,比如在Lambda表达式和并行计算中都大量地使用,需要我们予以关注起来! /* 新语法索引 */ 1.自动属性 Auto-Impleme

.NET中那些所谓的新语法之一:自动属性、隐式类型、命名参数与自动初始化器

开篇:在日常的.NET开发学习中,我们往往会接触到一些较新的语法,它们相对以前的老语法相比,做了很多的改进,简化了很多繁杂的代码格式,也大大减少了我们这些菜鸟码农的代码量.但是,在开心欢乐之余,我们也不禁地对编译器内部到底为我们做了哪些事儿而感到好奇?于是,我们就借助反编译神器,去看看编译器到底做了啥事!其实本篇中很多都不算新语法,对于很多人来说可能都是接触了很久了,这里主要是针对.NET的老版本来说,是一个“相对”的新语法. /* 新语法索引 */ 1.自动属性 Auto-Implemente

总结常见的ES6新语法特性。

前言 ES6是即将到来的新版本JavaScript语言的标准,他给我们带来了更"甜"的语法糖(一种语法,使得语言更容易理解和更具有可读性,也让我们编写代码更加简单快捷),如箭头函数(=>).class等等.用一句话来说就是: ES6给我们提供了许多的新语法和代码特性来提高javascript的体验 不过遗憾的是,现在还没有浏览器能够很好的支持es6语法,点这里查看浏览器支持情况,所以我们在开发中还需要用babel进行转换为CommonJS这种模块化标准的语法. 因为下面我会讲到一

.NET中那些所谓的新语法之四:标准查询运算符与LINQ

开篇:在上一篇中,我们了解了预定义委托与Lambda表达式等所谓的新语法,这一篇我们继续征程,看看标准查询运算符和LINQ.标准查询运算符是定义在System.Linq.Enumerable类中的50多个为IEnumerable<T>准备的扩展方法,而LINQ则是一种类似于SQL风格的查询表达式,它们可以大大方便我们的日常开发工作.因此,需要我们予以关注起来! /* 新语法索引 */ 1.自动属性 Auto-Implemented Properties 2.隐式类型 var 3.参数默认值 和

总结常见的ES6新语法特性

总结常见的ES6新语法特性 ES6给我们带来了更"甜"的语法糖(一种语法,使得语言更容易理解和更具有可读性,也让我们编写代码更加简单快捷),如箭头函数(=>).class等等.用一句话来说就是: ES6给我们提供了许多的新语法和代码特性来提高javascript的体验 不过遗憾的是,现在还没有浏览器能够很好的支持es6语法,点这里查看浏览器支持情况,所以我们在开发中还需要用babel进行转换为CommonJS这种模块化标准的语法. 因为下面我会讲到一些es6新特性的例子,如果想要

一起学习c++11——c++11中的新语法

c++11新语法1: auto关键字 c++11 添加的最有用的一个特性应该就是auto关键字. 不知道大家有没有写过这样的代码: std::map<std::string, std::vector<std::shared_ptr<std::list<T> > > > map; std::map<std::string, std::vector<std::shared_ptr<std::list<T> > > >

Qt 学习之路:深入 Qt5 信号槽新语法

在前面的章节(信号槽和自定义信号槽)中,我们详细介绍了有关 Qt 5 的信号槽新语法.由于这次改动很大,许多以前看起来不是问题的问题接踵而来,因此,我们用单独的一章重新介绍一些 Qt 5 的信号槽新语法. 基本用法 Qt 5 引入了信号槽的新语法:使用函数指针能够获得编译期的类型检查.使用我们在自定义信号槽中设计的Newspaper类,我们来看看其基本语法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

.NET中那些所谓的新语法

.NET中那些所谓的新语法之四:标准查询运算符与LINQ 摘要: 开篇:在上一篇中,我们了解了预定义委托与Lambda表达式等所谓的新语法,这一篇我们继续征程,看看标准查询运算符和LINQ.标准查询运算符是定义在System.Linq.Enumerable类中的50多个为IEnumerable准备的扩展方法,而LINQ则是一种类似于SQL风格的查询表达式,它们可以...阅读全文 posted @ 2014-11-20 21:29 Edison Chou 阅读(1230) | 评论 (10) 编辑