C# 实用语法特性

@Tomato

现在是 2021 年,相信 C# 7.0 以前的版本大家都应该没有什么问题,在这里我们主要讲解大家C# 7.0、8.0 以及 9.0 的语法特性。考虑到文章篇幅有限,这里选取的都是博主个人比较喜欢的语法特性

C# 7.0

1、元组和弃元

元组:这个概念乍听起来可能会有一点陌生,其实,按我的理解,这就是增强的元组语法,终于可以摆脱Item1、Item2…啦:

(string AB, string AC) AAA = (AB: "Tomato", AC: "番茄");
Console.WriteLine($"{AAA.AB}, {AAA.AC}");  //(Tomato, 番茄)

(int count, string label) BBB = (777, "Tomato");
Console.WriteLine(BBB);   //(777, Tomato)


private static (string, double) GetTomato()
{
    string name = "Tomato";
    double age = 23;
    return (name, age);
}
(string name, double age) = GetTomato();
Console.WriteLine($"{name},({age})"); // Tomato,(23)

弃元:可以使用下划线_来表示要舍弃的元,是为弃元

private static (string, double) GetTomato()
{
    string name = "Tomato";
    double age = 23;
    return (name, age);
}

(string name, _) = GetTomato();
Console.WriteLine($"{name}"); //Tomato



2、更多的 expression-bodied 成员

这部分同样是经过强化的 Lambda 表达式,之前我们可以在成员函数和 只读属性上使用 Lambda 表达式,而现在,我们可以将其运用在构造函数、终结器以及 get和set访问器:

internal class Program
{
    private string name;
    public string Name
    {
        get => name;
        set => this.name = value ?? "No Name";
    }
    public Program(string name) => this.Name = name;

    ~Program() => Console.Error.WriteLine("Finalized!");

    private static void Main(string[] args)
    {
        Program AA = new Program("Tomato");
        Console.WriteLine(AA.Name);
        // Tomato
        // Finalized

    }
}



3、out变量不用再单独声明

out变量不用再单独声明

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



4、模式匹配

主要是针对 is 和 switch 语句提供了增强的语法。在这里,对于前者来说,我们可以将判断和赋值两个步骤合二为一:

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

//new 
public static double ComputeArea_Version3(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}



4、引发表达式

这个主要是针对 throw 关键字的增强

//场景A:条件运算符
string arg = args.Length >= 1 ? args[0] :
    throw new ArgumentException("You must supply an argument");
//场景B:Null合并运算符
public string Name
{
    get => name;
    set => name = value ??
        throw new ArgumentNullException(
          paramName: nameof(value), 
          message: "Name cannot be null");
}
//场景C:Lambda表达式
DateTime ToDateTime(IFormatProvider provider) =>
    throw new InvalidCastException("Conversion to a DateTime is not supported.");




C# 8.0

默认接口方法

所谓的 默认接口方法 指的是接口中定义了一个默认实现的方法, 如果实现该接口的类没有实现默认接口方法的话,那么这个 默认接口方法 只能从接口上进行访问,这是一个很有用的特性,因为它可以帮助开发人员在不破坏现有功能的情况下向接口的未来版本添加新方法。

public class ChineseSayHello : ISayHello
{
    public string Who { get; set; }
}

public interface ISayHello
{
    private const string DefaultPersopn = "Anumouse";
    string Who { get; set; }
    void SayHello()
    {
        Who = DefaultPersopn;
         Console.WriteLine($"Hello, {Who}");
    }
 } 
 
//在上面这个例子里,ChineseSayHello没有实现SayHello()方法不影响编译,因为ISayHello有默认实现,
//可正因为如此,SayHello()方法属于ISayHello,不属于ChineseSayHello:
//正确,可以编译
var sayHello = new ChineseSayHello() as ISayHello;
sayHello.SayHello();
//错误,无法编译
var sayHello = new ChineseSayHello();
sayHello.SayHello();




异步流

该特性可以看作是IEnumerable的一个延伸,即IAsyncEnumerable,主要有下面三个属性: 它是用 async 修饰符声明的。 它将返回 IAsyncEnumerable。 * 该方法包含用于在异步流中返回连续元素的 yield return 语句。

//生成异步流
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 (var number in GenerateSequence())
{
    Console.WriteLine(number);
}




索引和范围

类似python中的切片语法

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
};  
//取最后一个元素
Console.WriteLine($"The last word is {words[^1]}");
//获取第一个元素到第三个元素
var quickBrownFox = words[1..4];
//获取倒数第一个元素到倒数第二个元素
var lazyDog = words[^2..^0];
//获取全部元素
var all = words[..];
//获取开始到第三个元素
var firstPhrase = words[..4];
//获取结束到倒数第二个元素
var lastPhrase = words[6..];




C# 9.0

Record

record 是 C# 9.0 中提供的一个新的关键字,地位上等同于 class 和 struct,中文翻译为:记录类型。这是一种引用类型,它提供合成方法来提供值语义,从而实现相等性。 默认情况下,记录是不可变的。简而言之,record 是不可变的引用类型。

可以借鉴 DDD 中的实体 和 值对象这两个概念。实体 通常都有一个唯一的标识并且在整个生命周期中具有连续性,这一类角色通过 class 来实现一直都工作得很好。例如,每一个 User 都会有一个唯一的UserId ,我们使用 UserId 来判断其相等性。而 值对象 则是指那些没有唯一的标识、不可变的、通过属性来判断相等性。例如,我们有一个地址 Address,它由省、市、区、县和详细地址组成,那么,问题来了,如果两个 Address 的省、市、区、县和详细地址都相同,这两个 Address 是不是同一个地址呢?常识告诉我们:不会,因为它们是不同的实例。

record Address
{
    public string Province { get; set; }
    public string City { get; set; }
    public string District { get; set; }
    public string County { get; set; }
}

var addr1 = new Address() { Province = "陕西省", City = "西安市", District = "雁塔区" };
var addr2 = new Address() { Province = "陕西省", City = "西安市", District = "雁塔区" };
Console.WriteLine($"addr1 == addr2:{addr1 == addr2}");

//想想以前我们是怎么做的呢?是不是要写类似下面这样的代码:
if (addr1.Province == addr2.Province && addr1.City == addr2.City) {
    //属性太多啦,我就不一个一个地比较啦,懂得都懂
}

所以,这就是 record 存在的意义。除此之外呢,这个关键字更多的是语法层面上的,实际上从编译出来的 IL 来看,它本质上依然是一个类,并且它是不可变的。定义记录类型时,编译器会合成其他几种方法:

  1. 基于值的相等性比较方法
  2. 替代 GetHashCode()
  3. 复制和克隆成员
  4. PrintMembers 和 ToString()

那么,你可能还会有疑问,假如我定义了两个不同的记录类型,它们都拥有相同的属性成员,如果按值相等来判断的话,岂不是这两个不同的记录类型变成相同的了?这么重要的问题,微软怎么可能没有想到呢?编译器会合成一个 EqualityContract 属性,该属性返回与记录类型匹配的 Type 对象。在这里,微软再一次发挥了元组的威力,对于上面定义的地址,我们可以继续使用解构语法:

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

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

var person = new Person("Bill", "Wagner");
Person brother = person with { FirstName = "Paul" }; // 修改FirstName的副本
Person clone = person with { }; // 空集副本


(province, city, district, county) = addr1;



模式匹配增强

感觉微软在模式匹配的道路上越走越远啊,说好的语法糖呢?这简直是毒药,7.0 里面眼花缭乱的switch都还没学会呢!

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值