小白都能懂的CSharp基础精讲之泛型(Generic),你真的懂吗?

简介: 小白都能懂的CSharp基础精讲之泛型(Generic),你真的懂吗?

引入

如下代码,如果我们想用一个方法来代替该怎么办呢?

/// <summary>
/// 打印个int值
/// </summary>
/// <param name="iParameter"></param>
public static void ShowInt(int iParameter)
​{
     ​Console.WriteLine("This is {0},type={1},parameter={2}",
        ​typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter);
​}
/// <summary>
​/// 打印个string值
/// </summary>
/// <param name="sParameter"></param>
public static void ShowString(string sParameter)
{
    Console.WriteLine("This is {0},type={1},parameter={2}",
        typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter);
}
/// <summary>
/// 打印个DateTime值
/// </summary>
/// <param name="oParameter"></param>
public static void ShowDateTime(DateTime dtParameter)
{
    Console.WriteLine("This is {0},type={1},parameter={2}",
        typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter);
}​

很自然,我们会想到用object作为传入参数的类型,因为object是一切类的基类,这样,我们就可以通过一个方法来实现传入多种类型,如

public static void ShowObject(object oParameter)
{
    Console.WriteLine("This is {0},type={1},parameter={2}",
        typeof(CommonMethod), oParameter.GetType().Name, oParameter);
}

但是,使用object来实现时,可能会存在以下问题:

1.装箱拆箱会有性能损耗

比如传入一个int值时,在装箱拆箱时,需要在堆和栈之间进行操作。

2.类型安全问题,传递的对象没有限制,可能会有安全问题。如需要传入一个people,但是传入一个animal,类型无错误,但编译和运行可能有误。

 

泛型是什么?

为了解决以上问题,在.net 2.0时,提出了泛型概念,那么什么是泛型呢?

其实,泛型就是为了解决类似于上述问题的,可以说,泛型是用一个事物来代替多种不同类型需求的,泛型的实现采用的是延迟声明的方式,是不同于使用object的继承方式的。那么,泛型的实现具体需要哪些支持呢?

泛型需要的框架支持:

1.编译器能支持类型参数,用占位符(`1、`2...)来表示有几个参数,在运行时确定类型。

2.CLR升级才能支持占位符。运行时,类型确定了,就可以把占位符给替换掉

注意,泛型不是语法糖(什么是语法糖,见文末),而是框架的升级。

 

泛型的声明和使用

介绍了什么是泛型,我们接着来看,如何定义一个泛型,其实,像List<>,Dictionary<>这样的带有<>的都是泛型声明,如下,是一个自定义的泛型方法

public static void Show<T>(T tParameter)
{
    Console.WriteLine("This is {0},type={1},parameter={2}",
       typeof(CommonMethod), tParameter.GetType().Name, tParameter);
}

如上,在定义时不指定具体类型而使用类似T这样的符号来代替(这里可以是任意的定义,除了关键字)的方式即可声明一个泛型。

泛型声明后,具体该如何使用呢?

因为泛型类型是在运行时解析并确定类型,因此,在调用泛型相关的对象时,需要指定类型。如

CommonMethod.Show<int>(iValue);//调用泛型,需要指定类型参数
CommonMethod.Show(iValue);//如果可以从参数类型推断,可以省略类型参数(这是一种语法糖)
CommonMethod.Show<string>(sValue);

综上所述,泛型在定义时不需要指定类型,但是在使用时,除了可从参数推断类型的情况,必须指定类型。可省略类型的情况,其实是一种语法糖,是编译器提供的功能。

 

泛型方法的性能

泛型方法的性能跟普通方法一致,是最好的,而且能一个方法满足多个不同类型

这里可以通过运行n(n要很大)多次相对应的方法来测试,方法内可以什么也不处理,只指明是普通方法、泛型方法以及object处理即可。

 

泛型的应用场景

1.泛型方法:为了一个方法满足不同类型的需求

比如:一个方法完成多实体查询、一个方法完成不同类型的数据展示、任意实体,转换成json字符串。

2.泛型类:一个类型满足不同类型需求,如dictionary、list

3.泛型接口:一个接口满足不同类型需求

4.泛型委托

5.泛型缓存

/// <summary>
/// 每个不同的T,都会生成一份不同的副本
/// 适合不同类型,需要缓存一份数据的场景,效率高
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericCache<T>
{
    static GenericCache()
    {
        Console.WriteLine("This is GenericCache 静态构造函数");
        _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
    }
    private static string _TypeTime = "";
    public static string GetCache()
    {
        return _TypeTime;
    }
}

这里只需了解什么是泛型缓存即可,之后我们还会讲到,泛型缓存的性能远高于字典缓存(因为泛型缓存在寄存器处理,字典缓存用hash表处理),缺点是只能对某一类型存储一个对象。

从以上场景我们可以看出来,还是那句话,泛型是用一个事物来代替多种类型的需求的。凡是此类情况,即可以考虑用泛型来处理。

 

明白了什么是泛型,如何定义泛型以及泛型的应用场景, 那我们再来了解一下泛型约束。那么什么是泛型约束?为什么要有泛型约束呢?

 

