C# 语法(C#6.0 C#7.0 C#8.0 C#9.0)

官网地址:https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-version-history

C# 9.0

init属性访问器

对象初始化方式对于创建对象来说是一种非常灵活和可读的格式,特别是对树状嵌入型对象的创建。简单的例如

new Person
{
    FirstName = "Scott",
    LastName = "Hunter"
}

原有的要进行对象初始化,我们必须要做就是写一些属性,并且在构造函数的初次调用中,通过给属性的setter赋值来实现。

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

就这样的方式而言,set访问器对于初始化来说是必须的,但是如果你想要的是只读属性,这个set就是不合适的。除过初始化,其他情况不需要,而且很多情况下容易引起属性值改变。为了解决这个矛盾,只用来初始化的init访问器出现了。init访问器是一个只在对象初始化时用来赋值的set访问器的变体,并且后续的赋值操作是不允许的。例如:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

 

record 关键字

可轻松创建不可变的引用类型,这个优点在使用共享数据的并发程序中更为明显

public record Person
{
    public string LastName { get; }
    public string FirstName { get; }

    public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

记录支持 with 表达式。 with 表达式指示编译器创建记录的副本,但修改指定的属性 * 

var person = new Person("Bill", "Wagner");
Person brother = person with { FirstName = "Paul" };

创建新的 Person 记录,其中 LastName 属性是 person 的副本,FirstName 为 "Paul"。 可在 with 表达式中设置任意数量的属性。 还可以使用 with 表达式来创建精确的副本。 为要修改的属性指定空集

Person clone = person with { };

 

对象声明

                  //FooInfo foo = new FooInfo(); 
                    FooInfo fooInfo1 = new();  //语法糖
                    FooInfo fooInfo2 = new() { PropA = "123", PropB = "234" };

 静态修饰

                Func<int> func1 = () => 1;

                Func<int> func = static () => 1;

 

顶级程序

通常,我们洗一个简单的C#程序,都要求有大量的样例代码:

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

这个对于初学者是无法抗拒,但是这使得代码凌乱,大量堆积,并且增加了缩进层级。在C#9.0中,你可以选择在顶级用如下代码代替写你的主程序:

System.Console.Console.WriteLine("Hello World!");

当然,任何语句都是允许的。但是这个程序代码必须出现在using后,在任何类型或者命名空间声明的前面。并且你只能在一个文件里面这样做,像你如今只写一个main方法一样。

如果你想返回状态,你可以那样做,你想用await,也可以那样做。并且,如果你想访问命令行参数,args也是可用的。

本地方法是语句的另一个形式,也是允许在顶级程序代码用的。在顶级代码段外部的任何地方调用他们都会产生错误。

 增强的模式匹配

C# 9 包括新的模式匹配改进:

  • 类型模式要求在变量是一种类型时匹配
  • 带圆括号的模式强制或强调模式组合的优先级
  • 联合 and 模式要求两个模式都匹配
  • 析取 or 模式要求任一模式匹配
  • 求反 not 模式要求模式不匹配
  • 关系模式要求输入小于、大于、小于等于或大于等于给定常数。

这些模式丰富了模式的语法。 请考虑下列示例:

public static bool IsLetter(this char c) =>
    c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

还可使用可选的括号来明确 and 的优先级高于 or

public static bool IsLetterOrSeparator(this char c) =>
    c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

最常见的用途之一是用于 NULL 检查的新语法:

if (e is not null)
{
    // ...
}

这些模式中的任何一种都可在允许使用模式的任何上下文中使用:is 模式表达式、switch 表达式、嵌套模式以及 switch 语句的 case 标签的模式。

 

新类型  nint

一组新类型(nint,nuint,nfloat等)'n'表示native(原生),该特性允许声明一个32位或64位的数据类型,这取决于操作系统的平台类型。

      nint nativeInt = 55; //在32位主机中编译时,需要4个字节。    
      nint nativeInt1 = 55; //使用x64编译设置在64位主机中编译时需要8个字节。

 

支持协变的返回值

一些时候,在子类的一个重写方法中返回一个更具体的、且不同于父类方法定义的返回类型更为有用,C# 9.0对这种情况提供了支持。

复制代码
abstract class Animal
{
    public abstract Food GetFood();
    ...
}
class Tiger : Animal
{
    public override Meat GetFood() => ...;
}
复制代码

