开放的编程资料库

当前位置:我爱分享网 > C#教程 > 正文

C#泛型

C#泛型教程展示了如何在C#中定义和使用泛型。在泛型编程中,我们使用自定义类型作为参数来定义其他自定义类型。

C#2.0中添加了泛型。

泛型是类、结构、接口和方法,它们具有用于存储或使用的一种或多种类型的占位符(类型参数)。通用集合类可能使用类型参数作为它存储的对象类型的占位符。

通用类型名称在类、结构、接口或方法名称后的尖括号中提供。此语法告诉编译器定义中使用了这样的名称。

按照惯例,类型参数使用大写字母,例如TUTKey,以及TValue.

实际上,我们延迟了数据类型的规范,直到它在程序中实际使用。泛型使我们能够编写适用于各种类型的代码,而无需为每种类型重复代码。

void Swap<T>(ref T lhs, ref T rhs)

我们有一个泛型方法的定义。T参数稍后在调用方被替换为具体类型,例如intstring

泛型是抽象蓝图,它们不能按原样使用;当我们使用通用类或方法时,我们必须用替换为匹配占位符的特定数据类型来实例化它们。

.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;它用于指代两个泛型方法:IncDec

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#教程。

未经允许不得转载:我爱分享网 » C#泛型

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