C#泛型教程展示了如何在C#中定义和使用泛型。在泛型编程中,我们使用自定义类型作为参数来定义其他自定义类型。
C#2.0中添加了泛型。
泛型是类、结构、接口和方法,它们具有用于存储或使用的一种或多种类型的占位符(类型参数)。通用集合类可能使用类型参数作为它存储的对象类型的占位符。
通用类型名称在类、结构、接口或方法名称后的尖括号中提供。此语法告诉编译器定义中使用了这样的名称。
按照惯例,类型参数使用大写字母,例如T
、U
或TKey
,以及TValue
.
实际上,我们延迟了数据类型的规范,直到它在程序中实际使用。泛型使我们能够编写适用于各种类型的代码,而无需为每种类型重复代码。
void Swap<T>(ref T lhs, ref T rhs)
我们有一个泛型方法的定义。T
参数稍后在调用方被替换为具体类型,例如int
或string
。
泛型是抽象蓝图,它们不能按原样使用;当我们使用通用类或方法时,我们必须用替换为匹配占位符的特定数据类型来实例化它们。
.NET在System.Collections.Generic
命名空间中提供了大量接口和类,用于实现泛型集合。
泛型具有以下优点:
- 可重用性
- 类型安全
- 改进的性能
一个泛型方法/类定义可以用于多种类型,从而减少所需的代码量。泛型提供类型安全;编译器在编译时检查是否提供了正确的类型。通用类型提供更好的性能,因为它们减少了对变量或对象进行装箱、拆箱、类型检查和类型转换的需要。
C#泛型方法
在下面的例子中,我们定义了一个通用的Swap
方法。
int x = 10; int y = 20; char a = 'x'; char b = 'y'; Swap<int>(ref x, ref y); Swap<char>(ref a, ref b); Console.WriteLine($"x = {x}, y = {y}"); Console.WriteLine($"a = {a}, b = {b}"); void Swap<T>(ref T lhs, ref T rhs) { T temp = lhs; lhs = rhs; rhs = temp; }
Swap
方法交换两个变量的值。通用Swap
方法有一个定义,但用于两种不同的类型。
Swap<int>(ref x, ref y); Swap<char>(ref a, ref b);
我们为整数值和字符值调用Swap
方法。该方法的类型规范是可选的,因为编译器可以从参数中推断出类型。
void Swap<T>(ref T lhs, ref T rhs) { T temp = lhs; lhs = rhs; rhs = temp; }
大写T
用作占位符,在编译时被替换为特定类型。
$ dotnet run x = 20, y = 10 a = y, b = x
接下来我们有一个通用的Debug
方法。它打印传递的泛型参数的类型和值。
string s = "falcon"; bool b = true; int i = 42; float f = 4.4f; double d = 2.0; Debug<string>(s); Debug<bool>(b); Debug<int>(i); Debug<float>(f); Debug<double>(d); void Debug<T>(T arg) { Type type = arg.GetType(); if (type == typeof(string)) { Console.WriteLine($"[String]: {arg}"); } else if (type == typeof(bool)) { Console.WriteLine($"[Boolean]: {arg}"); } else if (type == typeof(int)) { Console.WriteLine($"[Integer]: {arg}"); } else if (type == typeof(float)) { Console.WriteLine($"[Float]: {arg}"); } else if (type == typeof(double)) { Console.WriteLine($"[Double]: {arg}"); } }
要获取参数的类型,我们使用GetType
方法。
$ dotnet run [String]: falcon [Boolean]: True [Integer]: 42 [Float]: 4.4 [Double]: 2
在下一个示例中,我们将创建一个随机排列列表的方法。
var rng = new Random(); var vals = new List<int> { 1, 2, 3, 4, 5, 6 }; var words = new List<string> { "sky", "blue", "war", "toy", "tick" }; Shuffle<int>(vals); Shuffle<string>(words); foreach (var e in vals) { Console.Write($"{e} "); } Console.WriteLine("\n-----------------------"); foreach (var e in words) { Console.Write($"{e} "); } Console.WriteLine(); void Shuffle<T>(IList<T> vals) { int n = vals.Count; while (n > 1) { n--; int k = rng.Next(n + 1); T value = vals[k]; vals[k] = vals[n]; vals[n] = value; } }
我们打乱一个整数和单词列表。
$ dotnet run 6 2 3 1 4 5 ----------------------- toy war sky blue tick $ dotnet run 2 5 1 4 3 6 ----------------------- war sky blue toy tick
接下来我们创建列表访问的方法。
var vals = new List<int> { 1, 2, 3, 4, 5, 6 }; var words = new List<string> { "sky", "blue", "war", "toy", "tick" }; Console.WriteLine(First(vals)); Console.WriteLine(Second(vals)); Console.WriteLine(Last(vals)); Console.WriteLine(SecondLast(vals)); Console.WriteLine("--------------"); Console.WriteLine(First(words)); Console.WriteLine(Second(words)); Console.WriteLine(Last(words)); Console.WriteLine(SecondLast(words)); T First<T>(IList<T> items) => items[0]; T Second<T>(IList<T> items) => items[1]; T Last<T>(IList<T> items) => items[^1]; T SecondLast<T>(IList<T> items) => items[^2];
在示例中,我们有四种通用方法来获取列表的第一个、第二个、最后一个和最后一个元素。
Console.WriteLine(First(vals)); Console.WriteLine(Second(vals)); ...
编译器根据我们传递给方法的值推断出参数的类型;因此,指定类型(例如First
)是可选的。
$ dotnet run 1 2 6 5 -------------- sky blue tick toy
C#泛型委托
在下面的示例中,我们定义了一个通用委托。dynamic
关键字告诉编译器其变量的类型是在运行时指定的。
var mv1 = new ModifyVal<int>(Inc); var mv2 = new ModifyVal<int>(Dec); var mv3 = new ModifyVal<float>(Inc); var mv4 = new ModifyVal<float>(Dec); Console.WriteLine(mv1(7)); Console.WriteLine(mv2(7)); Console.WriteLine(mv3(8f)); Console.WriteLine(mv4(8f)); T Inc<T>(T val) { dynamic x = val; return ++x; } T Dec<T>(T val) { dynamic x = val; return --x; } delegate T ModifyVal<T>(T fn);
我们定义了一个通用委托ModifyVal
;它用于指代两个泛型方法:Inc
和Dec
。
T Inc<T>(T val) { dynamic x = val; return ++x; }
Inc
是一个泛型方法;它接受一个通用参数并返回一个通用值。dynamic
关键字有助于简化代码:我们不必在此处进行类型检查。
delegate T ModifyVal<T>(T fn);
这是通用委托的定义。
$ dotnet run 8 6 9 7
C#泛型类
接下来,我们定义一个泛型类。
var ds1 = new DataStore<string>(); ds1.Data = "an old falcon"; Console.WriteLine(ds1); var ds2 = new DataStore<int>(); ds2.Data = 23; Console.WriteLine(ds2); var ds3 = new DataStore<bool>(); ds3.Data = false; Console.WriteLine(ds3); class DataStore<T> { public T Data { get; set; } public override string ToString() { return Data.ToString(); } }
该示例将值存储在通用字段中。
$ dotnet run an old falcon 23 False
C#泛型FindAll
在下一个示例中,我们定义了一个FindAll
列表扩展方法。
public static class ExtensionMethods { public static List<T> FindAll<T>(this List<T> vals, List<Predicate<T>> preds) { List<T> data = new List<T>(); foreach (T e in vals) { bool pass = true; foreach (Predicate<T> p in preds) { if (!(p(e))) { pass = false; break; } } if (pass) data.Add(e); } return data; } }
FindAll
方法返回填充所有指定谓词的列表元素。
public static List<T> FindAll<T>(this List<T> vals, List<Predicate<T>> preds)
FindAll
方法将通用谓词函数列表作为参数。它返回过滤后的通用列表。
var preds = new List<Predicate<int>>(); preds.Add(e => e > 0); preds.Add(e => e % 2 == 0); var vals = new List<int> {-3, -2, -1, 0, 1, 2, 3, 4}; var filtered = vals.FindAll(preds); foreach (var e in filtered) { Console.WriteLine(e); } Console.WriteLine("---------------------"); var words = new List<string> {"sky", "wrath", "wet", "sun", "pick", "who", "cloud", "war", "water", "jump", "ocean"}; var preds2 = new List<Predicate<string>>(); preds2.Add(e => e.StartsWith("w")); preds2.Add(e => e.Length == 3); var filtered2 = words.FindAll(preds2); foreach (var e in filtered2) { Console.WriteLine(e); }
我们定义了两个列表:一个整数列表和一个字符串列表。从整数列表中,我们过滤掉所有正偶数。从字符串列表中,我们得到所有以’w’开头且具有三个字母的单词。
$ dotnet run 2 4 --------------------- wet who war
C#泛型约束
泛型定义中的where
子句指定了对类型的约束,这些类型用作泛型类型中类型参数的实参。
var animals = new List<Animal<Cat>> { new Animal<Cat>(), new Animal<Cat>(), new Animal<Cat>() }; foreach (var animal in animals) { Console.WriteLine(animal); } interface IAnimal { } class Cat : IAnimal { } class Lion : IAnimal { } class Dog : IAnimal { } class Flower {} class Animal<T> where T : IAnimal { }
在示例中,我们定义了一个Animal
类,将T
类型限制为IAnimal
。
C#通用列表迭代器
以下示例创建了一个通用迭代器。
var words = new List<string> { "sky", "cloud", "rock", "war", "web" }; var it = CreateIterator(words); string e; while ((e = it()) != null) { Console.WriteLine(e); } Iterator<T> CreateIterator<T>(IList<T> data) where T : class { var i = 0; return delegate { return (i < data.Count) ? data[i++] : null; }; } public delegate T Iterator<T>() where T : class;
迭代器使用匿名委托。
$ dotnet run sky cloud rock war web
C#通用列表forEach
以下示例为列表容器创建一个通用的forEach方法。
Action<int> show = Console.WriteLine; var vals = new List<int> { 1, 2, 3, 4, 5, 6, 7 }; forEach(vals, show); Console.WriteLine("--------------------------"); var words = new List<string> { "sky", "cloud", "rock", "water" }; forEach(words, Console.WriteLine); Console.WriteLine("--------------------------"); var users = new List<User> { new ("John Doe", "gardener"), new ("Roger Roe", "driver"), }; forEach(users, Console.WriteLine); void forEach<T>(IList<T> vals, Action<T> fn) { foreach (T val in vals) { fn(val); } } record User(string Name, string Occupation);
通用的forEach方法遍历三个列表容器的整数、字符串和User
元素。
void forEach<T>(IList<T> vals, Action<T> fn)
forEach
方法采用通用列表和通用Action
委托作为参数。
$ dotnet run 1 2 3 4 5 6 7 -------------------------- sky cloud rock water -------------------------- User { Name = John Doe, Occupation = gardener } User { Name = Roger Roe, Occupation = driver }
C#泛型字典ForEach
在下一个示例中,我们为字典创建一个通用的ForEach
方法。
var domains = new Dictionary<string, string> { {"sk", "Slovakia"}, {"ru", "Russia"}, {"de", "Germany"}, {"no", "Norway"} }; domains.ForEach((k, v) => Console.WriteLine($"{k} - {v}")); var vals = new Dictionary<int, string> { {1, "coin"}, {2, "pen"}, {3, "pencil"}, {4, "book"} }; vals.ForEach((k, v) => Console.WriteLine($"{k} - {v}")); static class DictionaryExtension { public static void ForEach<T1, T2>(this Dictionary<T1, T2> dict, Action<T1, T2> fn) { foreach(KeyValuePair<T1, T2> pair in dict) { fn(pair.Key, pair.Value); } } }
ForEach
方法被定义为扩展方法。在Action
委托中,我们使用foreach
循环遍历字典对。
$ dotnet run sk - Slovakia ru - Russia de - Germany no - Norway 1 - coin 2 - pen 3 - pencil 4 - book
在本文中,我们介绍了C#中的泛型编程。
列出所有C#教程。