面试必备:C# 编程技术要点详解(二)、轻松应对技术拷问(细到原理,动机、好处、示例)

衔接上一篇文章:text

面试如闯关,装备要精良。

本文核心要义,是让你能够深刻理解C#各个版本的技术要点,从而在面试时,能够游刃有余地回答面试官的各种刁钻问题。同时,当你对技术要点有深刻理解后,有一个全局的把控,从而在面试和工作开发中,能够更好地选择技术特性,给出更加优雅的方案。

本文主要内容点

C# 8.0 - 可空引用类型、异步流和默认接口方法

C# 9.0 - 记录和模式匹配增强

C# 10.0 - 文件范围命名空间声明和全局 Using 指令

C# 11.0 - 泛型属性和 Required 修饰符

C# 12.0 - 主构造函数、集合字面量、内联数组和实验性状态

C# 13.0 - 字段关键字、新的锁类型和参数集合

C# 14.0 - 部分构造函数和事件、Null 条件赋值

C# 8.0 主要更新要点

C# 8.0 于 2019 年随 .NET Core 3.0 和 Visual Studio 2019 发布。

1. 可空引用类型 (Nullable Reference Types)

引入原因和好处:

空引用被其发明者称为"十亿美元的错误"。可空引用类型是C#在类型安全方面的一次重大改进,通过静态分析帮助开发人员在编译时发现潜在的空引用异常。

解决的问题:

  1. 在编译时检测潜在的NullReferenceException
  2. 明确表达API的意图(哪些参数/返回值可以为null)
  3. 减少运行时的空引用异常
  4. 提高代码质量和可靠性

示例代码:


// 启用可空引用类型后

string name;        // 不可为空

string? nickname;   // 可以为 null



name = null;        // 编译器警告

nickname = null;    // 正常



// 安全访问

if (nickname != null)

{

    int length = nickname.Length; // 安全访问
}



// 使用空合并运算符

int length = nickname?.Length ?? 0;



// 在API设计中的应用

public class Person

{

    public string FirstName { get; }  // 不可为空
    public string? MiddleName { get; } // 可以为空
    public string LastName { get; }   // 不可为空
    
    // 构造函数中确保非空属性被正确初始化
    public Person(string firstName, string lastName, string? middleName = null)
    {
        FirstName = firstName ?? throw new ArgumentNullException(nameof(firstName));
        LastName = lastName ?? throw new ArgumentNullException(nameof(lastName));
        MiddleName = middleName; // 可以为 null
    }
}

2. 异步流 (Async Streams)

引入原因和好处:

传统的迭代模式(IEnumerable)是同步的,无法很好地处理需要异步获取的数据源。异步流结合了异步编程和迭代模式的优点,适用于需要逐步异步获取数据的场景。

解决的问题:

  1. 支持异步数据源的迭代处理
  2. 提高处理大量数据时的内存效率
  3. 在数据到达时逐步处理,而不是等待所有数据准备就绪
  4. 支持实时数据流处理

示例代码:


// 异步返回多个值

public async IAsyncEnumerable<int> GetNumbersAsync()

{

    for (int i = 0; i < 10; i++)
    {
        await Task.Delay(1000);
        yield return i;
    }
}



// 使用异步流

public async Task ProcessNumbersAsync()