  Lambda 参数弃元 

复制代码
                {
                    // C# 9 之前
                    Func<int, int, int> zero = (a, b) => 0;
                    Func<int, int, int> func = delegate (int a, int b) { return 0; };
                }

                // C# 9
                {
                    Func<int, int, int> zero = (_, _) => 0;
                    Func<int, int, int> func = delegate (int _, int _) { return 0; };
                }
复制代码

 

C#8.0

Readonly 成员

可将 readonly 修饰符应用于结构的成员。 它指示该成员不会修改状态。 这比将 readonly 修饰符应用于 struct 声明更精细。 请考虑以下可变结构:

复制代码
public struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Distance => Math.Sqrt(X * X + Y * Y);
​
    public override string ToString() =>
        $"({X}, {Y}) is {Distance} from the origin";
}
复制代码

与大多数结构一样,ToString() 方法不会修改状态。 可以通过将 readonly 修饰符添加到 ToString() 的声明来对此进行指示:

public readonly override string ToString() =>
    $"({X}, {Y}) is {Distance} from the origin";

上述更改会生成编译器警告,因为 ToString 访问未标记为 readonlyDistance 属性:

warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an implicit copy of 'this'

需要创建防御性副本时,编译器会发出警告。 Distance 属性不会更改状态,因此可以通过将 readonly 修饰符添加到声明来修复此警告:

public readonly double Distance => Math.Sqrt(X * X + Y * Y);

请注意,readonly 修饰符对于只读属性是必需的。 编译器会假设 get 访问器可以修改状态;必须显式声明 readonly。 自动实现的属性是一个例外;编译器会将所有自动实现的 Getter 视为 readonly,因此,此处无需向 XY 属性添加 readonly 修饰符。

编译器确实会强制执行 readonly 成员不修改状态的规则。 除非删除 readonly 修饰符,否则不会编译以下方法:

public readonly void Translate(int xOffset, int yOffset)
{
    X += xOffset;
    Y += yOffset;
}

通过此功能,可以指定设计意图,使编译器可以强制执行该意图,并基于该意图进行优化。

有关详细信息,请参阅结构类型一文中的 readonly 实例成员部分。

在更多位置中使用更多模式

模式匹配 提供了在相关但不同类型的数据中提供形状相关功能的工具。

switch 表达式

通常情况下,switch 语句在其每个 case 块中生成一个值。 借助 Switch 表达式 ,可以使用更简洁的表达式语法。 只有些许重复的 casebreak 关键字和大括号。 以下面列出彩虹颜色的枚举为例:

复制代码
public enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
复制代码

之前写法

复制代码
public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
    switch (colorBand)
    {
        case Rainbow.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case Rainbow.Orange:
            return new RGBColor(0xFF, 0x7F, 0x00);
        case Rainbow.Yellow:
            return new RGBColor(0xFF, 0xFF, 0x00);
        case Rainbow.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case Rainbow.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        case Rainbow.Indigo:
            return new RGBColor(0x4B, 0x00, 0x82);
        case Rainbow.Violet:
            return new RGBColor(0x94, 0x00, 0xD3);
        default:
            throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
    };
}
复制代码

现在的写法

复制代码
public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
        Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };
复制代码

这里有几个语法改进:

  • 变量位于 switch 关键字之前。 不同的顺序使得在视觉上可以很轻松地区分 switch 表达式和 switch 语句。

  • case: 元素替换为 =>。 它更简洁,更直观。

  • default 事例替换为 _ 弃元。

  • 正文是表达式,不是语句。

将其与使用经典 switch 语句的等效代码进行对比:

属性模式

借助属性模式 ,可以匹配所检查的对象的属性。 请看一个电子商务网站的示例,该网站必须根据买家地址计算销售税。 这种计算不是 Address 类的核心职责。 它会随时间变化,可能比地址格式的更改更频繁。 销售税的金额取决于地址的 State 属性。 下面的方法使用属性模式从地址和价格计算销售税:

复制代码
public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
    location switch
    {
        { State: "WA" } => salePrice * 0.06M,
        { State: "MN" } => salePrice * 0.075M,
        { State: "MI" } => salePrice * 0.05M,
        // other cases removed for brevity...
        _ => 0M
    };
