掌握 C# 泛型
C# 中的泛型自引入以来已成为该语言不可分割的一部分,即使并非所有开发人员都完全理解其工作原理,但也能带来诸多好处。 在他的视频"如何在 C# 中创建泛型,包括新功能"中,Tim Corey 解释了泛型为何重要、如何创建泛型,并演示了它们的实际应用。
本文提供了 C# 泛型的综合指南,从 Tim Corey 关于该主题的视频中提供了宝贵的见解。 它涵盖了泛型的基本原理,包括类型安全、性能优势和实际应用。 文章还探讨了创建泛型方法、类、接口以及对泛型应用约束的问题。 此外,还要突出现实世界中的使用案例,以及使用泛型编写高效、类型安全代码的重要性。
简介
C# 泛型允许开发人员定义可用于任何数据类型的类、方法、接口和集合,为创建灵活、可重用和类型安全的代码提供了一种强大的方法。 通过使用泛型类或泛型方法,开发人员可以定义一个泛型类型参数(如 T),它可以代表任何数据类型。 这消除了代码重复的需要,并增强了代码的重用性,同时在编译时确保了类型安全。像List和Dictionary<TKey, TValue>这样的泛型集合类允许高效处理不同的数据类型,而泛型接口和泛型委托则支持创建可与多个类型参数一起工作的自定义泛型类型。 通过利用泛型,开发人员可以最大限度地提高代码效率,减少非泛型类的缺点。 这种灵活性可以在不牺牲性能或类型安全的情况下创建更多可重复使用的代码。
Tim at(0:00)通过强调泛型在 C# 中的广泛使用引入主题。 他的目标是解释为什么类属是必不可少的,并演示如何有效地创建和使用类属。
创建项目
Tim 首先创建了一个名为 "GenericsDemoApp "的新控制台应用程序,专门演示泛型,没有任何用户界面干扰。 他使用 .NET 8 和 Visual Studio 2022 启动了该项目。
泛型基础
Tim在(2:22)用List类的一个示例开始讲解泛型的概念。 泛型允许指定集合可容纳的元素类型,从而提供类型安全并避免运行时出错。
List<int> numbers = new List<int> { 1, 2, 3 };
List<string> strings = new List<string> { "Tim", "Corey", "Sue" };List<int> numbers = new List<int> { 1, 2, 3 };
List<string> strings = new List<string> { "Tim", "Corey", "Sue" };List确保只能将整数添加到numbers列表中,而只能将字符串添加到strings列表中。
类型安全和效率
Tim 强调了泛型提供的类型安全的重要性。 编译器会在设计时检查类型,防止类型不匹配并确保代码的安全执行。 泛型还可以避免装箱和拆箱,从而提高代码效率。
非通用集合的低效率
为了演示使用非通用集合的低效率,Tim 创建了一个 List,并展示了它如何容纳不同类型的对象。
List<object> objects = new List<object> { "Tim", 4, 3.6m };List<object> objects = new List<object> { "Tim", 4, 3.6m };他解释说,使用非通用集合可能会导致类型不匹配,并由于装箱和拆箱而导致效率低下。
性能比较
Tim 在(6:15)进行了使用 List
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i <= 1_000_000; i++)
{
objects.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
for (int i = 1; i <= 1_000_000; i++)
{
numbers.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms");Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i <= 1_000_000; i++)
{
objects.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
for (int i = 1; i <= 1_000_000; i++)
{
numbers.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms");结果表明,向 List

创建类型检查器方法
Tim 在(10:14)演示了如何创建名为 TypeChecker 的通用方法。 该方法检查给定值的类型并打印出来,展示了泛型的灵活性和强大功能。
public static void TypeChecker<t>(T value)
{
Console.WriteLine($"Type: {typeof(T)}, Value: {value}");
}public static void TypeChecker<t>(T value)
{
Console.WriteLine($"Type: {typeof(T)}, Value: {value}");
}TypeChecker 方法使用 typeof 操作符确定泛型参数 T 的类型,并打印类型和值。
使用类型检查方法
Tim 提供了使用不同类型参数调用 TypeChecker 方法的示例。
TypeChecker(1); // Type: System.Int32, Value: 1
TypeChecker("Tim"); // Type: System.String, Value: Tim
TypeChecker(1.1); // Type: System.Double, Value: 1.1TypeChecker(1); // Type: System.Int32, Value: 1
TypeChecker("Tim"); // Type: System.String, Value: Tim
TypeChecker(1.1); // Type: System.Double, Value: 1.1通过向 TypeChecker 方法传递各种类型,Tim 展示了泛型如何无缝处理不同的数据类型。
创建一个通用类:更好的列表
Tim 在 16:25 开始创建一个名为 BetterList 的通用类。该类封装了一个指定类型的列表,并提供了额外的功能。
public class BetterList<t>
{
private List<t> data = new List<t>();
public void AddToList(T value)
{
data.Add(value);
Console.WriteLine($"{value} has been added to the list");
}
}public class BetterList<t>
{
private List<t> data = new List<t>();
public void AddToList(T value)
{
data.Add(value);
Console.WriteLine($"{value} has been added to the list");
}
}BetterList类包含一个私有的List和一个方法AddToList,该方法将值添加到列表并打印一条指示添加的消息。
使用更好的列表类
Tim 举例说明了如何使用不同类型的 BetterList 类。
BetterList<int> betterNumbers = new BetterList<int>();
betterNumbers.AddToList(5);
BetterList<PersonRecord> people = new BetterList<PersonRecord>();
people.AddToList(new PersonRecord("Tim", "Corey"));BetterList<int> betterNumbers = new BetterList<int>();
betterNumbers.AddToList(5);
BetterList<PersonRecord> people = new BetterList<PersonRecord>();
people.AddToList(new PersonRecord("Tim", "Corey"));在这些示例中,BetterList
创建通用接口
Tim 在(21:48)处介绍了名为 IImportance 的通用接口的概念。 该接口定义了一种方法,用于确定两个值中哪个更重要。
public interface IImportance<t>
{
T MostImportant(T a, T b);
}public interface IImportance<t>
{
T MostImportant(T a, T b);
}实现通用接口
Tim 演示了如何针对不同类型实现该接口。他从整数的实现开始。
public class EvaluateImportance : IImportance<int>
{
public int MostImportant(int a, int b)
{
return a > b ? a : b;
}
}public class EvaluateImportance : IImportance<int>
{
public int MostImportant(int a, int b)
{
return a > b ? a : b;
}
}接下来,他实现了字符串的接口,使用字符串的长度来确定重要性。
public class EvaluateStringImportance : IImportance<string>
{
public string MostImportant(string a, string b)
{
return a.Length > b.Length ? a : b;
}
}public class EvaluateStringImportance : IImportance<string>
{
public string MostImportant(string a, string b)
{
return a.Length > b.Length ? a : b;
}
}这些实现演示了如何将相同的接口应用于不同类型,并为每种类型提供特定的逻辑。
将约束应用于泛型
Tim 在(25:21)中解释了如何对泛型应用约束,确保它们满足特定条件。 例如,泛型可以被限制为具有空构造函数或实现特定接口。
public class SampleClass<t> where T : new()
{
// Class implementation
}
public class SampleClassWithInterface<t> where T : IImportance<t>
{
// Class implementation
}public class SampleClass<t> where T : new()
{
// Class implementation
}
public class SampleClassWithInterface<t> where T : IImportance<t>
{
// Class implementation
}这些约束有助于确保通用类型符合必要的标准,防止运行时出错并提高类型安全性。
微软对 INumber 的实现
Tim 讨论了 Microsoft 如何使用 INumber 接口来限制数字操作。 这样就可以在通用类型上进行加减等算术运算。
public class MathOperations<t> where T : INumber<t>
{
public T Add(T x, T y)
{
return x + y;
}
}public class MathOperations<t> where T : INumber<t>
{
public T Add(T x, T y)
{
return x + y;
}
}通过将泛型类型T约束为INumber,确保该类型支持数值操作。
使用不同数值类型的泛型
Tim 在(33:55)中扩展了 MathOperations 类,演示了如何将泛型用于不同的数字类型,如双倍和小数。
Tim 演示了如何为整数和双数创建 MathOperations 实例:
MathOperations<int> intMath = new MathOperations<int>();
Console.WriteLine(intMath.Add(1, 4)); // Outputs: 5
MathOperations<double> doubleMath = new MathOperations<double>();
Console.WriteLine(doubleMath.Add(1.5, 4.3)); // Outputs: 5.8MathOperations<int> intMath = new MathOperations<int>();
Console.WriteLine(intMath.Add(1, 4)); // Outputs: 5
MathOperations<double> doubleMath = new MathOperations<double>();
Console.WriteLine(doubleMath.Add(1.5, 4.3)); // Outputs: 5.8这展示了泛型的灵活性,允许在同一个类中无缝处理不同的数字类型。
处理不同的数字类型
Tim 通过说明不能混合使用不同的数字类型,强调了类型安全的重要性。例如,将 double 添入整数会导致编译错误。
// This will result in a compile-time error
// Console.WriteLine(intMath.Add(1.5, 4));// This will result in a compile-time error
// Console.WriteLine(intMath.Add(1.5, 4));避免类型转换开销
Tim 解释了使用泛型来避免类型转换带来的开销的好处。 例如,在进行数学运算时,将整数转换为双数,然后再转换回整数,这样做的成本会很高。 使用泛型可以直接对本地类型进行操作,从而保持性能和精度。
实践中的通用语言
Tim 建议在使用泛型时要谨慎,建议开发人员适当使用泛型,避免过度使用。 他强调了泛型的好处,如类型安全、减少装箱和拆箱、编译时检查以及增强代码可读性。
他还指出,泛型在像List和Dictionary<TKey, TValue>这样的集合中普遍存在,以及在可处理各种类型而无需事先了解具体细节的日志框架中也很常见。
结论
Tim Corey 对 C# 中的高级泛型进行了详细的探讨,对其实际应用和优点提供了宝贵的见解。 如果您想加深对泛型的理解并查看实际应用中的示例,请务必观看 Tim Corey 关于 C# 泛型的详细视频。 他清晰的解释和动手演示将帮助您充分掌握概念,并在自己的项目中有效应用。

