图解C#高级教程(三):泛型

本讲用许多代码示例介绍了 C# 语言当中的泛型,主要包括泛型类、接口、结构、委托和方法。

1. 为什么需要泛型?

在之前的教程中,我们使用的自定义类都是具有具体的类型。如果对类型进行抽象,该类型就是泛型,即一种广泛的类型。例如,我们有一个水果篮,水果篮中可以放不同种类的水果,例如苹果、香蕉、菠萝等等。这个水果篮可以放置的水果就是一种泛型,水果可以代指很多不同种类的水果。

为什么需要泛型?泛型的好处之一就是能够提高代码的复用性。例如,我们有一种栈数据结构,栈当中的类型可以是 intfloat等等,但很多操作都是相同的:入栈、出栈、获取栈大小,所不同的是数据类型的不同。利用泛型,我们只需要实现一套代码,而不需要针对不同的数据类型分别实现各自的一套栈代码。

在 C# 中,泛型又称为参数化类型。泛型可以作用在类、函数、结构、委托和接口,由此衍生出泛型类、泛型函数、泛型结构、泛型委托和泛型接口的概念。
在这里插入图片描述

2. 泛型类的定义

2.1 泛型类的定义

2.2 使用泛型类创建变量和实例

在使用泛型类创建变量时,可以使用关键字 var,让编译器根据 = 右边的类型自动推断变量的类型。

using System;

class SomeClass<T1, T2>
{
    public T1 Property1 { get; set; }
    public T2 Property2 { get; set; }

    // Constructor
    public SomeClass(T1 property1, T2 property2)
    {
        Property1 = property1;
        Property2 = property2;
    }
}
class Program
{
    static void Main(string[] args)
    {
        SomeClass<int, float> sc = new SomeClass<int, float>(10, 3.14f);
        var sc2 = new SomeClass<string, bool>("Hello", true);  // 让编译器推断类型
        Console.WriteLine(sc.Property1);
        Console.WriteLine(sc.Property2);
        Console.WriteLine(sc2.Property1);
        Console.WriteLine(sc2.Property2);
    }
}

输出:

10
3.14
Hello
true

需要注意的是,为具体类分配的内存是存储在堆上的。

3. 使用泛型类实现一个简单的栈

using System;

namespace GenericStackExample
{
    // 定义一个泛型栈类  
    public class Stack<T>
    {
        // 使用数组来存储栈中的元素  
        private T[] _items;
        // 栈顶元素的索引(初始化为-1表示栈为空)  
        private int _top;
        // 栈的容量  
        private int _capacity;
        
