C#泛型编程:深入探究泛型的威力

发布时间 2023-12-11 20:15:44作者: 52Hertz程序人生

文章目录

泛型(Generic)

泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。

C#的泛型,无论是源码,IL语言,还是CLR中,都是完全不同的类型。也就是在泛型的基础上每多指定一个泛型,就会多出一个类,这个类是确实存在的,在IL中的名称为XXClass`|,这些多出来的类型有自己的虚方法表和类型数据,这种实现称为类型膨胀,所以C#的泛型为真实泛型。

泛型(Generic)的特性

使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:

  • 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
  • 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
  • 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
  • 您可以对泛型类进行约束以访问特定数据类型的方法。
  • 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。

泛型是个好东西,能够有效的约束烂代码,还能更显式的提醒开发人员该如何使用这个类。使用泛型的情况有很多,但对于新人来说理解易应用难,需要经验的积累才能熟练应用。

泛型使得类与类的关联更加稳定,使代码更加严谨。

泛型约束

如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。泛型约束是泛型的核心。

下表列出了五种类型的约束:

 

派生约束

1.常见的

public class MyClass5<T> where T :IComparable { }

2.约束放在类的实际派生之后

public class B { }

public class MyClass6<T> : B where T : IComparable { }

3.可以继承一个基类和多个接口,且基类在接口前面

public class B { }

public class MyClass7<T> where T : B, IComparable, ICloneable { }

构造函数约束

1.常见的

public class MyClass8<T> where T :  new() { }

2.可以将构造函数约束和派生约束组合起来,前提是构造函数约束出现在约束列表的最后

public class MyClass8<T> where T : IComparable, new() { }

值约束

1.常见的

public class MyClass9<T> where T : struct { }

2.与接口约束同时使用,在最前面(不能与基类约束,构造函数约束一起使用)

public class MyClass11<T> where T : struct, IComparable { }

引用约束

public class MyClass10<T> where T : class { }

多个泛型参数

public class MyClass12<T, U> where T : IComparable  where U : class { }

泛型类

最常见的泛型类,自定义Collection:

using System;
using System.Collections.Generic;

namespace GenericApplication
{
    public class MyGenericArray<T>
    {
        private T[] array;
        public MyGenericArray(int size)
        {
            array = new T[size + 1];
        }
        public T getItem(int index)
        {
            return array[index];
        }
        public void setItem(int index, T value)
        {
            array[index] = value;
        }
    }
           
    class Tester
    {
        static void Main(string[] args)
        {
            // 声明一个整型数组
            MyGenericArray<int> intArray = new MyGenericArray<int>(5);
            // 设置值
            for (int c = 0; c < 5; c++)
            {
                intArray.setItem(c, c*5);
            }
            // 获取值
            for (int c = 0; c < 5; c++)
            {
                Console.Write(intArray.getItem(c) + " ");
            }
            Console.WriteLine();
            // 声明一个字符数组
            MyGenericArray<char> charArray = new MyGenericArray<char>(5);
            // 设置值
            for (int c = 0; c < 5; c++)
            {
                charArray.setItem(c, (char)(c+97));
            }
            // 获取值
            for (int c = 0; c < 5; c++)
            {
                Console.Write(charArray.getItem(c) + " ");
            }
            Console.WriteLine();
            Console.ReadKey();
        }
    }
}

继承泛型约束

先提供一个泛型基类B:

public class B<T>{ }

1.在从泛型基类派生时,可以提供类型实参,而不是基类泛型参数

public class SubClass11 : B<int>
{ }

2.如果子类是泛型,而非具体的类型实参,则可以使用子类泛型参数作为泛型基类的指定类型

public class SubClass12<R> : B<R>
{ }

3.在子类重复基类的约束(在使用子类泛型参数时,必须在子类级别重复在基类级别规定的任何约束)

public class B<T> where T : ISomeInterface { }
public class SubClass2<T> : B<T> where T : ISomeInterface { }

4.构造函数约束

