面试必备:C# 编程技术要点详解(一)、轻松应对技术拷问(细到每个版本,技术产生原因、好处、示例)
C# 版本更新要点详解
问:这篇文章有什么作用?
答:帮你包装C#面试所有的技术要点,新手读完轻松应对技术拷问。👇
引言
0.1 、新手面试的痛:从“Hello World”到“当场去世”
应届生小王第一次面试,被问:“C#里readonly
和const
啥区别?”他脑中闪过《孔乙己》的名场面——仿佛在考“茴香豆的‘茴’有几种写法”。结果支吾半天,面试官微笑送客。
真相:const
编译时定生死,readonly
运行时还能浪!记住:const
像刻石头的墓志铭,readonly
像可擦写遗嘱。
0.2、通关秘籍:三大“江湖暗器”助你反杀
1️⃣ 面向对象三大招:封装、继承、多态
面试官:说说多态?
你:“就像孙悟空七十二变——本质是猴,但能变土地庙、变鱼!虚方法virtual
是变身咒,override
是具体形态”[2]。
考点:继承是“子承父业”,封装是“金库上锁”,多态是“一人千面“。
2️⃣ 必考生死题:值类型 vs 引用类型
值类型(
int
,struct
)像现金——一手交钱一手交货;
引用类型(class
,string
)像支票——传递的是藏宝图,找到同一箱金子[5]。
陷阱题:string
虽是引用类型,但修改它会诞生“新支票”,旧支票作废!
3️⃣ .NET内功心法:GC、委托、LINQ
- 垃圾回收(GC):保洁阿姨定时扫内存,
IDisposable
是你主动倒垃圾[3]; - 委托(Delegate):像外卖平台——你点单(注册方法),骑手配送(触发事件);
- LINQ:数据库的“意念操控术”,一句
from...where...select
榨干数据[2]。
0.3、经典翻车现场拯救计划
场景1:面试官冷笑:“try-catch-finally
和直接throw
有啥区别?”
神回复:
“
try
是闯鬼屋,catch
是抓鬼法器,finally
是出门前必须交还钥匙——就算吓尿也得还!”[3]
场景2:对方甩出:“ASP.NET页面传值哪家强?”
满分答案:
“
QueryString
像漂流瓶,Session
像银行保险箱,ViewState
像加密日记本——但别用Application
传情书,全公司都能看!”
0.4、终极奥义:用“比尔·盖茨思维”拿Offer
在掌握技术要点基础,原理和来龙去脉,再结合生活进行形象应对,让面试官笑出腹肌,Offer便是你的囊中之物。
本文提炼50+企业核心真题,覆盖c#技术要点、产生原理,带来的好处,所能解决的问题。全方位解读,让你从“孔乙己”进化成“扫地僧”!
本文主要内容点
C# 1.0:类、结构、接口、事件、属性等基础特性
C# 2.0:泛型、分部类型、匿名方法、可空值类型等
C# 3.0:LINQ 相关特性,如 Lambda 表达式、扩展方法、匿名类型等
C# 4.0:动态绑定、命名参数和可选参数、泛型协变和逆变等
C# 5.0 的异步编程支持和调用方信息添加了引入原因和好处说明
C# 6.0 的字符串插值、表达式体成员和空条件运算符添加了详细的解释
C# 7.0 的元组和模式匹配增加了背景说明
C# 1.0 主要更新要点
C# 1.0 于 2002 年发布,是 C# 语言的第一个版本,随 .NET Framework 1.0 一起发布。
引入原因和好处:
C# 1.0 是微软创建的一种现代、面向对象的编程语言,旨在为 .NET 平台提供强大的开发工具。它结合了 C++、Java 等语言的优点,同时避免了它们的一些复杂性。
主要特性:
- 类(Classes) - 支持面向对象编程的基本构建块
- 结构(Structs) - 轻量级的数据结构
- 接口(Interfaces) - 定义契约的机制
- 事件(Events) - 支持发布-订阅模式
- 属性(Properties) - 封装字段访问的机制
- 委托(Delegates) - 类型安全的函数指针
- 运算符和表达式 - 丰富的运算符集合
- 语句(Statements) - 控制程序流程的结构
- 特性(Attributes) - 为代码元素添加元数据
C# 1.1/1.2 主要更新要点
C# 1.1/1.2 于 2003 年随 .NET Framework 1.1 发布,主要是对 C# 1.0 的小幅改进。
主要改进:
- 性能优化 - 对运行时和编译器进行了优化
- 错误修复 - 解决了 C# 1.0 中发现的问题
C# 2.0 主要更新要点
C# 2.0 于 2005 年随 .NET Framework 2.0 发布。
引入原因和好处:
随着应用程序变得越来越复杂,开发人员需要更强大的工具来管理代码复杂性。C# 2.0 引入了许多新特性,旨在提高开发人员的生产力和代码的可维护性。
解决的问题:
- 提高代码复用性和类型安全性
- 简化集合和委托的使用
- 增强泛型编程能力
主要特性:
1. 泛型(Generics)
好处:
- 提供编译时类型安全检查
- 减少装箱和拆箱操作,提高性能
- 增强代码复用性
示例代码:
// 旧方式
ArrayList list = new ArrayList();
list.Add(1);
list.Add("string"); // 允许添加不同类型,容易出错
int value = (int)list[1]; // 运行时可能出错
// 新方式
List<int> list = new List<int>();
list.Add(1);
// list.Add("string"); // 编译时错误
int value = list[0]; // 类型安全
2. 分部类型(Partial Types)
好处:
- 允许将一个类、结构或接口拆分到多个文件中
- 支持工具自动生成代码与手动编写代码分离
- 提高大型项目的可维护性
示例代码:
// 文件1: Person.cs
public partial class Person
{
public string FirstName { get; set; }
}
// 文件2: Person.Business.cs
public partial class Person
{
public string LastName { get; set; }
public string GetFullName() => $"{FirstName} {LastName}";
}
3. 匿名方法(Anonymous Methods)
好处:
- 简化事件处理程序和委托的编写
- 减少小型回调函数的代码量
示例代码:
// 旧方式
button.Click += new EventHandler(Button_Click);
// ... 在其他地方定义 Button_Click 方法
// 新方式
button.Click += delegate { MessageBox.Show("按钮被点击"); };
4. 可空值类型(Nullable Value Types)
好处:
- 允许值类型存储 null 值
- 解决数据库字段与 C# 类型映射问题
- 提高与数据库交互的便利性
示例代码:
int? age = null; // 可以为 null 的 int
if (age.HasValue)
{
Console.WriteLine($"年龄: {age.Value}");
}
else
{
Console.WriteLine("年龄未知");
}
5. 迭代器(Iterators)
好处:
- 简化 IEnumerable 和 IEnumerator 的实现
- 支持延迟执行
- 提高内存效率
示例代码:
public IEnumerable<int> GetEvenNumbers(int limit)
{
for (int i = 0; i <= limit; i += 2)
{
yield return i;
}
}
6. 其他特性
- getter/setter 单独可访问性 - 允许属性的 getter 和 setter 有不同的访问级别
- 静态类 - 无法实例化的类,只能包含静态成员
- 委托推断 - 简化委托创建语法
C# 3.0 主要更新要点
C# 3.0 于 2007 年随 .NET Framework 3.5 发布,主要为支持 LINQ(Language Integrated Query)而设计。
引入原因和好处:
随着数据驱动应用的兴起,开发人员需要一种统一的方式来查询各种数据源(内存集合、数据库、XML 等)。C# 3.0 引入了 LINQ 相关特性,使查询成为语言的一部分。
解决的问题:
- 提供统一的数据查询语法
- 提高数据访问代码的类型安全性
- 简化复杂数据操作
主要特性:
1. 自动实现的属性(Auto-Implemented Properties)
好处:
- 简化简单属性的声明
- 减少样板代码
示例代码:
// 旧方式
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
// 新方式
public string Name { get; set; }
2. 隐式类型局部变量(Implicitly Typed Local Variables)
好处:
- 简化复杂泛型类型的声明
- 提高代码可读性
示例代码:
// 旧方式
Dictionary<string, List<int>> data = new Dictionary<string, List<int>>();
// 新方式
var data = new Dictionary<string, List<int>>();
3. 对象和集合初始化器(Object and Collection Initializers)
好处:
- 简化对象创建和初始化过程
- 提高代码可读性
示例代码:
// 对象初始化器
var person = new Person { Name = "张三", Age = 25 };
// 集合初始化器
var numbers = new List<int> { 1, 2, 3, 4, 5 };
4. 匿名类型(Anonymous Types)
好处:
- 快速创建简单的数据传输对象
- 支持 LINQ 查询投影
示例代码:
var person = new { Name = "张三", Age = 25 };
Console.WriteLine(person.Name); // 输出: 张三
5. 扩展方法(Extension Methods)
好处:
- 为现有类型添加新方法而无需修改原始类型
- 提高代码组织性和可读性
示例代码:
public static class StringExtensions
{
public static string ToTitleCase(this string str)
{
if (string.IsNullOrEmpty(str))
return str;
return char.ToUpper(str[0]) + str.Substring(1).ToLower();
}
}
// 使用
string name = "zhang san";
Console.WriteLine(name.ToTitleCase()); // 输出: Zhang san
6. Lambda 表达式(Lambda Expressions)
好处:
- 提供更简洁的函数定义语法
- 支持函数式编程风格
示例代码:
// 旧方式
button.Click += delegate(object sender, EventArgs e) {
MessageBox.Show("按钮被点击");
};
// 新方式
button.Click += (sender, e) => MessageBox.Show("按钮被点击");
// 用于 LINQ
var evenNumbers = numbers.Where(n => n % 2 == 0);
7. LINQ 查询表达式(LINQ Query Expressions)
好处:
- 提供类似 SQL 的查询语法
- 统一各种数据源的查询方式
示例代码:
var query = from p in people
where p.Age > 18
orderby p.Name
select p;
// 等价于
var query = people
.Where(p => p.Age > 18)
.OrderBy(p => p.Name);
8. 表达式树(Expression Trees)
好处:
- 将代码表示为数据结构
- 支持动态查询构建和转换
示例代码:
Expression<Func<int, int>> expr = x => x * x;
// 可以在运行时分析或转换这个表达式
C# 4.0 主要更新要点
C# 4.0 于 2010 年随 .NET Framework 4 发布。
引入原因和好处:
随着应用程序需要与更多不同的系统(COM 组件、动态语言、Web 服务等)集成,C# 4.0 引入了动态编程能力,提高了与其他技术和语言的互操作性。
解决的问题:
- 简化 COM 互操作
- 提高与动态语言的互操作性
- 简化命名参数和可选参数的使用
主要特性:
1. 动态绑定(Dynamic Binding)
好处:
- 支持与动态语言(如 IronPython、IronRuby)的互操作
- 简化与 COM 对象的交互
- 提供更灵活的对象成员访问
示例代码:
dynamic obj = GetSomeObject();
obj.SomeMethod(); // 在运行时解析
obj.SomeProperty = "value"; // 在运行时解析
2. 命名参数和可选参数(Named and Optional Arguments)
好处:
- 提高方法调用的可读性
- 减少方法重载的需要
- 简化 API 使用
示例代码:
public void SendMessage(string to, string subject, string body = "", bool isImportant = false)
{
// 方法实现
}
// 使用命名和可选参数
SendMessage(to: "user@example.com", subject: "问候", isImportant: true);
3. 泛型协变和逆变(Generic Covariance and Contravariance)
好处:
- 提高泛型接口和委托的灵活性
- 支持更自然的类型转换
示例代码:
// 协变 - 允许派生类型替换基类型
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // C# 4.0 之前不允许
// 逆变 - 允许基类型替换派生类型
Action<object> objectAction = obj => Console.WriteLine(obj);
Action<string> stringAction = objectAction; // C# 4.0 之前不允许
4. 嵌入互操作类型(Embedded Interop Types)
好处:
- 简化部署,无需包含互操作程序集
- 减少程序集依赖
C# 5.0 主要更新要点
C# 5.0 于 2012 年发布,主要引入了两个重要特性:
1. 异步编程支持 (Async/Await)
这是 C# 5.0 最重要的特性,它极大地简化了异步编程。
引入原因和好处:
随着应用程序变得越来越复杂,特别是涉及网络请求、文件操作或数据库访问等I/O密集型操作时,异步编程变得至关重要。传统的异步编程模型(如回调或事件)难以编写和维护,容易导致"回调地狱"。async/await的引入旨在提供一种更自然、更易读的方式来编写异步代码,使异步代码看起来像同步代码一样。
解决的问题:
- 简化异步代码的编写和维护
- 避免阻塞UI线程,提高应用响应性
- 提高服务器应用程序的可伸缩性,能够处理更多并发请求
- 减少开发人员编写异步代码时的认知负担
示例代码:
// 传统异步编程方式
public Task<string> GetDataAsync()
{
return Task.Run(() => {
Thread.Sleep(2000); // 模拟耗时操作
return "数据获取完成";
});
}
// 使用 async/await 的现代方式
public async Task<string> GetDataModernAsync()
{
await Task.Delay(2000); // 模拟耗时操作
return "数据获取完成";
}
// 调用示例
public async void Button_Click()
{
// 不会阻塞 UI 线程
string result = await GetDataModernAsync();
Console.WriteLine(result); // 2秒后输出:数据获取完成
}
2. 调用方信息 (Caller Information)
提供获取调用方信息的方法,常用于日志记录。
引入原因和好处:
在调试和日志记录过程中,开发人员经常需要知道方法的调用位置信息。以前需要使用反射或堆栈跟踪来获取这些信息,这会影响性能。调用方信息特性提供了一种编译时机制,可以在不显著影响性能的情况下获取调用方的详细信息。
解决的问题:
- 简化日志记录和调试过程
- 提供高性能的方法调用信息获取方式
- 减少手动维护调用信息的工作量
- 增强诊断和监控能力
示例代码:
public void LogMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Console.WriteLine($"消息: {message}");
Console.WriteLine($"方法: {memberName}");
Console.WriteLine($"文件: {sourceFilePath}");
Console.WriteLine($"行号: {sourceLineNumber}");
}
public void DoWork()
{
LogMessage("执行任务");
// 输出:
// 消息: 执行任务
// 方法: DoWork
// 文件: C:\src\Program.cs
// 行号: 25
}
C# 6.0 主要更新要点
C# 6.0 于 2015 年发布,是一次增量更新,提供了多个简化开发的功能。
1. 字符串插值 (String Interpolation)
引入原因和好处:
String.Format方法虽然功能强大,但使用起来不够直观,需要记住参数的位置索引,容易出错。字符串插值提供了一种更直观、更易读的方式来格式化字符串,将变量直接嵌入到字符串中。
解决的问题:
- 提高字符串格式化的可读性和可维护性
- 减少因参数位置错误导致的bug
- 简化复杂字符串的构建过程
- 提供编译时检查,避免运行时格式错误
示例代码:
// 旧方式
string name = "张三";
int age = 25;
string message = string.Format("姓名: {0}, 年龄: {1}", name, age);
// 新方式
string message2 = $"姓名: {name}, 年龄: {age}";
2. 表达式体成员 (Expression-bodied members)
引入原因和好处:
对于简单的属性和方法,传统的花括号语法显得冗长。表达式体成员提供了一种更简洁的语法来定义只包含单个表达式的成员,使代码更加紧凑和易读。
解决的问题:
- 减少简单属性和方法的代码冗余
- 提高代码的可读性和简洁性
- 更好地表达简单的getter属性和单行方法的意图
- 与函数式编程风格保持一致
示例代码:
// 旧方式
public class Person
{
private string firstName;
private string lastName;
public string FullName
{
get { return $"{firstName} {lastName}"; }
}
public string GetInfo()
{
return $"姓名: {FullName}";
}
}
// 新方式
public class Person
{
private string firstName;
private string lastName;
public string FullName => $"{firstName} {lastName}";
public string GetInfo() => $"姓名: {FullName}";
}
// C# 6.0 还支持只读属性的表达式体
public class MathHelper
{
public double Pi => Math.PI;
public double E => Math.E;
}
3. 空条件运算符 (Null-conditional operators)
引入原因和好处:
在面向对象编程中,空引用异常是最常见的运行时错误之一。传统的空检查需要多层嵌套的if语句,代码冗长且容易遗漏。空条件运算符提供了一种简洁的方式来安全地访问可能为null的对象成员。
解决的问题:
- 简化空引用检查代码
- 减少NullReferenceException异常的发生
- 提高代码的可读性和安全性
- 减少样板代码的数量
示例代码:
// 旧方式
if (person != null && person.Address != null)
{
string city = person.Address.City;
}
// 新方式
string city = person?.Address?.City;
// 更多示例
public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
public List<string> Hobbies { get; set; }
}
public class Address
{
public string City { get; set; }
public string Street { get; set; }
}
// 安全访问深层属性
string city = person?.Address?.City;
// 安全调用方法
int length = person?.Name?.Length ?? 0;
// 安全访问集合
int hobbyCount = person?.Hobbies?.Count ?? 0;
// 安全调用委托
someDelegate?.Invoke();
// 结合空合并运算符
string displayName = person?.Name ?? "未知用户";
4. 异常筛选器 (Exception Filters)
引入原因和好处:
传统的异常处理只能基于异常类型进行捕获,无法在catch块中添加条件判断。异常筛选器允许在catch子句中添加条件表达式,只有满足条件时才捕获异常。
解决的问题:
- 提供更精确的异常处理控制
- 减少不必要的异常处理代码
- 保持异常堆栈信息的完整性
- 提高异常处理的性能
示例代码:
// 旧方式
try
{
// 可能抛出异常的代码
DoSomething();
}
catch (WebException ex)
{
// 检查是否是特定错误
if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotFound)
{
// 处理404错误
HandleNotFound();
}
else
{
// 重新抛出异常
throw;
}
}
// 新方式
try
{
// 可能抛出异常的代码
DoSomething();
}
catch (WebException ex) when (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotFound)
{
// 只捕获404错误
HandleNotFound();
}
// 更多示例
try
{
ProcessData();
}
catch (Exception ex) when (ex.Message.Contains("临时错误"))
{
// 只处理包含"临时错误"的消息
await Task.Delay(1000);
RetryProcess();
}
catch (Exception ex) when (Environment.IsDevelopment())
{
// 只在开发环境中捕获所有异常
LogDetailedError(ex);
throw;
}
5. nameof 表达式
引入原因和好处:
在需要使用标识符名称的场景中(如参数验证、属性更改通知等),硬编码字符串容易出错且难以维护。nameof表达式提供了一种编译时安全的方式来获取标识符的名称。
解决的问题:
- 提供编译时安全的标识符名称获取
- 减少因重命名导致的字符串不匹配错误
- 提高代码重构的安全性
- 简化参数验证等场景的代码
示例代码:
// 旧方式
public void SetName(string name)
{
if (name == null)
throw new ArgumentNullException("name"); // 硬编码字符串,容易出错
}
// 新方式
public void SetName(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name)); // 编译时安全
}
// 在属性更改通知中的应用
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name)); // 重构安全
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// 在数据绑定中的应用
public class ViewModel
{
public ICommand SaveCommand { get; }
public ViewModel()
{
SaveCommand = new RelayCommand(Save, () => CanSave)
.ObservesProperty(() => nameof(Name)) // 使用 nameof
.ObservesProperty(() => nameof(Email));
}
}
C# 7.0 主要更新要点
C# 7.0 于 2017 年随 Visual Studio 2017 发布。
1. 元组 (Tuples)
引入原因和好处:
在很多场景中,我们需要从方法返回多个值。以前可以使用out参数、创建自定义类或使用System.Tuple,但这些方法都有各自的缺点。C# 7.0的元组提供了更简洁、更易用的解决方案。
解决的问题:
- 简化多值返回的语法
- 提供更好的命名支持,提高代码可读性
- 减少创建简单数据传输类的需求
- 支持解构,使元组使用更加灵活
示例代码:
// 旧方式
public Tuple<string, int> GetPersonInfo()
{
return Tuple.Create("张三", 25);
}
var person = GetPersonInfo();
string name = person.Item1;
int age = person.Item2;
// 新方式
public (string Name, int Age) GetPersonInfo()
{
return ("张三", 25);
}
var (name, age) = GetPersonInfo();
// 更多示例
// 作为返回值
public (bool Success, string Message, int ErrorCode) TryProcessData(string input)
{
if (string.IsNullOrEmpty(input))
return (false, "输入不能为空", 1001);
// 处理数据
return (true, "处理成功", 0);
}
// 使用
var (success, message, errorCode) = TryProcessData("some data");
if (success)
{
Console.WriteLine($"成功: {message}");
}
else
{
Console.WriteLine($"失败: {message} (错误码: {errorCode})");
}
// 作为局部变量
(string firstName, string lastName) = ("张", "三");
// 丢弃不需要的值
var (firstName, _) = GetPersonInfo(); // 只关心名字,不关心年龄
// 作为字段
public class DataProcessor
{
private (string Status, DateTime LastUpdate) _statusInfo;
public void UpdateStatus(string status)
{
_statusInfo = (status, DateTime.Now);
}
}
2. 模式匹配 (Pattern Matching)
引入原因和好处:
传统的类型检查和转换需要使用is运算符和强制转换,代码冗长且容易出错。模式匹配提供了一种更安全、更直观的方式来检查对象类型并提取其值。
解决的问题:
- 简化类型检查和转换代码
- 提高代码的安全性和可读性
- 减少强制类型转换带来的运行时错误
- 支持更复杂的匹配逻辑,如带条件的匹配
示例代码:
// is 表达式模式匹配
public void ProcessObject(object obj)
{
if (obj is string s)
{
Console.WriteLine($"字符串长度: {s.Length}");
}
else if (obj is int i && i > 0)
{
Console.WriteLine($"正整数: {i}");
}
else if (obj is IEnumerable<int> numbers)
{
Console.WriteLine($"数字集合包含 {numbers.Count()} 个元素");
}
}
// switch 模式匹配
public string GetDiscount(object customer)
{
switch (customer)
{
case PremiumCustomer p when p.Years > 5:
return "20%";
case PremiumCustomer p:
return "15%";
case BasicCustomer b when b.Age > 65:
return "10%";
case null:
throw new ArgumentNullException(nameof(customer));
default:
return "5%";
}
}
// 常量模式
public string AnalyzeValue(object value)
{
switch (value)
{
case 0:
return "零";
case 1:
case 2:
return "小数字";
case int i when i < 0:
return "负数";
case string s when s.Length > 10:
return "长字符串";
case null:
return "空值";
default:
return "其他";
}
}
3. 局部函数 (Local Functions)
引入原因和好处:
有时我们需要在方法内部定义只在该方法中使用的辅助函数。局部函数提供了一种将这些辅助逻辑封装在方法内部的方式,同时保持代码的清晰性和封装性。
解决的问题:
- 提供更好的代码组织方式
- 保持辅助函数的局部性,避免污染类型接口
- 支持递归逻辑的实现
- 提高代码的可读性和维护性
示例代码:
public string ProcessTree(TreeNode root)
{
// 局部函数
string Traverse(TreeNode node)
{
if (node == null)
return string.Empty;
var result = node.Value.ToString();
foreach (var child in node.Children)
{
result += ", " + Traverse(child); // 递归调用
}
return result;
}
return Traverse(root);
}
// 异步局部函数
public async Task<string> ProcessDataAsync()
{
async Task<string> DownloadAndProcessAsync(string url)
{
var data = await DownloadDataAsync(url);
return ProcessData(data);
}
var result1 = await DownloadAndProcessAsync("http://example.com/data1");
var result2 = await DownloadAndProcessAsync("http://example.com/data2");
return $"{result1} | {result2}";
}
// 与迭代器结合使用
public IEnumerable<int> GetFibonacci(int count)
{
IEnumerable<int> Generate()
{
int a = 0, b = 1;
for (int i = 0; i < count; i++)
{
yield return a;
(a, b) = (b, a + b);
}
}
return Generate();
}
4. Out 变量 (Out Variables)
引入原因和好处:
传统的out参数需要在调用方法前声明变量,代码显得冗余。Out变量允许在方法调用时直接声明变量,使代码更加简洁。
解决的问题:
- 简化out参数的使用
- 减少变量声明的冗余代码
- 提高代码的可读性
- 支持隐式类型推断
示例代码:
// 旧方式
int result;
if (int.TryParse("123", out result))
{
Console.WriteLine($"解析成功: {result}");
}
// 新方式
if (int.TryParse("123", out int result))
{
Console.WriteLine($"解析成功: {result}");
}
// 使用 var 关键字
if (int.TryParse("123", out var result))
{
Console.WriteLine($"解析成功: {result}");
}
// 在更复杂的场景中
public bool ProcessData(string input, out ProcessedData data)
{
data = null;
if (string.IsNullOrEmpty(input))
return false;
// 处理数据
data = new ProcessedData { Value = input.ToUpper() };
return true;
}
// 使用
if (ProcessData("some data", out var processedData))
{
Console.WriteLine($"处理结果: {processedData.Value}");
}
C# 7.1 主要更新要点
C# 7.1 于 2017 年随 Visual Studio 2017 version 15.3 发布。
1. 异步 Main 方法 (Async Main)
引入原因和好处:
在控制台应用程序中,如果需要执行异步操作,以前需要在Main方法中使用GetAwaiter().GetResult()或Wait(),这可能导致死锁或其他问题。异步Main方法允许Main方法直接使用async修饰符。
解决的问题:
- 简化控制台应用程序中的异步编程
- 避免异步操作中的死锁问题
- 提供更自然的异步入口点
示例代码:
// 旧方式
static void Main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
await DoSomethingAsync();
}
// 新方式
static async Task Main(string[] args)
{
await DoSomethingAsync();
}
// 支持不同的返回类型
static async Task<int> Main(string[] args)
{
try
{
await DoSomethingAsync();
return 0; // 成功退出码
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
return 1; // 错误退出码
}
}
2. 默认表达式 (Default Expressions)
引入原因和好处:
在泛型代码中,需要获取类型的默认值时,以前需要使用default(T)语法。默认表达式简化了这一语法,特别是在编译器可以推断类型的情况下。
解决的问题:
- 简化默认值的获取语法
- 提高代码的可读性
- 减少冗余的类型指定
示例代码:
// 旧方式
public T GetDefaultValue<T>()
{
return default(T);
}
var value = default(int); // 需要指定类型
// 新方式
public T GetDefaultValue<T>()
{
return default; // 编译器推断类型
}
var value = default; // 在可以推断类型的地方使用
C# 7.2 主要更新要点
C# 7.2 于 2017 年随 Visual Studio 2017 version 15.5 发布。
1. Span 和内存相关类型
引入原因和好处:
在处理大量数据或需要高性能的场景中,传统的数组操作可能涉及不必要的内存分配和复制。Span
解决的问题:
- 提高内存操作的性能
- 减少不必要的内存分配
- 提供安全的栈内存操作
- 支持零拷贝数据处理
示例代码:
// 使用 Span<T> 处理数组片段
public void ProcessData(byte[] data)
{
// 创建指向数组一部分的 Span
Span<byte> span = data.AsSpan(10, 20); // 从索引10开始的20个元素
// 高效处理
for (int i = 0; i < span.Length; i++)
{
span[i] = (byte)(span[i] ^ 0xFF); // 简单的 XOR 操作
}
}
// 使用 stackalloc 和 Span
public void ProcessOnStack()
{
// 在栈上分配内存
Span<int> numbers = stackalloc int[100];
// 初始化
for (int i = 0; i < numbers.Length; i++)
{
numbers[i] = i;
}
// 使用
int sum = 0;
foreach (var number in numbers)
{
sum += number;
}
}
2. in 参数修饰符
引入原因和好处:
在需要传递大型结构体或只读数据时,按值传递会造成性能损失,而按引用传递(ref)可能不安全。in参数允许以只读引用的方式传递参数,既保证了安全性又提高了性能。
解决的问题:
- 提高大型结构体传递的性能
- 保证参数不会被方法修改
- 减少不必要的数据复制
示例代码:
// 定义一个大型结构体
public struct LargeStruct
{
public int Value1, Value2, Value3, Value4, Value5;
// 使用 in 参数的方法
public void ProcessData(in LargeStruct other)
{
// 可以读取 other 的值
int result = this.Value1 + other.Value1;
// 不能修改 other
// other.Value1 = 10; // 编译错误
}
}
// 调用示例
var s1 = new LargeStruct();
var s2 = new LargeStruct();
s1.ProcessData(s2); // s2 以只读引用传递
C# 7.3 主要更新要点
C# 7.3 于 2018 年随 Visual Studio 2017 version 15.7 发布。
1. 泛型约束的改进
引入原因和好处:
在泛型编程中,有时需要更精确地约束类型参数。C# 7.3 增强了泛型约束系统,提供了更多的约束选项。
解决的问题:
- 提供更精确的泛型类型约束
- 支持更灵活的泛型编程模式
- 减少运行时类型检查的需要
示例代码:
// 枚举约束
public static T ParseEnum<T>(string value) where T : Enum
{
return Enum.Parse<T>(value);
}
// 委托约束
public static void InvokeDelegate<T>(T del, object[] args) where T : Delegate
{
del.DynamicInvoke(args);
}
// 非托管类型约束
public static unsafe void ProcessUnmanaged<T>(T* ptr) where T : unmanaged
{
// 只能用于非托管类型(没有引用类型的结构体)
}
// 构造函数约束的改进
public class Factory<T> where T : new()
{
public T CreateInstance()
{
return new T(); // C# 7.3 改进了构造函数约束的行为
}
}
2. 表达式变量的改进
引入原因和好处:
C# 7.0 引入的表达式变量(out变量等)在某些场景下使用受限。C# 7.3 扩展了这些变量的使用范围。
解决的问题:
- 提供更灵活的表达式变量使用场景
- 减少临时变量的声明
- 提高代码的简洁性
示例代码:
// 在字段初始化器中使用
public class MyClass
{
// C# 7.3 允许在字段初始化器中使用表达式变量
private static readonly bool _isValid = int.TryParse("123", out var result) && result > 0;
public MyClass()
{
// 在构造函数中使用
if (int.TryParse("456", out var localResult))
{
// 使用 localResult
}
}
}
重要参考网址,官方文档:https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history
下一篇继续聊 C# 8.0 -- C#13