泛型与枚举器:C编程中的重要概念解析
立即解锁
发布时间: 2025-08-21 00:06:08 阅读量: 2 订阅数: 8 


C#编程基础与实战技巧
### 泛型与枚举器:C# 编程中的重要概念解析
在 C# 编程中,泛型和枚举器是两个非常重要的概念。泛型提供了类型安全和代码复用的强大功能,而枚举器则使得我们能够方便地遍历集合中的元素。下面将详细介绍这两个概念及其相关应用。
#### 1. 泛型中的协变和逆变
在接口中,协变和逆变的原理与委托中的类似,并且在接口声明中使用 `out` 和 `in` 关键字来实现。以下是一个使用接口协变的示例代码:
```csharp
class Animal { public string Name; }
class Dog: Animal{ };
interface IMyIfc<out T>
{
T GetFirst();
}
class SimpleReturn<T>: IMyIfc<T>
{
public T[] items = new T[2];
public T GetFirst() { return items[0]; }
}
class Program
{
static void DoSomething(IMyIfc<Animal> returner)
{
Console.WriteLine(returner.GetFirst().Name);
}
static void Main( )
{
SimpleReturn<Dog> dogReturner = new SimpleReturn<Dog>();
dogReturner.items[0] = new Dog() { Name = "Avonlea" };
IMyIfc<Animal> animalReturner = dogReturner;
DoSomething(dogReturner);
}
}
```
上述代码的执行步骤如下:
1. 在 `Main` 方法的前两行,使用 `Dog` 类创建并初始化 `SimpleReturn` 泛型类的一个实例。
2. 接着,将该对象赋值给一个声明为 `IMyIfc<Animal>` 类型的变量。这里需要注意,赋值左边的类型是接口类型而非类,并且尽管接口类型不完全匹配,但由于接口声明中的协变 `out` 说明符,编译器会允许这样的赋值。
3. 最后,使用实现了接口的协变类调用 `DoSomething` 方法。
代码的输出结果为:
```plaintext
Avonlea
```
#### 2. 关于变体的更多内容
除了显式的协变和逆变,还存在编译器自动识别构造委托为协变或逆变并自动进行类型强制转换的情况。当对象尚未分配类型时,就会发生这种情况。以下是一个示例代码:
```csharp
class Animal { public int Legs = 4; }
class Dog : Animal { }
class Program
{
delegate T Factory<out T>();
static Dog MakeDog() { return new Dog(); }
static void Main()
{
Factory<Animal> animalMaker1 = MakeDog;
Factory<Dog> dogMaker = MakeDog;
Factory<Animal> animalMaker2 = dogMaker;
Factory<Animal> animalMaker3 = new Factory<Dog>(MakeDog);
}
}
```
在这个示例中,`Main` 方法的第一行从一个返回类型为 `Dog` 对象的方法创建了一个 `Factory<Animal>` 类型的构造委托。在创建这个委托时,赋值运算符右侧的方法名尚未有类型,编译器能够确定该方法除了返回类型是 `Dog` 而非 `Animal` 外,符合委托的类型,并且能够识别这是一种协变关系,从而创建构造类型并将其赋值给变量。
而 `Main` 方法的第三行和第四行的赋值,由于等号右侧的表达式已经有了类型,因此需要委托声明中的 `out` 说明符来告知编译器允许它们是协变的。
需要注意的其他关于变体的重要内容如下:
- 变体处理的是安全地用基类型替代派生类型以及反之的问题,因此仅适用于引用类型,因为值类型不能被派生。
- 使用 `in` 和 `out` 关键字的显式变体仅适用于委托和接口,不适用于类、结构体或方法。
- 不包含 `in` 或 `out` 关键字的委托和接口类型参数称为不变量,这些类型不能用于协变或逆变。
#### 3. 枚举器和可枚举类型
在 C# 中,我们可以使用 `foreach` 语句来遍历数组的元素。例如:
```csharp
int[] arr1 = { 10, 11, 12, 13 };
foreach (int item in arr1)
Console.WriteLine("Item value: {0}", item);
```
代码的输出结果为:
```plaintext
Item value: 10
Item value: 11
Item value: 12
Item value: 13
```
数组之所以能够使用 `foreach` 语句进行遍历,是因为数组可以根据请求生成一个称为枚举器的对象。枚举器可以按顺序逐个返回数组的元素,并且“知道”元素的顺序并跟踪其在序列中的位置。
对于具有枚举器的类型,必须有一种方法来获取它们。在 .NET 中,获取对象枚举器的标准方法是调用对象的 `GetEnumerator` 方法。实现 `GetEnumerator` 方法的类型称为可枚举类型,数组就是可枚举类型。
`foreach` 构造设计用于与可枚举类型一起工作。只要它要迭代的对象是可枚举类型,它将执行以下操作:
1. 通过调用对象的 `GetEnumerator` 方法获取其枚举器。
2. 从枚举器请求每个项,并将其作为迭代变量提供给代码,代码可以读取(但不能更改)该变量。
枚举器有三种变体,分别是:
- 使用 `IEnumerator/IEnumerable` 接口的非泛型接口形式。
- 使用 `IEnumerator<T>/IEnumerable<T>` 接口的泛型接口形式。
- 不使用接口的形式。
#### 4. 使用 `IEnumerator` 接口
非泛型接口形式的枚举器是实现 `IEnumerator` 接口的类,之所以称为非泛型,是因为它不使用 C# 泛型。`IEnumerator` 接口包含三个功能成员:`Current`、`MoveNext` 和 `Reset`。
- `Current` 是一个只读属性,返回序列中当前位置的项,返回类型为 `object`,因此可以返回任何类型的对象。
- `MoveNext` 是一个方法,将枚举器的位置推进到集合中的下一项,并返回一个布尔值,指
0
0
复制全文