跳转到内容

C# 编程/泛型

来自维基教科书,自由的教科书

泛型是 C# 语言和公共语言运行时 (CLR) 2.0 版本后引入的新特性。泛型在 .NET Framework 中引入了类型参数的概念,它使您可以设计在类或方法被客户端代码声明和实例化之前推迟一个或多个类型规范的类和方法。泛型的最常见用途是创建集合类。泛型类型的引入是为了 最大化代码重用最大化类型安全性能[1]

泛型类

[编辑 | 编辑源代码]

在某些情况下,您需要创建一个类来管理某种类型的对象,而无需对其进行修改。在没有泛型的情况下,创建这种类的通常方法(高度简化)是这样的

public class SomeObjectContainer
{
    private object _obj;

    public SomeObjectContainer(object obj)
    {
        this._obj = obj;
    }

    public object GetObject()
    {
        return this._obj;
    }
}

它的用法是

class Program
{
    static void Main(string[] args)
    {
        SomeObjectContainer container = new SomeObjectContainer(25);
        SomeObjectContainer container2 = new SomeObjectContainer(5);

        Console.WriteLine((int) container.GetObject() + (int) container2.GetObject());
        Console.ReadKey(); // wait for user to press any key, so we could see results
    }
}

注意,我们必须每次想要从这样的容器中获取对象时都强制转换为我们选择的原始数据类型(在本例中为 int)。在像这样的小型程序中,一切都清楚。但在更复杂的程序中,如果程序的不同部分存在多个容器,我们必须注意容器应该是 int 类型,而不是其他数据类型,因为在这种情况下,将抛出 InvalidCastException

此外,如果我们选择的原始数据类型是值类型,例如 int,那么每次访问集合的元素时,我们都会因 C# 的 自动装箱 特性而造成性能损失。

但是,我们可以用 try - catch 块包围每个不安全区域,或者我们可以为需要的每个数据类型创建一个单独的“容器”,以避免强制转换。虽然这两种方法都可以工作(并且多年来一直有效),但现在已经没有必要了,因为泛型提供了更优雅的解决方案。

为了使我们的“容器”类支持任何对象并避免强制转换,我们将每个以前的 object 类型替换为一个新名称,在本例中为 T,并在类名后添加 <T> 标记以指示此 T 类型是泛型/任何类型。

注意:您可以选择任何名称,并为类使用多个泛型类型,例如 <genKey, genVal>
public class GenericObjectContainer<T>
{
    private T _obj;

    public GenericObjectContainer(T obj)
    {
        this._obj = obj;
    }

    public T getObject()
    {
        return this._obj;
    }
}

没有太大的区别,这导致了简单而安全的用法

class Program
{
    static void Main(string[] args)
    {
        GenericObjectContainer<int> container = new GenericObjectContainer<int>(25);
        GenericObjectContainer<int> container2 = new GenericObjectContainer<int>(5);
        Console.WriteLine(container.getObject() + container2.getObject());

        Console.ReadKey(); // wait for user to press any key, so we could see results
    }
}

泛型确保您只为“容器”指定一次类型,避免了前面提到的问题,以及对 struct 的自动装箱。

虽然这个例子远非实用,但它确实说明了泛型在某些情况下很有用

  • 您需要在一个类中保留单一类型的对象
  • 您不需要修改对象
  • 您需要以某种方式操作对象
  • 您希望将“值类型”(如 intshortstring 或任何自定义 struct)存储在集合类中,而不会在每次操作存储的元素时都产生自动装箱的性能损失。

泛型接口

[编辑 | 编辑源代码]

泛型接口接受一个或多个类型参数,类似于泛型类

public interface IContainer<T>
{
    T GetObject();
    void SetObject(T value);
}

public class StringContainer : IContainer<string>
{
    private string _str;
    
    public string GetObject()
    {
        return _str;
    }
    
    public void SetObject(string value)
    {
        _str = value;
    }
}

public class FileWithString : IContainer<string>
{
    ...
}

class Program
{
    static void Main(string[] args)
    {
        IContainer<string> container = new StringContainer();
        
        container.SetObject("test");

        Console.WriteLine(container.GetObject());
        container = new FileWithString();

        container.SetObject("another test");

        Console.WriteLine(container.GetObject());
        Console.ReadKey();
    }
}

泛型接口在可能存在某个类的多个实现时很有用。例如,来自 System.Collections.Generic 命名空间的 List<T> 类(将在下面讨论)和 LinkedList<T> 类都实现了 IEnumerable<T> 接口。List<T> 有一个构造函数,它根据实现 IEnumerable<T> 的现有对象创建一个新列表,因此我们可以编写以下代码

LinkedList<int> linkedList = new LinkedList<int>();

linkedList.AddLast(1);
linkedList.AddLast(2);
linkedList.AddLast(3);
// linkedList now contains 1, 2 and 3.

List<int> list = new List<int>(linkedList);

// now list contains 1, 2 and 3 as well!

泛型方法

[编辑 | 编辑源代码]

泛型方法与泛型类和接口非常相似

using System;
using System.Collections.Generic;

public static bool ArrayContains<T>(T[] array, T element)
{
    foreach (T e in array)
    {
        if (e.Equals(element))
        {
            return true;
        }
    }

    return false;
}

此方法可用于搜索任何类型的数组

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        string[] strArray = { "string one", "string two", "string three" };
        int[] intArray = { 123, 456, 789 };
        
        Console.WriteLine(ArrayContains<string>(strArray, "string one")); // True
        Console.WriteLine(ArrayContains<int>(intArray, 135)); // False
    }
}

类型约束

[编辑 | 编辑源代码]

可以使用 where 关键字在任何泛型类、接口或方法中指定一个或多个类型约束。以下示例显示了所有可能的类型约束

public class MyClass<T, U, V, W>
    where T : class,        // T should be a reference type (array, class, delegate, interface)
        new()               // T should have a public constructor with no parameters
    where U : struct        // U should be a value type (byte, double, float, int, long, struct, uint, etc.)
    where V : MyOtherClass, // V should be derived from MyOtherClass
        IEnumerable<U>      // V should implement IEnumerable<U>
    where W : T,            // W should be derived from T
        IDisposable         // W should implement IDisposable
{
    ...
}

这些类型约束通常是必要的,以便

  1. 创建一个新的泛型类型实例(new())约束
  2. 对泛型类型的变量使用 foreachIEnumerable<T> 约束)
  3. 对泛型类型的变量使用 usingIDisposable 约束)
  1. "Generics (C# Programming Guide)". http://msdn.microsoft.com/en-us/: msdn. Retrieved 2011-08-09. {{cite web}}: External link in |location= (help)
华夏公益教科书