CSharp 发展过程中各版本特性整理

November 27, 20249 minutes

引言

C#语言的诞生背景和设计理念

C#语言诞生于2000年,是由微软的Anders Hejlsberg领导的团队开发的一门编程语言。当时的背景是:

微软需要一个现代的、面向对象的编程语言来支持其.NET平台战略。虽然已有Visual Basic和C++等语言,但它们或多或少都存在一些局限性。 Java语言在企业开发领域获得了巨大成功,微软希望能够提供一个既保持Java优点、又能解决其不足的替代方案。

C#的核心设计理念包括:

简单性和现代性:语言语法简洁清晰,去除了C++中的一些复杂特性(如多重继承),同时又引入了许多现代编程特性。 面向对象:完全支持面向对象编程范式,包括封装、继承、多态等特性,并在语言层面强制使用对象。 类型安全:采用强类型系统,在编译时就能发现类型错误,提高代码的可靠性。 组件化:支持基于组件的开发,可以方便地创建和使用软件组件。 版本兼容:注重向后兼容性,新版本的改进不会破坏现有代码。 高效性能:虽然运行在托管环境中,但通过即时编译(JIT)等技术确保较高的运行效率。

版本更新的总体趋势

  • 早期版本(C# 1.0 - 3.0)主要关注基础语言特性的构建
  • 中期版本(C# 4.0 - 6.0)开始注重简化常见编程模式的语法
  • 近期版本(C# 7.0及以后)更加强调简洁优雅的代码表达方式

C# 1.0(2002)- 基础特性

面向对象的基本特性

C# 1.0从设计之初就将面向对象作为核心理念。它提供了完整的面向对象编程支持:

  • 封装:通过访问修饰符(public、private、protected、internal)实现细粒度的访问控制
  • 继承:支持单一继承体系,所有类默认继承自Object类
  • 多态:通过虚方法(virtual)和重写(override)实现动态绑定
  • 接口:提供接口(interface)机制支持多重实现

类型系统

C# 1.0建立了统一且严格的类型系统:

  • 所有类型都继承自System.Object
  • 原始数据类型(int、double等)与对象类型具有相同的地位
  • 支持值类型与引用类型的区分
  • 提供装箱(boxing)和拆箱(unboxing)机制实现值类型和引用类型的转换
  • 引入可空类型的概念

托管代码环境

依托于.NET Framework平台,C# 1.0提供了托管代码执行环境:

  • 自动内存管理:通过垃圾回收机制自动释放未使用的内存
  • 类型安全:在编译时和运行时提供类型检查
  • 异常处理:完整的try-catch-finally异常处理机制
  • 程序集(Assembly)作为部署和版本控制的基本单位

Windows Forms支持

作为面向Windows平台开发的重要组成部分,C# 1.0提供了完整的Windows Forms支持:

  • 可视化窗体设计器
  • 丰富的控件库
  • 事件驱动编程模型
  • 简单的数据绑定机制

C# 2.0(2005)- 增强型特性

泛型(Generics)

泛型的引入是C# 2.0最重要的更新之一,它从根本上改变了开发者处理类型安全的方式:

  • 实现了类型安全的集合类,避免了频繁的类型转换和装箱/拆箱操作
  • 允许开发者编写可重用的类型无关代码,提高了代码复用性
  • 提供了约束机制,使得泛型类型参数可以被限制在特定范围内
  • 显著提升了运行时性能,减少了内存占用

分部类(Partial Types)

分部类允许将一个类、结构或接口的定义分散到多个源文件中:

  • 支持更好的代码组织和团队协作
  • 使自动生成的代码与手写代码得以分离
  • 便于处理大型类的维护工作
  • 特别适合Windows Forms和WPF等设计器生成的代码
partial class Customer
{
    public void Save() { ... }
}

partial class Customer
{
    public void Load() { ... }
}

可空类型(Nullable Types)

可空类型的引入解决了值类型无法表示"无值"状态的问题:

  • 允许值类型存储null值
  • 提供了简洁的语法支持(?运算符)
  • 增加了处理数据库交互等场景的便利性
  • 引入了相关的运算符和方法,如HasValue和Value属性
int? nullableInt = null;
DateTime? nullableDate = null;
if (nullableInt.HasValue)
{
    Console.WriteLine(nullableInt.Value);
}

匿名方法(Anonymous Methods)

匿名方法为委托提供了更灵活的语法支持:

  • 允许内联定义委托的实现
  • 可以捕获外部变量(闭包)
  • 简化了事件处理程序的编写
  • 为后续Lambda表达式的引入奠定基础
button.Click += delegate(object sender, EventArgs e)
{
    MessageBox.Show("Button clicked!");
};

迭代器(Iterators)

迭代器极大地简化了集合类型的实现:

  • 通过yield return语法简化了枚举器的编写
  • 支持延迟计算和惰性求值 (Mark to be supplemented)
  • 提供了更自然的集合遍历方式
  • 减少了样板代码的编写
public IEnumerable<int> GetNumbers()
{
    for (int i = 0; i < 10; i++)
    {
        yield return i;
    }
}

C# 3.0(2007)- LINQ革命

LINQ(Language Integrated Query)

LINQ的引入彻底改变了.NET平台的数据查询方式:

  • 提供统一的查询语法,可以查询任何数据源(数组、集合、XML、数据库等)
  • 支持声明式的数据操作,使代码更加直观和易于维护
  • 编译时类型检查,避免运行时错误
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表达式

Lambda表达式为C#带来了更简洁的函数式编程体验:

  • 提供了更简洁的匿名函数语法
  • 自动类型推断减少了代码冗余
  • 与LINQ完美配合
  • 支持闭包,可以捕获外部变量
// 传统委托写法
delegate(int x) { return x * x; }

// Lambda表达式
x => x * x;

// 在LINQ中的应用
var squares = numbers.Select(x => x * x);

扩展方法(Extension Methods)

扩展方法允许在不修改原有类型的情况下为其添加新方法:

  • 无需继承就能扩展类型的功能
  • LINQ的基础设施之一
  • 提高代码的重用性和可维护性
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类的原生方法一样

自动实现的属性(Auto-Implemented Properties)

大大简化了属性的声明语法:

  • 减少了样板代码
  • 编译器自动生成背后的字段
  • 保持了属性的封装性

匿名类型(Anonymous Types)

允许快速定义仅用于临时存储数据的类型:

  • 适合LINQ查询结果的投影
  • 编译器自动生成类型定义
  • 类型安全且支持智能提示

对象和集合初始化器

提供了更简洁的对象初始化语法:

  • 简化对象创建和属性设置
  • 支持嵌套的复杂对象初始化
  • 使代码更加清晰易读

C# 4.0(2010)- 动态编程

动态绑定(Dynamic Binding)

动态绑定是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使用默认值1

泛型的协变和逆变

C# 4.0引入了泛型接口和委托的变体支持,通过out(协变)和in(逆变)关键字来实现:

  • 协变(Covariance):允许使用比原指定类型更派生的类型
  • 逆变(Contravariance):允许使用比原指定类型更基础的类型
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;  // 合法,string是object的子类

// 逆变示例
Action<object> actObject = ProcessObject;
Action<string> actString = actObject;  // 合法,object是string的基类

COM互操作性的改进

COM互操作性的改进 C# 4.0对COM互操作进行了显著改进:

  • 通过dynamic关键字简化了COM对象的使用
  • 简化了Office自动化等常见COM互操作场景
  • 改进了类型等效性和互操作性
dynamic excel = new Microsoft.Office.Interop.Excel.Application();
excel.Visible = true;
excel.Workbooks.Add();

C# 5.0(2012)- 异步编程

async和await关键字

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优势:

  • 避免了回调地狱(Callback Hell)
  • 保持了代码的线性结构,提高了可读性
  • 异常处理机制与同步代码保持一致
  • 简化了异步操作的组合和链式调用

调用者信息特性

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

C# 6.0(2015)- 代码简化

字符串插值(String Interpolation)

字符串插值引入了一种更直观的字符串格式化方式,使用$符号前缀和{}占位符:

// 传统的字符串格式化
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的对象:

// 传统的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操作符

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-7.3(2017)- 现代语言特性

元组(Tuples)和解构

C# 7.0引入的元组是一个重大改进,它让开发者能够更优雅地处理多个返回值。新的语法使得创建和使用元组变得异常简单:

// 声明和初始化元组
(string name, int age) person = ("张三", 25);

// 方法返回多个值
(string city, double temperature) GetWeather()
{
    return ("北京", 20.5);
}

// 解构赋值
var (name, age) = person;

模式匹配(Pattern Matching)

模式匹配为类型检查和数据提取提供了更优雅的语法,包括:

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

本地函数(Local Functions)

本地函数允许在方法内部定义函数,适合处理特定作用域的复杂逻辑:

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变量

简化了使用out参数的语法,允许在方法调用时直接声明变量:

// 旧方式
int result;
if (int.TryParse("123", out result))
{
    Console.WriteLine(result);
}

// 新方式
if (int.TryParse("123", out int result))
{
    Console.WriteLine(result);
}

async Main方法

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(2019)- 空安全和更多

可空引用类型

这是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检查
    }
}

异步流(Async Streams)

引入了异步数据流的概念,让异步数据的迭代变得更加自然:

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表达式增强

引入了更简洁的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
};