复制代码

模式匹配为表达此算法创建了简洁的语法。

元组模式

一些算法依赖于多个输入。 使用元组模式,可根据表示为元组的多个值进行切换 。 以下代码显示了游戏“rock, paper, scissors(石头剪刀布)”的切换表达式: :

复制代码
public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };
复制代码

消息指示获胜者。 弃元表示平局(石头剪刀布游戏)的三种组合或其他文本输入。

位置模式

某些类型包含 Deconstruct 方法,该方法将其属性解构为离散变量。 如果可以访问 Deconstruct 方法,就可以使用位置模式 检查对象的属性并将这些属性用于模式。 考虑以下 Point 类,其中包含用于为 XY 创建离散变量的 Deconstruct 方法:

复制代码
public class Point
{
    public int X { get; }
    public int Y { get; }
​
    public Point(int x, int y) => (X, Y) = (x, y);
​
    public void Deconstruct(out int x, out int y) =>
        (x, y) = (X, Y);
}
复制代码

此外,请考虑以下表示象限的各种位置的枚举:

复制代码
public enum Quadrant
{
    Unknown,
    Origin,
    One,
    Two,
    Three,
    Four,
    OnBorder
}
复制代码

下面的方法使用位置模式 来提取 xy 的值。 然后,它使用 when 子句来确定该点的 Quadrant

复制代码
static Quadrant GetQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};
复制代码

xy 为 0(但不是两者同时为 0)时,前一个开关中的弃元模式匹配。 Switch 表达式必须要么生成值,要么引发异常。 如果这些情况都不匹配,则 switch 表达式将引发异常。 如果没有在 switch 表达式中涵盖所有可能的情况,编译器将生成一个警告。

可在此模式匹配高级教程中探索模式匹配方法。

using 声明

using 声明 是前面带 using 关键字的变量声明。 它指示编译器声明的变量应在封闭范围的末尾进行处理。 以下面编写文本文件的代码为例:

之前经典 using 语句的代码写法

复制代码
static int WriteLinesToFile(IEnumerable<string> lines)
{
    // We must declare the variable outside of the using block
    // so that it is in scope to be returned.
    int skippedLines = 0;
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        foreach (string line in lines)
        {
            if (!line.Contains("Second"))
            {
                file.WriteLine(line);
            }
            else
            {
                skippedLines++;
            }
        }
    } // file is disposed here
    return skippedLines;
}
复制代码

现在写法

复制代码
static int WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    // Notice how we declare skippedLines after the using statement.
    int skippedLines = 0;
    foreach (string line in lines)
    {
        if (!line.Contains("Second"))
        {
            file.WriteLine(line);
        }
        else
        {
            skippedLines++;
        }
    }
    // Notice how skippedLines is in scope here.
    return skippedLines;
    // file is disposed here
}
复制代码

在上面的的示例中,当到达方法的右括号时,将对该文件进行处理。 这是声明 file 的范围的末尾。 在前面的示例中,当到达与 using 语句关联的右括号时,将对该文件进行处理。

在这两种情况下,编译器将生成对 Dispose() 的调用。 如果 using 语句中的表达式不可用,编译器将生成一个错误。

静态本地函数

现在可以向本地函数添加 static 修饰符,以确保本地函数不会从封闭范围捕获(引用)任何变量。 这样做会生成 CS8421,“静态本地函数不能包含对 <variable> 的引用”。

考虑下列代码。 本地函数 LocalFunction 访问在封闭范围(方法 M)中声明的变量 y。 因此,不能用 static 修饰符来声明 LocalFunction

int M()
{
    int y;
    LocalFunction();
    return y;
​
    void LocalFunction() => y = 0;
}

下面的代码包含一个静态本地函数。 它可以是静态的,因为它不访问封闭范围中的任何变量:

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);
​
    static int Add(int left, int right) => left + right;
}

可处置的 ref 结构

ref 修饰符声明的 struct 可能无法实现任何接口,因此无法实现 IDisposable。 因此,要能够处理 ref struct,它必须有一个可访问的 void Dispose() 方法。 此功能同样适用于 readonly ref struct 声明。

异步流

