November 27, 20249 minutes
C#语言诞生于2000年,是由微软的Anders Hejlsberg领导的团队开发的一门编程语言。当时的背景是:
微软需要一个现代的、面向对象的编程语言来支持其.NET平台战略。虽然已有Visual Basic和C++等语言,但它们或多或少都存在一些局限性。 Java语言在企业开发领域获得了巨大成功,微软希望能够提供一个既保持Java优点、又能解决其不足的替代方案。
C#的核心设计理念包括:
简单性和现代性:语言语法简洁清晰,去除了C++中的一些复杂特性(如多重继承),同时又引入了许多现代编程特性。 面向对象:完全支持面向对象编程范式,包括封装、继承、多态等特性,并在语言层面强制使用对象。 类型安全:采用强类型系统,在编译时就能发现类型错误,提高代码的可靠性。 组件化:支持基于组件的开发,可以方便地创建和使用软件组件。 版本兼容:注重向后兼容性,新版本的改进不会破坏现有代码。 高效性能:虽然运行在托管环境中,但通过即时编译(JIT)等技术确保较高的运行效率。
C# 1.0从设计之初就将面向对象作为核心理念。它提供了完整的面向对象编程支持:
C# 1.0建立了统一且严格的类型系统:
依托于.NET Framework平台,C# 1.0提供了托管代码执行环境:
作为面向Windows平台开发的重要组成部分,C# 1.0提供了完整的Windows Forms支持:
泛型的引入是C# 2.0最重要的更新之一,它从根本上改变了开发者处理类型安全的方式:
分部类允许将一个类、结构或接口的定义分散到多个源文件中:
partial class Customer
{
public void Save() { ... }
}
partial class Customer
{
public void Load() { ... }
}可空类型的引入解决了值类型无法表示"无值"状态的问题:
int? nullableInt = null;
DateTime? nullableDate = null;
if (nullableInt.HasValue)
{
Console.WriteLine(nullableInt.Value);
}匿名方法为委托提供了更灵活的语法支持:
button.Click += delegate(object sender, EventArgs e)
{
MessageBox.Show("Button clicked!");
};迭代器极大地简化了集合类型的实现:
public IEnumerable<int> GetNumbers()
{
for (int i = 0; i < 10; i++)
{
yield return i;
}
}LINQ的引入彻底改变了.NET平台的数据查询方式:
var numbers = new[] { 1, 2, 3, 4, 5 };
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
// 或使用方法链语法
var evenNumbers = numbers.Where(n => n % 2 == 0)
.Select(n => n);Lambda表达式为C#带来了更简洁的函数式编程体验:
// 传统委托写法
delegate(int x) { return x * x; }
// Lambda表达式
x => x * x;
// 在LINQ中的应用
var squares = numbers.Select(x => x * x);扩展方法允许在不修改原有类型的情况下为其添加新方法:
public static class StringExtensions
{
public static int WordCount(this string str)
{
return str.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
// 使用扩展方法
string text = "Hello World";
int wordCount = text.WordCount(); // 就像是string类的原生方法一样大大简化了属性的声明语法:
允许快速定义仅用于临时存储数据的类型:
提供了更简洁的对象初始化语法:
动态绑定是C# 4.0最重要的新特性。通过引入dynamic关键字,C#能够支持动态类型编程,使得代码可以在运行时而不是编译时解析类型。这带来了几个重要优势:
开发者可以方便地与动态语言(如Python、Ruby)进行互操作 简化了与COM组件的交互 提供了更灵活的对象操作方式
dynamic obj = GetSomeObject();
obj.SomeMethod("parameter"); // 方法调用在运行时解析这个特性大大提升了方法调用的灵活性和代码的可读性:
public void ProcessOrder(string productName, int quantity = 1, bool express = false)
{
// 处理订单
}
// 调用方式
ProcessOrder(productName: "laptop", express: true); // quantity使用默认值1C# 4.0引入了泛型接口和委托的变体支持,通过out(协变)和in(逆变)关键字来实现:
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // 合法,string是object的子类
// 逆变示例
Action<object> actObject = ProcessObject;
Action<string> actString = actObject; // 合法,object是string的基类COM互操作性的改进 C# 4.0对COM互操作进行了显著改进:
dynamic excel = new Microsoft.Office.Interop.Excel.Application();
excel.Visible = true;
excel.Workbooks.Add();async和await关键字的引入是C# 5.0最具革命性的特性,它们从根本上改变了开发者编写异步代码的方式:
public async Task<string> DownloadDataAsync(string url)
{
using (var client = new HttpClient())
{
string result = await client.GetStringAsync(url);
return result;
}
}这种新的异步模式带来了plusieurs优势:
C# 5.0引入了一组新的特性,用于获取方法调用者的信息:
public void LogMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Console.WriteLine($"Message: {message}");
Console.WriteLine($"Member: {memberName}");
Console.WriteLine($"File: {sourceFilePath}");
Console.WriteLine($"Line: {sourceLineNumber}");
}这些特性主要包括:
CallerMemberName:获取调用方法的成员名称 CallerFilePath:获取源代码文件路径 CallerLineNumber:获取调用代码的行号
这些特性在日志记录、调试和诊断等场景中特别有用。
C# 5.0对异步编程模型进行了全面简化:
任务并行库(TPL)的整合:
public async Task ProcessItemsAsync(List<string> items)
{
foreach (var item in items)
{
await Task.Delay(100); // 模拟异步操作
await ProcessItemAsync(item);
}
}异步流程控制:
public async Task<int> GetTotalAsync()
{
var task1 = GetValueAsync(1);
var task2 = GetValueAsync(2);
// 并行执行多个异步任务
var results = await Task.WhenAll(task1, task2);
return results.Sum();
}异步I/O操作的简化:
public async Task SaveToFileAsync(string content)
{
using (StreamWriter writer = new StreamWriter("file.txt"))
{
await writer.WriteAsync(content);
}
}字符串插值引入了一种更直观的字符串格式化方式,使用$符号前缀和{}占位符:
// 传统的字符串格式化
string oldWay = string.Format("Hello, {0}! Today is {1}", name, date);
// 使用字符串插值
string newWay = $"Hello, {name}! Today is {date}";
// 可以在占位符中包含表达式
string message = $"Total price: {quantity * unitPrice:C2}";null条件运算符(也称为空传播运算符)提供了一种优雅的方式来处理可能为null的对象:
// 传统的null检查
string value = null;
if (customer != null && customer.Orders != null && customer.Orders.Count > 0)
{
value = customer.Orders[0].Description;
}
// 使用null条件运算符
string value = customer?.Orders?.FirstOrDefault()?.Description;
// 数组索引的null条件运算符
int? length = array?[0]?.Length;这个特性允许使用lambda表达式语法来定义方法、属性和其他成员:
// 传统方式
public string GetFullName()
{
return $"{FirstName} {LastName}";
}
// 表达式体成员
public string GetFullName() => $"{FirstName} {LastName}";
// 属性的表达式体
public string FullName => $"{FirstName} {LastName}";
// 表达式体索引器
public string this[int index] => collection[index];允许在属性声明时直接初始化自动属性:
public class Customer
{
// 自动属性初始化
public string Status { get; set; } = "New";
public List<Order> Orders { get; } = new List<Order>();
public DateTime CreatedDate { get; } = DateTime.UtcNow;
// 只读自动属性
public string Id { get; } = Guid.NewGuid().ToString();
}nameof操作符获取标识符的文本名称,这在异常处理和属性更改通知中特别有用:
public void ProcessAge(int age)
{
if (age < 0)
{
// 使用nameof避免硬编码参数名
throw new ArgumentException("Cannot be negative", nameof(age));
}
}
// 在属性更改通知中使用
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}try
{
// 一些可能抛出异常的代码
}
catch (Exception ex) when (ex.InnerException != null)
{
// 只处理有内部异常的情况
}C# 7.0引入的元组是一个重大改进,它让开发者能够更优雅地处理多个返回值。新的语法使得创建和使用元组变得异常简单:
// 声明和初始化元组
(string name, int age) person = ("张三", 25);
// 方法返回多个值
(string city, double temperature) GetWeather()
{
return ("北京", 20.5);
}
// 解构赋值
var (name, age) = person;模式匹配为类型检查和数据提取提供了更优雅的语法,包括:
// is 表达式与模式匹配
if (obj is int number)
{
Console.WriteLine($"数字是: {number}");
}
// switch 语句的模式匹配
switch (shape)
{
case Circle c when c.Radius > 10:
Console.WriteLine($"大圆形");
break;
case Rectangle r:
Console.WriteLine($"矩形面积: {r.Width * r.Height}");
break;
}本地函数允许在方法内部定义函数,适合处理特定作用域的复杂逻辑:
public int Calculate(int[] numbers)
{
int Sum(int[] values, int start, int end)
{
int sum = 0;
for (int i = start; i < end; i++)
sum += values[i];
return sum;
}
return Sum(numbers, 0, numbers.Length);
}简化了使用out参数的语法,允许在方法调用时直接声明变量:
// 旧方式
int result;
if (int.TryParse("123", out result))
{
Console.WriteLine(result);
}
// 新方式
if (int.TryParse("123", out int result))
{
Console.WriteLine(result);
}C# 7.1开始支持异步入口点,使得在程序入口就能使用异步操作:
static async Task Main(string[] args)
{
await DoAsyncWork();
}C# 7.3增强了泛型约束能力,引入了新的约束选项:
// Enum 约束
public class EnumContainer<T> where T : Enum { }
// 委托约束
public class DelegateContainer<T> where T : Delegate { }
// unmanaged 约束
public class UnmanagedContainer<T> where T : unmanaged { }这是C# 8.0最重要的特性之一,它通过编译时的静态分析来帮助开发者防止空引用异常:
// 启用可空引用类型
#nullable enable
public class Person
{
public string Name { get; set; } // 非空字符串
public string? Nickname { get; set; } // 可空字符串
}
public void ProcessPerson(Person? person) // 参数可能为null
{
if (person != null)
{
Console.WriteLine(person.Name.Length); // 编译器确保Name非空
Console.WriteLine(person.Nickname?.Length); // 需要null检查
}
}引入了异步数据流的概念,让异步数据的迭代变得更加自然:
public async IAsyncEnumerable<int> GenerateNumbers()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100); // 模拟异步操作
yield return i;
}
}
public async Task ConsumeNumbers()
{
await foreach (var number in GenerateNumbers())
{
Console.WriteLine(number);
}
}允许在接口中定义具有默认实现的方法,这个特性提升了接口的灵活性:
public interface ILogger
{
void Log(string message);
// 默认实现
void LogError(string error)
{
Log($"ERROR: {error}");
}
// 带默认实现的属性
bool EnableLogging { get; set; } = true;
}引入了新的语法来处理数组和集合的切片操作:
var numbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 使用新的索引语法
Index last = ^1; // 从末尾开始的索引
Console.WriteLine(numbers[last]); // 输出: 9
// 使用范围
Range range = 1..4; // 从索引1到3
int[] slice = numbers[range]; // 结果: { 1, 2, 3 }
// 更多范围示例
var firstThree = numbers[..3]; // { 0, 1, 2 }
var lastThree = numbers[^3..]; // { 7, 8, 9 }
var allButEnds = numbers[1..^1]; // 去除首尾引入了结构体中的只读成员,提供了更好的不可变性支持:
public struct Point
{
public readonly double X { get; }
public readonly double Y { get; }
public Point(double x, double y) => (X, Y) = (x, y);
// 只读方法,保证不会修改结构体的状态
public readonly double GetDistance() => Math.Sqrt(X * X + Y * Y);
}引入了更简洁的switch表达式语法,结合模式匹配带来更强大的表达能力:
public static string GetDisplayName(object item) => item switch
{
string s => s,
int i => i.ToString(),
DateTime d => d.ToShortDateString(),
Point p => $"({p.X}, {p.Y})",
null => "null",
_ => "unknown"
};
// 属性模式
public static decimal GetDiscount(Customer customer) => customer switch
{
{ Loyalty: "Gold", Orders: > 100 } => 0.15m,
{ Loyalty: "Silver", Orders: > 50 } => 0.10m,
{ Loyalty: "Bronze" } => 0.05m,
_ => 0m
};Records 是C# 9.0中最重要的新特性之一,它为创建不可变的数据类型提供了简洁的语法:
// 基本记录声明
public record Person(string FirstName, string LastName);
// 带有额外成员的记录
public record Employee(string FirstName, string LastName, decimal Salary)
{
public decimal CalculateBonus() => Salary * 0.1m;
}
// 记录的使用
var john = new Person("John", "Doe");
var jane = john with { FirstName = "Jane" }; // 非破坏性修改
// 解构
var (firstName, lastName) = john;
// 值相等性比较
var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");
Console.WriteLine(person1 == person2); // 输出: TrueInit-only属性允许在对象创建时设置属性值,之后便不可修改,增强了对象的不可变性:
public class Book
{
// 只能在对象初始化时设置
public string Title { get; init; }
public string Author { get; init; }
public int Year { get; init; }
}
// 使用示例
var book = new Book
{
Title = "C# 高级编程",
Author = "张三",
Year = 2020
};
// book.Title = "新标题"; // 编译错误简化了程序入口点的编写,特别适合简单的控制台应用程序和脚本:
// Program.cs
using System;
// 不需要显式的Main方法
Console.WriteLine("Hello, World!");
// 可以直接使用await
await Task.Delay(1000);
Console.WriteLine("Goodbye!");
// 返回值
return 0;C# 9.0进一步增强了模式匹配的能力,引入了新的模式类型:
// 关系模式
bool IsWorkingAge(Person person) => person.Age is >= 18 and <= 65;
// 逻辑模式
bool IsValidIdentifier(string s) => s is not null and [FirstChar] and [Length];
// 模式组合
static string GetWeatherAdvice(double temperature) => temperature switch
{
>= 30 => "太热了!",
>= 20 and < 30 => "天气不错!",
>= 10 and < 20 => "有点凉!",
< 10 => "太冷了!",
_ => "无效温度"
};简化了对象创建语法,当类型可以从上下文推断时,允许省略类型名:
// 旧方式
Dictionary<string, List<int>> map = new Dictionary<string, List<int>>();
// 新方式
Dictionary<string, List<int>> map = new(); // 类型推断
// 在方法调用中
void ProcessList(List<string> list) { }
ProcessList(new()); // 类型从参数推断
// 在属性初始化中
public class DataContainer
{
public List<int> Numbers { get; set; } = new();
}引入全局using指令大大简化了项目中常用命名空间的导入管理:
// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
// 还可以在.csproj文件中启用常用的全局using
<ItemGroup>
<Using Include="System"/>
<Using Include="System.Collections.Generic"/>
</ItemGroup>新的文件作用域命名空间声明提供了更简洁的语法,减少了代码的缩进层级:
// 旧方式
namespace MyCompany.MyProduct
{
public class MyClass
{
// 类实现
}
}
// 新方式
namespace MyCompany.MyProduct;
public class MyClass
{
// 类实现
}扩展了C# 9.0中引入的记录概念,增加了记录结构体的支持:
// 简单的记录结构体
public record struct Point(double X, double Y);
// 带有额外成员的记录结构体
public record struct Temperature(double Celsius)
{
public double Fahrenheit => Celsius * 9 / 5 + 32;
public void Deconstruct(out double celsius, out double fahrenheit)
{
celsius = Celsius;
fahrenheit = Fahrenheit;
}
}
// 使用示例
var temp = new Temperature(25);
var (c, f) = temp; // 解构
var newTemp = temp with { Celsius = 30 }; // 非破坏性修改允许在编译时计算常量字符串插值表达式:
const string ApiVersion = "v1";
const string BaseUrl = $"https://api.example.com/{ApiVersion}"; // 现在支持!
public class Constants
{
private const string ServiceName = "UserService";
public const string LogPrefix = $"[{ServiceName}] "; // 编译时常量
// 在编译时计算的格式化字符串
public const string WelcomeMessage = $"Welcome to {ServiceName} {ApiVersion}!";
}Lambda表达式获得了多项改进,包括自然类型推断和属性支持:
// 自然类型推断
var parse = (string s) => int.Parse(s);
// Lambda表达式的属性
var log = [Description("Logging function")]
(string message) => Console.WriteLine(message);
// 明确的返回类型
var multiply = double (double a, double b) => a * b;
// Lambda表达式的属性和返回类型结合
var calculate = [Obsolete]
int (int x, int y) => x + y;
// 使用ref和out参数
var tryParse = (string s, out int result) => int.TryParse(s, out result);
// 支持attributes
var validate = [ValidateArgument]
(string input) => !string.IsNullOrEmpty(input);// 方法一:直接查看项目文件(.csproj)
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10.0</LangVersion> // 这里指定了C#版本
</PropertyGroup>
// 方法二:通过 Visual Studio
右键项目 -> 属性 -> Build -> Advanced... -> Language Version.NET 6.0 -> 默认 C# 10.0
.NET 5.0 -> 默认 C# 9.0
.NET Core 3.1 -> 默认 C# 8.0
.NET Framework 4.8 -> 默认 C# 7.3<PropertyGroup>
<LangVersion>latest</LangVersion> // 使用最新版本
<!-- 或 -->
<LangVersion>10.0</LangVersion> // 使用特定版本
<!-- 或 -->
<LangVersion>preview</LangVersion> // 使用预览版本
</PropertyGroup>可用的LangVersion值