C# 9.0(2020)- 简洁与不变性

记录类型(Records)

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); // 输出: True

Init-only属性

Init-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 = "新标题"; // 编译错误

顶级语句(Top-level statements)

简化了程序入口点的编写,特别适合简单的控制台应用程序和脚本:

// 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 => "太冷了!",
    _ => "无效温度"
};

目标类型new表达式

简化了对象创建语法,当类型可以从上下文推断时,允许省略类型名:

// 旧方式
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();
}

C# 10.0(2021)及以后

全局using指令

引入全局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表达式改进

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);

项目的C#版本

查看当前项目的C#版本

// 方法一:直接查看项目文件(.csproj)
<PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <LangVersion>10.0</LangVersion>  // 这里指定了C#版本
</PropertyGroup>

// 方法二:通过 Visual Studio
右键项目 -> 属性 -> Build -> Advanced... -> Language Version

C#与.NET版本的对应关系

.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

手动指定C#版本的方法

<PropertyGroup>
    <LangVersion>latest</LangVersion>  // 使用最新版本
    <!-- 或 -->
    <LangVersion>10.0</LangVersion>    // 使用特定版本
    <!-- 或 -->
    <LangVersion>preview</LangVersion> // 使用预览版本
</PropertyGroup>

可用的LangVersion值

  • latest: 最新发布版本
  • preview: 预览版本
  • 具体数字: 7.3, 8.0, 9.0, 10.0等
  • default: 基于目标框架的默认版本