泛型约束

俗话说,没有规矩不成方圆,那么规矩就是约束,试想一下,如果没有交通规则,那我们的道路情况是会井井有条还是混乱不堪?其实也就是有约束才有权利、才能更好的工作。那么泛型约束也是一样的道理,是为了让泛型更高效,还记得在开篇的时候,我提到的object处理形式的第二个缺点吗?对,就是类型安全问题,用约束可以完美的解决类型安全问题。talk is cheap,show you the code。看过之后你应该就会明白。

请注意观察以下2段代码的区别:

public static void ShowObject(object oParameter)
{
    Console.WriteLine("This is {0},parameter={1},type={2}",
        typeof(GenericConstraint), oParameter, oParameter.GetType().Name);
    People people = (People)oParameter;
    Console.WriteLine($"{people.Id}  {people.Name}");
}
public static void Show<T>(T tParameter)
    where T : People, ISports, IWork, new()
{
    Console.WriteLine("This is {0},parameter={1},type={2}",
       typeof(GenericConstraint), tParameter, tParameter.GetType().Name);
    Console.WriteLine($"{tParameter.Id}  {tParameter.Name}");
}

第二段代码定义了一个有泛型约束的方法,其中where T:People...即是泛型的约束定义。

其实以上的主要区别就在类型转换的那一行,在处理前需要先显式转换,而泛型类型则可直接调用,区别在于,泛型约束类型其实已经约束了类型,让编译器知道当前的类型是否匹配。那么此时,如果我们在调用泛型方法时传入一个Animal对象,那肯定会报错的。因为Animal不是People。编译不能通过。

因此,可以说没有约束其实很局限,因为要实现类型转换,加了约束之后,可以不用转换可以直接调用。

 

既然,泛型约束这有有用,那么泛型约束有哪些种类呢?一般的,我们通常有5大种类的约束。

 

约束类型

1.基类约束  where T:People(不能是密封类,如where T:String,密封类没有意义,因为没有子类)

2.接口约束  where T:ISport

3.应用类型约束  where T:class

4.值类型约束  where T:struct

5.无参构造函数约束  where T:new()

5种约束可以组合使用,但需要考虑实际情况,比如一般不可能既是class又是struct。默认值用default(T)获取

public T GetT<T, S>()
    where S : People//基类约束
    where S :ISports//接口约束
    where T : class//引用类型约束
    where T : struct//值类型
    where T : new()//无参数构造函数
{
    //return default(T);//default是个关键字,会根据T的类型去获得一个默认值
    return new T();
}

 

关于泛型可能涉及到一些问题。


问题扩展


1.什么是语法糖?泛型是语法糖吗

语法糖是编译器提供的便捷功能,比如var i=3;以及泛型调用时省略参数类型。语法糖是在编译时确定类型。

泛型不是简单的语法糖,是框架的升级。

 

2.webservice能用泛型吗?

webservice、WCF都是不能用泛型的,因为服务在发布时类型必须是确定的,而泛型是在运行时确定类型的。

其次,对于这种跨语言的形式,可能别的语言不支持泛型也会有问题。

 

3.getType()和typeof有区别?

getType是object的一个方法,在对象上调用。

typeof是一个关键字,参数是一个类型

相关文章
|
6月前
|
Java API
从零开始学习 Java:简单易懂的入门指南之File类(二十九)
从零开始学习 Java:简单易懂的入门指南之File类(二十九)
|
存储 Java 数据处理
重温经典《Thinking in java》第四版之第二章 一切都是对象(十一)
重温经典《Thinking in java》第四版之第二章 一切都是对象(十一)
65 1
|
安全 Java 程序员
重温经典《Thinking in java》第四版之第三章 操作符(二十)
重温经典《Thinking in java》第四版之第三章 操作符(二十)
51 0
|
6月前
|
设计模式 算法 Java
重温经典《Thinking in java》第四版之第九章 接口(四十七)
重温经典《Thinking in java》第四版之第九章 接口(四十七)
50 0
|
6月前
|
存储 Java
从零开始学习 Java:简单易懂的入门指南之可变参数及Collections类(二十五)
从零开始学习 Java:简单易懂的入门指南之可变参数及Collections类(二十五)
|
6月前
|
存储 安全 Java
从零开始学习 Java:简单易懂的入门指南之泛型及set集合(二十二)
从零开始学习 Java:简单易懂的入门指南之泛型及set集合(二十二)
|
6月前
|
Java 编译器 C++
重温经典《Thinking in java》第四版之第九章 接口(四十六)
重温经典《Thinking in java》第四版之第九章 接口(四十六)
41 0
|
Java 程序员
从零开始学习 Java:简单易懂的入门指南之抽象类&接口&内部类(十一)
从零开始学习 Java:简单易懂的入门指南之抽象类&接口&内部类(十一)
|
Java
重温经典《Thinking in java》第四版之第三章 操作符(十八)
重温经典《Thinking in java》第四版之第三章 操作符(十八)
51 0
|
Java
重温经典《Thinking in java》第四版之第三章 操作符(十七)
重温经典《Thinking in java》第四版之第三章 操作符(十七)
51 0