泛型

如果一个类成员允许使用多种数据类型(例如设计一个通用的数据结构),一种实现方法是将类成员指定为object类型,但这种写法代码可读性较差。C#2.0引入了泛型,泛型可以实现将类成员的类型作为一个参数抽离出来,在某些场景下使用泛型定义类,能够极大提高代码的可读性和可复用性。

泛型定义和使用

C#中的类、方法、接口、委托都支持泛型。

泛型类

泛型类的参数使用一对尖括号<>指定,泛型参数允许存在多个,使用逗号分隔。下面例子我们定义了一个泛型类,两个属性DataADataB的数据类型是通过泛型参数TU指定的,这里类实例化时会传入具体的类型名。

class Container<T, U>
{
    public T DataA { get; set; }
    public U DataB { get; set; }
}

实例化泛型类时需要传入对应的类型参数,写法如下:

Container<int, string> container = new Container<int, string>();
container.DataA = 1;
container.DataB = "hello";

泛型方法

泛型方法的声明和泛型类差不多,也是增加一组泛型参数。下面例子中,我们在一个普通的类里声明了泛型方法。

class A
{
    public void Foo<T, U>(T t, U u)
    {
        Console.WriteLine(u);
        Console.WriteLine(t);
    }
}

调用泛型方法时,也可以使用尖括号传入泛型参数。

A a = new A();
a.Foo<int, string>(1, "hello");

不过实际上,这里我们声明的Foo方法的两个类型参数TU显然可以从其参数tu类型中推断出来,因此调用该方法时我们可以不必传泛型参数,写作a.Foo(1, "hello");也是可以的,但不能自动推断的时候就必须显示的写出泛型参数了。

约束泛型类型

我们定义泛型时,可以使用where关键字对其进行限制,例如:必须继承某个类或接口,必须有无参构造方法,必须是引用类型或是值类型等,这些约束还可以组合使用。类、方法、接口、委托都支持泛型约束。

约束 说明
where T : class T必须是引用类型
where T : struct T必须是值类型
where T : new() T必须有无参构造函数,如果和其他约束组合使用,new()必须放在最后
where T : 类名或接口名 T必须派生自某类或接口

下面代码对类的泛型进行了约束,T类型必须继承Animal类。

public class Zoo<T> where T : Animal { }

多个约束可以组合使用,使用逗号分隔。

public class Zoo<T> where T : Animal, new() { }

下面代码对方法的泛型进行了约束,T类型必须有无参构造函数。

public static T GetInstance<T>() where T : new() { }

C#和Java在泛型上的区别

C#的泛型和Java的泛型虽然写法相似,但有本质上的不同。

Java中的泛型是采用类型擦除实现的,这种实现方式经常被诟病为“假泛型”,所谓的假泛型是一种编译期的语法糖,且只适用于引用类型,Java字节码中使用的还是Object类型,只不过编译器帮我们添加了类型转换的代码。而C#泛型是运行时级别支持的,且适用于引用类型和值类型,这导致两者在用法上有很多区别,C#支持很多Java程序员想都不敢想的写法!

下面例子代码我们编写了一个实例化泛型类型的代码,代码中直接使用new关键字实例化了一个泛型类型,这在Java中是无法实现的,Java实现类似功能只能传入一个类型对象,然后使用反射来实例化,写法十分扭曲。

public class ServiceFactory
{
    public static T GetInstance<T>() where T : new()
    {
        T t = new T();
        return t;
    }
}

代码中我们使用了where T : new()约束保证T有无参构造函数。

调用该泛型方法:

StudentService s = ServiceFactory.GetInstance<StudentService>();
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。