从 C# 8.0 开始,可以创建并以异步方式使用流。 返回异步流的方法有三个属性:

  1. 它是用 async 修饰符声明的。

  2. 它将返回 IAsyncEnumerable

  3. 该方法包含用于在异步流中返回连续元素的 yield return 语句。

使用异步流需要在枚举流元素时在 foreach 关键字前面添加 await 关键字。 添加 await 关键字需要枚举异步流的方法,以使用 async 修饰符进行声明并返回 async 方法允许的类型。 通常这意味着返回 TaskTask。 也可以为 ValueTaskValueTask。 方法既可以使用异步流,也可以生成异步流,这意味着它将返回 IAsyncEnumerable。 下面的代码生成一个从 0 到 19 的序列,在生成每个数字之间等待 100 毫秒:

public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence()
{
    for (int i = 0; i < 20; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

可以使用 await foreach 语句来枚举序列:

await foreach (var number in GenerateSequence())
{
    Console.WriteLine(number);
}

可以在创建和使用异步流的教程中自行尝试异步流。 默认情况下,在捕获的上下文中处理流元素。 如果要禁用上下文捕获,请使用 TaskAsyncEnumerableExtensions.ConfigureAwait 扩展方法。 有关同步上下文并捕获当前上下文的详细信息,请参阅有关使用基于任务的异步模式的文章。

索引和范围

索引和范围为访问序列中的单个元素或范围提供了简洁的语法。

此语言支持依赖于两个新类型和两个新运算符:

  • System.Index 表示一个序列索引。

  • 来自末尾运算符 ^ 的索引,指定一个索引与序列末尾相关。

  • System.Range 表示序列的子范围。

  • 范围运算符 ..,用于指定范围的开始和末尾,就像操作数一样。

让我们从索引规则开始。 请考虑数组 sequence0 索引与 sequence[0] 相同。 ^0 索引与 sequence[sequence.Length] 相同。 请注意,sequence[^0] 不会引发异常,就像 sequence[sequence.Length] 一样。 对于任何数字 n,索引 ^nsequence.Length - n 相同。

范围指定范围的开始和末尾 。 包括此范围的开始,但不包括此范围的末尾,这表示此范围包含开始但不包含末尾 。 范围 [0..^0] 表示整个范围,就像 [0..sequence.Length] 表示整个范围。

请看以下几个示例。 请考虑以下数组,用其顺数索引和倒数索引进行注释:

复制代码
var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0
复制代码

可以使用 ^1 索引检索最后一个词:

Console.WriteLine($"The last word is {words[^1]}");
// writes "dog"

以下代码创建了一个包含单词“quick”、“brown”和“fox”的子范围。 它包括 words[1]words[3]。 元素 words[4] 不在该范围内。

var quickBrownFox = words[1..4];

以下代码使用“lazy”和“dog”创建一个子范围。 它包括 words[^2]words[^1]。 末尾索引 words[^0] 不包括在内:

var lazyDog = words[^2..^0];

下面的示例为开始和/或结束创建了开放范围:

var allWords = words[..]; // contains "The" through "dog".
var firstPhrase = words[..4]; // contains "The" through "fox"
var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"

此外可以将范围声明为变量:

Range phrase = 1..4;

然后可以在 [] 字符中使用该范围:

var text = words[phrase];

不仅数组支持索引和范围。 还可以将索引和范围用于 stringSpanReadOnlySpan。 有关详细信息,请参阅索引和范围的类型支持

可在有关索引和范围的教程中详细了解索引和范围。

Null 合并赋值

C# 8.0 引入了 null 合并赋值运算符 ??=。 仅当左操作数计算为 null 时,才能使用运算符 ??= 将其右操作数的值分配给左操作数。

复制代码
List<int> numbers = null;
int? i = null;
​
numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);
​
Console.WriteLine(string.Join(" ", numbers));  // output: 17 17
Console.WriteLine(i);  // output: 17
复制代码

有关详细信息,请参阅 ?? 和 ??= 运算符一文。

非托管构造类型

在 C# 7.3 及更低版本中,构造类型(包含至少一个类型参数的类型)不能为非托管类型。 从 C# 8.0 开始,如果构造的值类型仅包含非托管类型的字段,则该类型不受管理。

例如,假设泛型 Coords 类型有以下定义:

public struct Coords<T>
{
    public T X;
    public T Y;
}

Coords 类型为 C# 8.0 及更高版本中的非托管类型。 与任何非托管类型一样,可以创建指向此类型的变量的指针,或针对此类型的实例在堆栈上分配内存块

Span<Coords<int>> coordinates = stackalloc[]
{
    new Coords<int> { X = 0, Y = 0 },
    new Coords<int> { X = 0, Y = 3 },
    new Coords<int> { X = 4, Y = 0 }
};

有关详细信息,请参阅非托管类型

内插逐字字符串的增强功能

内插逐字字符串中 $@ 标记的顺序可以任意安排:$@"..."@$"..." 均为有效的内插逐字字符串。 在早期 C# 版本中,$ 标记必须出现在 @ 标记之前。

C# 7.0

一、out输出参数

在以前使用out输出参数的时候,必须先定义变量,然后才能使用,例如:

先定义一个方法,方法参数是out类型的输出参数:

 private void DoNoting(out int x, out int y)
 {
     x = 1;
     y = 2;
 }

以前版本的写法:

// 必须先定义i、j,才能使用out参数
 int i = 0;
 int j = 0;
 this.DoNoting(out i, out j);
 Console.WriteLine($"i+j={i+j}");

在C#7.0中,可以不用先定义,就能够直接使用了:

this.DoNoting(out int x, out int y);
 Console.WriteLine($"x+y={x + y}");
 this.DoNoting(out var l, out var m);

结果:

img

二、模式

复制代码
  /// <summary>
 /// 具有模式的 IS 表达式
 /// </summary>
 /// <param name="o"></param>
 public void PrintStars(object o)
 {
       if (o is null) return;     // 常量模式 "null"
      if (!(o is int i)) return; // 类型模式 定义了一个变量 "int i" i的值就是o的值 相当于is类型判断
       Console.WriteLine($"i={i}");
 }
复制代码

使用方法:

1 this.PrintStars(null);
2 this.PrintStars(3);

结果:

img

除了可以像上面那样使用外,还可以使用下面的方式:

复制代码
 1 private void Switch(string text)
 2 {
 3             int k = 100;
 4             switch (text)
 5             {
 6                 case "Tom" when k > 10:   // text="Tom"且k<10才会输出Tom
 7                     Console.WriteLine("Tom");
 8                     break;
 9                 case "Joe" when text.Length < 10:  //text="Joe"且text的长度<10才会输出Joe
10                     Console.WriteLine("Joe");
11                     break;
12                 case string s when s.Length > 7://模式 定义变量s,s就是text的值
13                     Console.WriteLine(s);
14                     break;
15                 default:
16                     Console.WriteLine("default");
17                     break;
18                 case null:
19                     Console.WriteLine("null");
20                     break;
21             }
22 }
复制代码

调用:

1 this.Switch(null);
2 this.Switch("TomTomKevin");
3 this.Switch("JoeForest");

img

三、元组

先来看下面的两个方法:

复制代码
 1 /// <summary>
 2 /// 使用默认参数名称
 3 /// </summary>
 4 /// <param name="id"></param>
 5 /// <returns></returns>
 6 private (string, string, string) LookupName(long id) // tuple return type
 7 {
 8       return ("first", "middle", "last");
 9 }
10 
11 /// <summary>
12 /// 不使用默认参数名称
13 /// </summary>
14 /// <param name="id"></param>
15 /// <returns></returns>
16 private (string first, string middle, string last) LookupNameByName(long id) // tuple return type
17 {
18      return ("first", "middle", "last");
19      //return (first: "first", middle: "middle", last: "last");
20 }
复制代码

调用:

复制代码
 1 // 使用默认参数名称:Item1、Item2、Item3
 2 Console.WriteLine($"使用默认参数名称");
 3 var result = this.LookupName(1);
 4 Console.WriteLine(result.Item1);
 5 Console.WriteLine(result.Item2);
 6 Console.WriteLine(result.Item3);
 7 // 不使用默认参数名称
 8 Console.WriteLine($"不使用默认参数名称");
 9 var result2 = this.LookupNameByName(1);
10 Console.WriteLine(result2.first);
11 Console.WriteLine(result2.middle);
12 Console.WriteLine(result2.last);
13 // 也可以使用默认参数名称
14 Console.WriteLine($"也可以使用默认参数名称");
15 Console.WriteLine(result2.Item1);
16 Console.WriteLine(result2.Item2);
17 Console.WriteLine(result2.Item3);
复制代码

结果:

img

四、数字分割

如果数字太长,可以按照一定的位数用“_”进行分割,例如:

1 long big = 100_000_0000;
2 Console.WriteLine($"big={big}");

 五、本地方法

复制代码
  public static string LocalFunction(string name)
        {
            return ZhxiToString(name);

            string ZhxiToString(string name)
            {
                return name;
            }
        }
复制代码

六、已扩展 expression bodied 成员

复制代码
    public class ExpressionBodied
    {
        public ExpressionBodied(string label) => this.Label = label;

        ~ExpressionBodied() => Console.Error.WriteLine("Finalized!");
        private string label;

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


        /// <summary>
        /// C#6
        /// </summary>
        public void Show1() => Console.WriteLine("朝夕教育--C#6的语法");

        /// <summary>
        /// C#6
        /// </summary>
        /// <returns></returns>
        public string Show2() => 123456.ToString();
    }
复制代码

七、default 默认文本表达式

 Func<string, bool> whereClause = default(Func<string, bool>);
 Func<string, bool> whereClause1 = default;

八、throw 表达式

string[] args1 = new string[] { "zhaoxi01", "zhaoxi02", "zhaoxi03" };
                string arg = args1.Length >= 4 ? args1[0] :
                                 throw new ArgumentException("You must supply an argument");

九、命名实参

复制代码
   public static void PrintOrderDetails(string sellerName, int orderNum, string productName)
        {
            if (string.IsNullOrWhiteSpace(sellerName))
            {
                throw new ArgumentException(message: "Seller name cannot be null or empty.", paramName: nameof(sellerName));
            }

            Console.WriteLine($"Seller: {sellerName}, Order #: {orderNum}, Product: {productName}");
        }
复制代码
复制代码
            //  该方法可以通过使用位置参数以正常方式调用。
                PrintOrderDetails("Gift Shop", 31, "Red Mug");

                // 可以按任何顺序为参数提供命名参数。
                PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");
                PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);

                // 与位置参数混合的命名参数有效 
                // 只要它们被使用在正确的位置。
                PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");
                PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug");    // C# 7.2 onwards
                PrintOrderDetails("Gift Shop", orderNum: 31, "Red Mug");                   // C# 7.2 onwards