        // 构造函数,初始化栈的容量  
        public Stack(int capacity = 10)
        {
            _capacity = capacity;
            _items = new T[capacity];
            _top = -1;
        }
        // 检查栈是否为空  
        public bool IsEmpty()
        {
            return _top == -1;
        }
        // 检查栈是否已满  
        public bool IsFull()
        {
            return _top == _capacity - 1;
        }
        // 入栈操作  
        public void Push(T item)
        {
            if (IsFull())
            {
                throw new InvalidOperationException("Stack is full.");
            }

            _items[++_top] = item;
        }
        // 出栈操作  
        public T Pop()
        {
            if (IsEmpty())
            {
                throw new InvalidOperationException("Stack is empty.");
            }

            return _items[_top--];
        }
        // 查看栈顶元素(不移除)  
        public T Peek()
        {
            if (IsEmpty())
            {
                throw new InvalidOperationException("Stack is empty.");
            }
            return _items[_top];
        }
        // 获取栈的大小(元素数量)  
        public int Size()
        {
            return _top + 1;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Stack<int> intStack = new Stack<int>(5); // 创建一个整型栈  
            // 入栈操作  
            intStack.Push(1);
            intStack.Push(2);
            intStack.Push(3);

            // 访问栈顶元素  
            Console.WriteLine($"栈顶元素是: {intStack.Peek()}");
            // 出栈操作  
            Console.WriteLine($"出栈元素: {intStack.Pop()}");
            // 获取栈的大小  
            Console.WriteLine($"栈的大小是: {intStack.Size()}");
            // 尝试在空栈上执行出栈操作以演示异常  
            try
            {
                intStack.Pop();
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

3.1 类型参数的约束

现在我们能够设计出泛型类,但是泛型类型它本身提供什么方法,我们是不知道的。例如下面的泛型类:

class Simple<T>
{
	static public bool LessThan(T i1, T i2)
	{
		return i1 < i2;
	}
}
...

但不是所有类型 T 都实现了小于运算符,所以会报出错误:
在这里插入图片描述
为此,我们需要告诉编译器关于泛型类型的额外信息,让其知道参数可以接受哪些类型。这些额外的信息叫做约束(constrain)。没有任何约束的类型参数称为未绑定的类型参数(unbounded type parameter)。

3.2 Where 子句

约束使用 where 子句列出:
在这里插入图片描述

3.3 约束类型和次序

共有 5 种类型的约束,如下表所示:

约束类型描述
某个具体的类名只有这个类型的类或从它继承的类才能用作类型参数
class任何引用类型,包括类、数组、委托和接口都可以用作类型参数
struct任何值类型都可以用作类型参数
接口名只有这个接口或实现这个接口的类型才能用作类型参数
new()任何带有无参公共构造函数的类型都可以用作类型参数。这叫做构造函数约束

对不同类型的 where 约束可以以任何顺序列出。但是,where 子句内的约束必须遵循一定的顺序:

  • 最多只能具有一个主约束,有的话必须放到第一位;
  • 可以有任意多的接口名约束;
  • 如果存在构造函数约束,则必须放到最后。
    在这里插入图片描述
    下面是一些例子:
class SortedList<S>
    where S: IComparable<S>
{ }

class LinkedList<M, N>
    where M: IComparable<M>
    where N: ICloneable
{ }

class MyDictionary<KeyType, ValueType>
    where KeyType: IComparable<KeyType>,
    new()
{ }

4. 泛型方法

泛型方法的定义如下:
在这里插入图片描述

泛型方法的使用:

void DoStuff<T1, T2> (T1 t1, T2 t2)
{
    T1 someVar = t1;
    T2 otherVar = t2;
}

DoStuff<int, string>(10, "Hello");
DoStuff<int, long>(iVal, lVal);

当参数类型列表的类型和方法列表中的类型相同时,在使用泛型方法时可以省略对参数类型的指定,例如:

public void MyMethod<T> (T val) {}
int myInt = 5;
MyMethod(myInt);

泛型方法的使用示例:

class Simple
{
    static public void ReverseAndPrint<T>(T[] arr)
    {
        Array.Reverse(arr);
        foreach (var item in arr)
        {
            Console.Write("{0}, ", item.ToString());
            Console.WriteLine();
        }
    }
}

class Program
{
    static void Main()
    {
        // 创建整数、字符串、浮点型数组  
        int[] intArray = { 1, 2, 3, 4, 5 };
        string[] stringArray = { "hello", "world" };
        float[] floatArray = { 1.1f, 2.2f, 3.3f };

        // 调用泛型方法,反转并打印数组  
        Simple.ReverseAndPrint(intArray);
        Simple.ReverseAndPrint<int>(intArray);

        Simple.ReverseAndPrint(stringArray);
        Simple.ReverseAndPrint<string>(stringArray);

        Simple.ReverseAndPrint(floatArray);
        Simple.ReverseAndPrint<float>(floatArray);

    }
}

输出结果:
在这里插入图片描述

5. 泛型结构

泛型结构和泛型类的定义类似,直接给出例子。

struct PieceOfData<T>
{
    public T _data { get; set; }
    public PieceOfData(T data)
    {
        _data = data;
    }
}

class Program
{
    static void Main()
    {
        PieceOfData<int> piece1 = new PieceOfData<int>(10);
        PieceOfData<string> piece2 = new PieceOfData<string>("Hello");

        Console.WriteLine(piece1._data);
        Console.WriteLine(piece2._data);
    }
}

6. 泛型委托

在这里插入图片描述
委托类型当中可以使用泛型的地方:

  • 返回类型
  • 类型参数
  • where子句

泛型委托的例子:

delegate void MyDelegate<T>(T value);

class Simple
{
    static public void PrintString(string s)
    {
        Console.WriteLine(s);
    }

    static public void PrintUpperString(string s)
    {
        Console.WriteLine(s.ToUpper());
    }
}

class Prgram
{
    static void Main()
    {
        MyDelegate<string> d1 = new MyDelegate<string>(Simple.PrintString);
        d1 += Simple.PrintUpperString;

        d1("Hello");
    }
}

输出:

hello
HELLO

7. 泛型接口

interface IMyIfc<T>
{
    T ReturnIt(T invalue);
}

class Simple: IMyIfc<int>, IMyIfc<string>
{
    // 因为 Simple 类实现了两个接口,所以它必须实现两个接口的相同方法。
    public int ReturnIt(int invalue)
    {
        return invalue;
    }

    public string ReturnIt(string invalue)
    {
        return invalue;
    }
}

class Program
{
    static void Main()
    {
        IMyIfc<int> intIfc = new Simple();
        IMyIfc<string> stringIfc = new Simple();

        Console.WriteLine(intIfc.ReturnIt(10));
        Console.WriteLine(stringIfc.ReturnIt("Hello"));
    }
}

本章小结:主要通过例子讲解了 C# 语言当中泛型{类、接口、结构、委托、方法}的用法。

各位道友,码字不易,记得一键三连啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值