目录
一.范围表达式
1.概述
2.语法
3.代码示例
4.实现原理
5.应用场景
二.模式匹配
1.概述
2.核心概念
3.常用模式类型
4.Switch表达式
5.使用示例
6.优势
三.逆变和协变
1.概述
2.泛型类型参数的变性
3.协变示例
4.逆变示例
5.注意事项
6.应用场景
总结
一.范围表达式
1.概述
- 范围表达式是C# 8.0引入的新特性,它提供了一种简洁的语法来表示数组,字符串或任何实现了索引器的集合类型的子范围(slice).通过范围表达式,可以更方便地从集合中提取出特定范围的元素
2.语法
- startIndex..endIndex:表示从startIndex开始(包含),到endIndex结束(不包含)的元素
..
endIndex:表示从集合的起始位置到endIndex(不包含)的元素- startIndex
..
:表示从startIndex开始(包含)到集合的末尾的所有元素 ..
:表示集合中的所有元素
注意:索引可以是正数,也可以是使用
^
符号表示的从末尾开始的索引,其中^1
表示最后一个元素
3.代码示例
int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 从索引2(值为2)到索引5(值为5),不包含索引5
int[] slice1 = numbers[2..5]; // {2, 3, 4}
// 从开始到索引3(不包含索引3)
int[] slice2 = numbers[..3]; // {0, 1, 2}
// 从索引5到结尾
int[] slice3 = numbers[5..]; // {5, 6, 7, 8, 9}
// 获取最后两个元素
int[] slice4 = numbers[^2..]; // {8, 9}
// 获取从索引1到倒数第二个元素
int[] slice5 = numbers[1..^1]; // {1, 2, 3, 4, 5, 6, 7, 8}
4.实现原理
范围表达式使用了System.Index和System.Range结构:
- Index:表示一个索引位置,可以从开头(从0开始)或从结尾(使用
^
符号)计数 - Range:由Index的起始和结束位置组成,表示一个范围
编译器会将范围表达式转换为调用Slice方法或其他适当的方法.例如numbers[2..5]会转换为numbers.
Slice(2, 3)
在C#中
,
Slice方法是与Span<T>和ReadonlySpan<T>类型相关的功能,用于在不复制数据的情况下生成一个子序列.这种方法在处理大型数据集或需要高性能操作时特别有用,因为它避免了不必要的数据复制基本用法:
using System; class Program { static void Main() { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Span<int> span = array; // 创建一个 Span<int> 包含整个数组 // 从索引2开始,取3个元素 Span<int> slice = span.Slice(2, 3); foreach (var item in slice) { Console.WriteLine(item); // 输出: 3 4 5 } } }
5.应用场景
- 字符串处理:
string text = "Hello, World!";
string subText = text[7..^1]; // "World"
- 列表和视图:
如果列表实现了Slice方法或索引器支持Range则也可以使用范围表达式
二.模式匹配
1.概述
- 模式匹配是C#从7.0版本开始引入的特性,用于更简洁地表达类型检查,解构和条件判断.在C# 8.0及后续版本中,模式匹配得到了进一步增强,使代码更加清晰和易读
2.核心概念
- 模式(Pattern):描述需要匹配的特定形状或条件,例如类型,值,属性等
- 表达式(Expression):应用模式匹配的对象或值\
3.常用模式类型
恒值模式:匹配特定的常量值
if (obj is null)
{
// obj为null
}
类型模式:检查对象是否为特定类型,并进行类型转换
if (obj is string s)
{
// obj是string类型,且已转换为s
}
属性模式:检查对象的属性是否满足特定条件
if (person is { Age: >= 18 })
{
// person的Age属性大于等于18
}
位置模式:对对象进行解构,并匹配解构后的值
if (point is (0, 0))
{
// point在原点
}
递归模式:在模式中嵌套使用其他模式
if (tree is Node(var left, var right))
{
// 对左子树和右子树进行处理
}
4.Switch表达式
C# 8.0引入了新的Switch表达式,更加简洁:
代码示例:
string GetShapeDescription(Shape shape) => shape switch
{
Circle { Radius: var r } => $"这是一个半径为{r}的圆形",
Rectangle { Width: var w, Height: var h } => $"这是一个宽{w}高{h}的矩形",
_ => "未知形状"
};
5.使用示例
类型检查和转换:
if (obj is int number)
{
Console.WriteLine($"整数:{number}");
}
属性匹配:
if (employee is { Position: "Manager", Salary: > 5000 })
{
// 匹配职位为Manager且薪水大于5000的员工
}
6.优势
- 增强可读性:使条件判断更加直观
- 减少类型转换代码:自动进行类型转换,减少冗余代码
- 支持复杂条件:可以嵌套和组合多种模式,表达复杂的匹配逻辑
三.逆变和协变
1.概述
协变和逆变用于解决泛型类型在继承关系中的转换问题,主要应用于泛型接口和泛型委托.它们允许你在泛型类型之间进行类型转换,而不需要创建新的类型或进行显式转换
- 协变(Covariance):允许从派生类型转换为基类型(输出位置)
- 逆变(Contravariance):允许从基类型转换为派生类型(输入位置)
2.泛型类型参数的变性
在泛型接口或委托中,可以使用out和in关键字来声明类型参数的变性:
- out:协变类型参数,只能用于返回值(输出)
- in:逆变类型参数,只能用于参数(输入)
3.协变示例
协变接口:
public interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator();
}
由于T被声明为out,因此IEnumerable<string>可以赋值给IEnumerable<object>:
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // 合法,协变
协变委托:
public delegate T Factory<out T>();
使用示例:
Factory<string> stringFactory = () => "Hello";
Factory<object> objectFactory = stringFactory; // 合法,协变
4.逆变示例
逆变接口:
public interface IComparer<in T>
{
int Compare(T x, T y);
}
由于T被声明为in,因此IComparer<object>可以赋值给IComparer<string>:
IComparer<object> objectComparer = new MyObjectComparer();
IComparer<string> stringComparer = objectComparer; // 合法,逆变
逆变委托:
public delegate void Action<in T>(T item);
使用示例:
Action<object> objectAction = obj => Console.WriteLine(obj);
Action<string> stringAction = objectAction; // 合法,逆变
5.注意事项
- 限制:变性只能用于接口和委托的类型参数,且类型参数只能用于输入位置(逆变)或输出位置(协变),不能同时用于输入和输出
- 类和结构体:泛型类和结构体的类型参数不支持变性
- 方法类型参数:泛型方法的类型参数也不支持变性
6.应用场景
- 接口的扩展:通过协变和逆变,可以设计更灵活的接口,使之更通用
- 事件处理程序:在委托中使用变性,可以赋值兼容的委托实例
- 泛型集合:在处理泛型集合时,可以更方便地进行类型转换
总结
- 协变适用于从派生类型转换为基类型(类型参数用于输出)
- 逆变适用于从基类型转换为派生类型(类型参数用于输入)