复制代码

 

 

C# 6.0

一、自动属性初始化

在以前的C#版本中,属性是这样写的:

1 public int Id { get; set; }
2 public string Name { get; set; }

在C#6.0中,属性可以自动赋初始值,例如:

1 public string Name { get; set; } = "summit";
2 public int Age { get; set; } = 22;
3 public DateTime BirthDay { get; set; } = DateTime.Now.AddYears(-20);
4 public IList<int> AgeList
5 {
6       get;
7       set;
8 } = new List<int> { 10, 20, 30, 40, 50 };

二、导入静态类

我们都知道,使用静态类的方法时是使用类名.方法名的形式,例如:

1 Console.WriteLine($"之前的使用方式: {Math.Pow(4, 2)}");

这里的Math是框架自带的静态类,要使用Pow()方法,必须要向上面的代码一样。在C#6.0中可以用下面的方式使用静态类的方法,例如:

1、使用using static导入静态类

img

2、导入静态类以后,可以像使用普通方法一样,直接使用,例如:

1 Console.WriteLine($"之前的使用方式: {Math.Pow(4, 2)}");
2 Console.WriteLine($"导入后可直接使用方法: {Pow(4, 2)}");

三、字符串内插

在以前版本中,如果要对字符串就行格式化,要使用string.Format,例如:

1 Console.WriteLine(string.Format("当前时间:{0}",DateTime.Now.ToString()));

在C#6.0中,可以直接使用$进行字符串的嵌入了,例如:

1 Console.WriteLine(string.Format("当前时间:{0}",DateTime.Now.ToString()));
2 Console.WriteLine($"年龄:{this.Age}  生日:{this.BirthDay.ToString("yyyy-MM-dd")}");
3 Console.WriteLine($"年龄:{{{this.Age}}}  生日:{{{this.BirthDay.ToString("yyyy -MM-dd")}}}");
4 Console.WriteLine($"{(this.Age <= 22 ? "小鲜肉" : "老鲜肉")}"); 

结果:

img

四、空值运算符

我们都知道,null值是不能调用ToString()方法的,会报“未将对象引用设置到对象实例”的错误,例如:

1 string name = null;
2 Console.WriteLine(name.ToString());

结果:

img

在C#6.0中有了空值运算符,如果是null值也可以调用ToString()方法了,这时程序什么都不会输出,例如:

1 int? iValue = 10;
2 Console.WriteLine(iValue?.ToString());//不需要判断是否为空
3 string name = null;
4 Console.WriteLine(name?.ToString()); // 程序不会报错,也不会输出任何值
5 Console.WriteLine("空值运算符");