public classB<T> where T : new()
{
    public T SomeMethod()
    {
        return new T();
    }
}
public class SubClass3<T> : B<T> where T : new(){ }

泛型方法

我们可以通过类型参数声明泛型方法。下面的程序说明了这个概念:

using System;
using System.Collections.Generic;

namespace GenericMethodAppl
{
    class Program
    {
        static void Swap<T>(ref T lhs, ref T rhs)
        {
            T temp;
            temp = lhs;
            lhs = rhs;
            rhs = temp;
        }
        static void Main(string[] args)
        {
            int a, b;
            char c, d;
            a = 10;
            b = 20;
            c = 'I';
            d = 'V';

            // 在交换之前显示值
            Console.WriteLine("Int values before calling swap:");
            Console.WriteLine("a = {0}, b = {1}", a, b);
            Console.WriteLine("Char values before calling swap:");
            Console.WriteLine("c = {0}, d = {1}", c, d);

            // 调用 swap
            Swap<int>(ref a, ref b);
            Swap<char>(ref c, ref d);

            // 在交换之后显示值
            Console.WriteLine("Int values after calling swap:");
            Console.WriteLine("a = {0}, b = {1}", a, b);
            Console.WriteLine("Char values after calling swap:");
            Console.WriteLine("c = {0}, d = {1}", c, d);
            Console.ReadKey();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

Int values before calling swap:
a = 10, b = 20
Char values before calling swap:
c = I, d = V
Int values after calling swap:
a = 20, b = 10
Char values after calling swap:
c = V, d = I

泛型方法的重载

//第一组重载
void MyMethod1<T>(T t, int i){ }

void MyMethod1<U>(U u, int i){ }

//第二组重载
void MyMethod2<T>(int i){ }
void MyMethod2(int i){ }

//第三组重载,假设有两个泛型参数
void MyMethod3<T>(T t) where T : A { }
void MyMethod3<T>(T t) where T : B { }

//第四组重载
public class MyClass8<T,U>
{
    public T MyMothed(T a, U b)
    {
        return a;
    }
    public T MyMothed(U a, T b)
    {
        return b;
    }
    public int MyMothed(int a, int b)
    {
        return a + b;
    }
}

泛型方法的重写

public class MyBaseClass1
{
    public virtual void MyMothed<T>(T t) where T : new() { }
}
public class MySubClass1 : MyBaseClass1
{
    public override void MyMothed<T>(T t) // 不能重复任何约束
    { }
}

public class MyBaseClass2
{
    public virtual void MyMothed<T>(T t)
    { }
}
public class MySubClass2 : MyBaseClass2
{
    public override void MyMothed<T>(T t) // 重新定义泛型参数T
    { }
}

虚方法泛型

  • 使用实参继承的时候方法要使用实参的类型
  • 使用泛型继承时,方法也是泛型
public class BaseClass4<T>
{
    public virtual T SomeMethod()
    {
        return default(T);
    }
}
public class SubClass4 : BaseClass4<int> // 使用实参继承的时候方法要使用实参的类型
{
    public override int SomeMethod()
    {
        return 0;
    }
}

public class SubClass5<T> : BaseClass4<T> // 使用泛型继承时,方法也是泛型
{
    public override T SomeMethod()
    {
        return default(T);
    }
}

泛型委托

委托级别的约束只在声明委托变量和实例化委托时使用,类似于在类型和方法的作用范围中实施的其他任何约束。
您可以通过类型参数定义泛型委托。例如:

delegate T NumberChanger<T>(T n);

下面的实例演示了委托的使用:

using System;
using System.Collections.Generic;

delegate T NumberChanger<T>(T n);
namespace GenericDelegateAppl
{
    class TestDelegate
    {
        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }

        public static int MultNum(int q)
        {
            num *= q;
            return num;
        }
        public static int getNum()
        {
            return num;
        }

        static void Main(string[] args)
        {
            // 创建委托实例
            NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
            NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
            // 使用委托对象调用方法
            nc1(25);
            Console.WriteLine("Value of Num: {0}", getNum());
            nc2(5);
            Console.WriteLine("Value of Num: {0}", getNum());
            Console.ReadKey();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

Value of Num: 35
Value of Num: 175

通过这种方式,就能够实现多个类型共同使用同一个委托,你传给我什么类型,我就给你返回什么类型。

泛型强转

泛型参数隐式强制转换

编译器只允许将泛型参数隐式强制转换到 Object 或约束指定的类型。

class MyClass<T> where T : BaseClass, ISomeInterface
{
    void SomeMethod(T t)
    {
        ISomeInterface obj1 = t;
        BaseClass obj2 = t;
        object obj3 = t;
    }
}

变通方法:使用临时的 Object 变量,将泛型参数强制转换到其他任何类型

class MyClass2<T>
{
    void SomeMethod(T t)
    {
        object temp = t;
        BaseClass obj = (BaseClass)temp;
    }
}

泛型参数显示强制转换

编译器允许您将泛型参数显式强制转换到其它任何接口,但不能将其转换到类。

class MyClass1<T>
{
    void SomeMethod(T t)
    {
        ISomeInterface obj1 = (ISomeInterface)t;   // 接口可以强转
        //BaseClass obj2 = (BaseClass)t;           // 普通类不能强转,不能通过编译
    }
}

泛型参数强制转换到其他任何类型

使用临时的 Object 变量,将泛型参数强制转换到其他任何类型。

class MyClass2<T>
{
    void SomeMethod(T t)
    {
        object temp = t;
        BaseClass obj = (BaseClass)temp;
    }
}

使用is和as运算符

public class MyClass3<T>
{
    public void SomeMethod(T t)
    {
        if (t is int) { }
        if (t is LinkedList<int>) { }
        string str = t as string;
        if (str != null) { }
        LinkedList<int> list = t as LinkedList<int>;
        if (list != null) { }
    }
}

总结

泛型(Generic)是 C# 中的一种编程机制,允许开发者编写出更加灵活、可重用和类型安全的代码。这是一个非常强大和灵活的概念,它影响着许多方面的代码设计和性能优化。以下是对泛型的更详细总结:

  1. 类型参数化: 泛型允许在类、接口、方法等中使用类型参数,使得代码更通用、更灵活。这些类型参数允许在使用时指定具体的类型,从而实现对不同类型的操作。

  2. 代码复用和灵活性: 泛型使得可以编写更少但更通用的代码,因为可以用相同的结构处理不同类型的数据,从而提高了代码的重用性和灵活性。

  3. 类型安全和编译时检查: 泛型代码在编译时会进行类型检查,因此可以捕获并预防许多与类型相关的错误。这种类型安全性在编写更健壮的代码方面非常有用。

  4. 高性能和避免装箱拆箱: 泛型避免了装箱和拆箱的需求,因为它们允许类型参数化,减少了在运行时的类型转换开销,因此可以提高性能。

  5. 泛型集合和算法: C# 中的泛型集合类(如 List<T>Dictionary<TKey, TValue> 等)和算法利用泛型,允许在集合和算法上以一种类型安全的方式进行操作。

  6. 约束(Constraints): 泛型允许对类型参数进行约束,例如要求类型参数必须具有特定的基类、接口或者具有特定的构造函数,从而在泛型代码中增加了更多的灵活性和控制力。

  7. 泛型方法和泛型类: 可以在类和方法中使用泛型,这使得代码更加灵活且可扩展,能够在不同的场景下处理不同类型的数据。

  8. 框架和库的泛型设计: C# 标准库和许多第三方库中都广泛使用了泛型设计,这样提供了更高效、更安全、更灵活的编程接口,提升了开发体验和性能。

总的来说,泛型是 C# 中非常重要的一个特性,它提供了一种强大的方式来编写通用、高效和类型安全的代码,有助于提高代码质量、可读性和可维护性。