{

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



// 实际应用场景:从API分页获取数据

public async IAsyncEnumerable<Product> GetProductsAsync()

{

    int page = 1;
    bool hasMore = true;
    
    while (hasMore)
    {
        var response = await _httpClient.GetAsync($"/api/products?page={page}");
        var result = await response.Content.ReadFromJsonAsync<PagedResult<Product>>();
        
        foreach (var product in result.Items)
        {
            yield return product;
        }
        
        hasMore = result.HasMore;
        page++;
    }
}

3. 默认接口方法 (Default Interface Methods)

引入原因和好处:

在接口中添加新方法会破坏现有实现。默认接口方法允许在接口中提供方法的默认实现,从而在不破坏现有实现的情况下扩展接口。

解决的问题:

  1. 允许在不破坏现有实现的情况下扩展接口
  2. 提供接口方法的默认实现
  3. 支持接口的演化

示例代码:


public interface ICalculator

{

    int Add(int x, int y);
    
    // 默认实现
    int Multiply(int x, int y) => x * y;
    
    // 带有私有辅助方法的默认实现
    int Power(int baseValue, int exponent)
    {
        if (exponent == 0) return 1;
        if (exponent == 1) return baseValue;
        return PowerHelper(baseValue, exponent);
    }
    
    // 接口中的私有方法
    private int PowerHelper(int baseValue, int exponent)
    {
        int result = 1;
        for (int i = 0; i < exponent; i++)
        {
            result = Multiply(result, baseValue);
        }
        return result;
    }
}



// 实现类可以选择是否重写默认方法

public class BasicCalculator : ICalculator

{

    public int Add(int x, int y) => x + y;
    
    // 可以选择使用默认实现
    // public int Multiply(int x, int y) => x * y;
    
    // 也可以提供自己的实现
    public int Multiply(int x, int y) => x * y * 2; // 自定义实现
}

C# 9.0 主要更新要点

C# 9.0 于 2020 年随 .NET 5 发布。

1. 记录 (Records)

引入原因和好处:

在数据传输对象(DTO)和不可变数据类型场景中,开发人员需要编写大量样板代码来实现相等比较、哈希码计算和字符串表示。记录类型通过简洁的语法自动生成这些常用功能。

解决的问题:

  1. 大幅减少不可变数据类型的样板代码
  2. 提供基于值的相等比较而不是引用比较
  3. 自动实现常用的数据方法(ToString、Equals、GetHashCode)
  4. 支持非破坏性修改(with表达式)

示例代码:


// 旧方式定义不可变类

public class Person 

{

    public string FirstName { get; }
    public string LastName { get; }
    
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
    
    public override bool Equals(object obj) => // 复杂的相等比较实现
    public override int GetHashCode() => // 哈希码实现
    public override string ToString() => $"{FirstName} {LastName}";
}



// 新方式使用记录

public record Person(string FirstName, string LastName);



// 使用

var person1 = new Person("张", "三");

var person2 = new Person("张", "三");

bool equal = person1 == person2; // true - 基于值的相等比较

var person3 = person1 with { FirstName = "李" }; // 非破坏性修改



// 记录的继承

public record Student(string FirstName, string LastName, int Grade) 

    : Person(FirstName, LastName);


// with 表达式的应用

var student1 = new Student("王", "五", 90);

var student2 = student1 with { Grade = 95 }; // 只修改 Grade

2. 模式匹配增强 (Pattern Matching Enhancements)

引入原因和好处:

C# 8.0 引入了模式匹配,但功能有限。C# 9.0 进一步增强了模式匹配能力,使其更加强大和灵活。

解决的问题:

  1. 提供更丰富的模式匹配选项
  2. 简化复杂对象的解构和匹配
  3. 提高代码的表达能力

示例代码:


// 类型模式

public static decimal CalculateArea(object shape)

{

    return shape switch
    {
        Rectangle r => r.Width * r.Height,
        Circle c => Math.PI * c.Radius * c.Radius,
        Triangle t => 0.5 * t.Base * t.Height,
        null => throw new ArgumentNullException(nameof(shape)),
        _ => throw new ArgumentException("未知的形状类型", nameof(shape))
    };
}



// 关系模式

public static string GetAgeGroup(int age) => age switch

{

    < 18 => "未成年",
    >= 18 and < 60 => "成年人",
    >= 60 => "老年人",
    _ => "未知"
};



// 逻辑模式

public static string AnalyzeValue(int value) => value switch

{

    > 0 and <= 100 => "正常范围",
    > 100 or < 0 => "超出范围",
    0 => "零值",
    _ => "其他"
};



// 属性模式

public static bool IsUsAdult(Person person) => person switch

{

    { Age: >= 18, Country: "US" } => true,
    _ => false
};

C# 10.0 主要更新要点

C# 10.0 于 2021 年随 .NET 6 发布。

1. 文件范围命名空间声明 (File-scoped namespace declaration)

引入原因和好处:

传统的命名空间声明需要使用花括号,导致额外的缩进层级,降低了代码的可读性。文件范围命名空间声明简化了语法,减少了不必要的缩进。

解决的问题:

  1. 减少代码文件中的嵌套层级
  2. 提高代码的可读性和整洁性
  3. 减少花括号的使用,使代码更简洁
  4. 与现代代码风格保持一致

示例代码:


// 旧方式

namespace MyApplication.Models

{

    public class Person
    {
        // 类内容
        public string Name { get; set; }
        
        public void DoWork()
        {
            // 方法内容
        }
    }
}



// 新方式

namespace MyApplication.Models;



public class Person

{

    public string Name { get; set; }
    
    public void DoWork()
    {
        // 方法内容
    }
}

2. 全局 Using 指令 (Global using directives)

引入原因和好处:

在大型项目中,许多using指令在多个文件中重复出现,增加了代码冗余。全局using指令允许在一处定义常用的命名空间,减少重复代码。

解决的问题:

  1. 减少项目中重复的using指令
  2. 简化单个代码文件的头部声明
  3. 更好地管理项目级别的命名空间引用
  4. 提高代码的一致性和可维护性

示例代码:


// 在 Program.cs 或单独的 Imports.cs 文件中

global using System.Text;

global using System.Net.Http;

global using System.Text.Json;



// 在其他文件中,无需再次声明 using System.Text;



// 可以使用别名

global using static System.Console;

global using Env = System.Environment;



// 在其他文件中直接使用

WriteLine("Hello World"); // 来自 System.Console

var machineName = Env.MachineName; // 来自 System.Environment

C# 11.0 主要更新要点

C# 11.0 于 2022 年随 .NET 7 发布。

1. 泛型属性 (Generic attributes)

引入原因和好处:

在C# 11之前,属性类不能接受泛型类型参数,这限制了属性的灵活性和复用性。泛型属性允许创建更通用、更灵活的属性类。

解决的问题:

  1. 提高属性类的灵活性和复用性
  2. 减少为不同类型的值创建重复的属性类
  3. 提供类型安全的属性参数传递
  4. 支持更复杂的元数据场景

示例代码:


public class GenericAttribute<T> : Attribute

{

    public T Value { get; }
    
    public GenericAttribute(T value)
    {
        Value = value;
    }
}



// 使用泛型属性

[GenericAttribute<string>("示例值")]

[GenericAttribute<int>(42)]

public class MyClass 

{

    // 类内容
}



// 在运行时获取泛型属性

var attributes = typeof(MyClass).GetCustomAttributes();

foreach (var attr in attributes)

{

    if (attr is GenericAttribute<string> stringAttr)
    {
        Console.WriteLine($"字符串值: {stringAttr.Value}");
    }
    else if (attr is GenericAttribute<int> intAttr)
    {
        Console.WriteLine($"整数值: {intAttr.Value}");
    }
}

2. Required 修饰符

引入原因和好处:

对象初始化时遗漏必需属性是常见的错误来源。Required修饰符通过编译时检查确保对象初始化时设置所有必需的属性。

解决的问题:

  1. 在编译时强制检查必需属性的初始化
  2. 减少运行时因缺少必需属性导致的错误
  3. 明确表达对象初始化的约束条件
  4. 提高API的可用性和健壮性

示例代码:


public class Person

{

    public required string FirstName { get; set; }
    public required string LastName { get; set; }
    public int Age { get; set; } // 非必需
    
    // 构造函数中也可以初始化 required 属性
    public Person(string firstName)
    {
        FirstName = firstName;
        // LastName 仍然需要在对象初始化器中设置
    }
}



// 使用

var person = new Person 

{ 

    FirstName = "张", 
    LastName = "三" 
    // Age 不是必需的,可以不设置
};



// 编译错误示例

// var person2 = new Person(); // 编译错误:缺少 FirstName 和 LastName

// var person3 = new Person { FirstName = "李" }; // 编译错误:缺少 LastName

C# 12.0 主要更新要点

C# 12.0 于 2023 年随 .NET 8 发布。

1. 主构造函数 (Primary Constructors)

引入原因和好处:

在类和结构体中,构造函数经常需要初始化只读字段或属性。主构造函数允许在类型声明中直接指定构造函数参数,简化了类的定义。

解决的问题:

  1. 减少样板代码,特别是对于简单的数据类
  2. 提高代码的可读性和简洁性
  3. 简化字段和属性的初始化过程

示例代码:


// 旧方式

public class Person

{

    private readonly string firstName;
    private readonly string lastName;
    private readonly int age;
    
    public Person(string firstName, string lastName, int age)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    
    public string FirstName => firstName;
    public string LastName => lastName;
    public int Age => age;
}



// 新方式 - 使用主构造函数

public class Person(string firstName, string lastName, int age)

{

    public string FirstName { get; } = firstName;
    public string LastName { get; } = lastName;
    public int Age { get; } = age;
    
    public string FullName => $"{firstName} {lastName}";
}



// 结构体也可以使用主构造函数

public struct Point(int x, int y)

{

    public int X { get; } = x;
    public int Y { get; } = y;
}

2. 集合字面量 (Collection Literals)

引入原因和好处:

创建集合对象并初始化其内容是一个常见操作,但语法相对冗长。集合字面量提供了一种更简洁的语法来创建和初始化集合。

解决的问题:

  1. 简化集合的创建和初始化语法
  2. 提高代码的可读性和简洁性
  3. 统一不同类型集合的初始化语法

示例代码:


// 旧方式

var numbers = new List<int> { 1, 2, 3, 4, 5 };

var names = new string[] { "张三", "李四", "王五" };

var dictionary = new Dictionary<string, int> 

{ 

    { "one", 1 }, 
    { "two", 2 }, 
    { "three", 3 } 
};



// 新方式 - 使用集合字面量

var numbers = [1, 2, 3, 4, 5];

var names = ["张三", "李四", "王五"];

var dictionary = new Dictionary<string, int> { ["one"] = 1, ["two"] = 2, ["three"] = 3 };



// 可以直接使用接口类型

IEnumerable<int> enumerable = [1, 2, 3];

ICollection<string> collection = ["a", "b", "c"];

IList<int> list = [1, 2, 3];

3. 内联数组 (Inline Arrays)

引入原因和好处:

System.Runtime.CompilerServices.InlineArrayAttribute 允许创建固定大小的结构体数组,这些数组存储在栈上而不是堆上,提高了性能。

解决的问题:

  1. 提供高性能的固定大小数组
  2. 减少堆内存分配
  3. 提高数值计算和游戏开发等场景的性能

示例代码:


[System.Runtime.CompilerServices.InlineArray(10)]

public struct Buffer

{

    private int _element0;
}



// 使用

Buffer buffer = new();

for (int i = 0; i < 10; i++)

{

    buffer[i] = i * i;
}



foreach (var item in buffer)

{

    Console.WriteLine(item);
}

4. 实验性状态 (Experimental Attribute)

引入原因和好处:

允许开发者标记某些API为实验性,使用者需要添加特定特性才能使用这些API,避免在API稳定前被广泛使用。

解决的问题:

  1. 允许发布实验性功能
  2. 防止实验性API被意外广泛使用
  3. 提供明确的实验性标记

示例代码:


[Experimental("EXPERIMENTAL001")]

public void ExperimentalMethod()

{

    // 实验性方法实现
}



// 使用实验性方法需要添加相应的特性

[Experimental("EXPERIMENTAL001", UrlFormat = "https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-messages/experimental")]

public class MyClass

{

    public void UseExperimentalMethod()
    {
        ExperimentalMethod();
    }
}

C# 13.0 主要更新要点

C# 13.0 于 2024 年随 .NET 9 发布。

1. 字段关键字 (Field Keyword)

引入原因和好处:

在属性访问器中,有时需要访问编译器生成的后备字段。以前需要手动声明字段,现在可以使用[field]关键字直接访问。

解决的问题:

  1. 简化属性中后备字段的访问
  2. 减少手动声明字段的样板代码
  3. 提高代码的可读性和简洁性

示例代码:


// 旧方式

private string _name;

public string Name 

{ 

    get => _name; 
    set => _name = value ?? throw new ArgumentNullException(nameof(value)); 
}



// 新方式 - 使用 field 关键字

public string Name 

{ 

    get => field; 
    set => field = value ?? throw new ArgumentNullException(nameof(value)); 
}

2. 新的锁类型 (New Lock Type)

引入原因和好处:

引入了新的 System.Threading.Lock 类型,提供了更高效和安全的线程同步机制。

解决的问题:

  1. 提供更高效的线程同步机制
  2. 简化锁的使用方式
  3. 提高多线程程序的性能

示例代码:


// 使用新的 Lock 类型

private readonly Lock _lock = new();



public void ThreadSafeMethod()

{

    lock (_lock)
    {
        // 线程安全的代码
    }
}

3. 参数集合 (Params Collections)

引入原因和好处:

扩展了 params 关键字,支持更多的集合类型,而不仅仅是数组。

解决的问题:

  1. 提供更灵活的参数传递方式
  2. 支持更多类型的集合参数
  3. 提高API的易用性

示例代码:


// 支持 List<T> 等集合类型

public void ProcessItems(params List<string> items)

{

    foreach (var item in items)
    {
        Console.WriteLine(item);
    }
}



// 调用

ProcessItems(["item1", "item2", "item3"]);

C# 14.0 主要更新要点

C# 14.0 是即将发布的版本,随 .NET 10 发布。

1. 部分构造函数和事件 (Partial Constructors and Events)

引入原因和好处:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值