结果:

img

这样就不需要像以前一样先判断一下变量是不是null值了。

五、对象初始化器

在前面一篇文章中讲过一次C#语法糖,在那里讲过对象初始化器和集合初始化器,以前初始化字典集合要像下面的方式:

复制代码
 1 IDictionary<int, string> dictOld = new Dictionary<int, string>()
 2 {
 3         { 1,"first"},
 4         { 2,"second"}
 5 };
 6 // 循环输出
 7 foreach(KeyValuePair<int,string> keyValue in dictOld)
 8 {
 9         Console.WriteLine($"key:{keyValue.Key},value:{keyValue.Value}");
10 }
复制代码

结果:

img

在C#6.0中,可以通过索引的方式给字段进行初始化,例如:

复制代码
 1 IDictionary<int, string> dictNew = new Dictionary<int, string>()
 2 {
 3        [4] = "first",
 4        [5] = "second"
 5 };
 6 // 循环输出
 7 foreach (KeyValuePair<int, string> keyValue in dictNew)
 8 {
 9         Console.WriteLine($"key:{keyValue.Key},value:{keyValue.Value}");
10 }
复制代码

结果:

img

六、异常过滤器

在以前的版本中,捕获异常通常使用try{}catch(),只要是发生了异常,就会进入到catch里面,例如:

1 try
2 {
3         Int32.Parse("we");
4 }  
5 catch(Exception ex)
6 {
7         Console.WriteLine(ex);
8 }

结果:

img

在C#6.0中,可以设置在满足某种条件的情况下,才能进入异常捕获,例如:

复制代码
1 int exceptionValue = 10;
2 try
3 {
4        Int32.Parse("s");
5 }
6 catch (Exception e) when (exceptionValue > 1)//满足条件才进入catch
7 {
8        Console.WriteLine("catch");
9 }
复制代码

结果:

img

七、nameof表达式

我们以前使用过typeof,typeof()可以获取类型,nameof表达式和typeof一样的,例如:

1 Console.WriteLine(nameof(peopleTest)); //获取peopleTest这个字符串
2 Console.WriteLine(nameof(People));
3 Console.WriteLine(nameof(CSharpSix));

结果:

img

应用场景:打印日志的时候打印出参数的值,如果方法里面参数的名称改了,而日志里面写的还是原来参数的名称,这样就会导致不一致,使用了nameof可以保证日志里面的参数名称和方法里面的参数名称一致。

八、在属性/方法里面使用Lambda表达式

1 public string NameFormat => string.Format("姓名: {0}", "summit");
2 public void Print() => Console.WriteLine(Name);

在Main()方法里面调用:

1 CSharpSix six = new CSharpSix();
2 Console.WriteLine(six.NameFormat);
3 six.Print();

 

结果:

img

 九、只读自动属性

复制代码
    public class Student
    {
        public Student(string firstName, string lastName)
        {
            FirstName = firstName;
            LastName = lastName;
        }

       
        public string FirstName { get; }
        public string LastName { get; }

        //对只读属性只能通过构造函数对其初始化值
        //public void SetName(string firstName, string lastName)
        //{
        //    FirstName = firstName;
        //    LastName = lastName;
        //}
    }
复制代码

在main里初始化

            //1.只读自动属性
                {
                    Student student = new Student("Richard", "Richard01");
                    string fullName = student.ToString();
                    string fullName1 = student.FullName;
                }

C#语法系列源码,请进群下载

 

posted @   明志德道  阅读(794)  评论(0编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示