C#浅谈对泛型的认识

15 篇文章 0 订阅
12 篇文章 3 订阅

    泛型,顾名思义,首先它是一个“类”型,其次修饰它的是“泛”,有广泛、宽泛之意。

    简单而言,带<T>就是泛型。

    初识泛型,是在四五年前刚学习C#时,看当时公司大牛的一段代码(向数据库插入一条数据,类似的还有删改查):

public bool Insert<T>(T entity)
{
    try
    {
        var type = typeof(T);
        var className = type.Name;
        //...code to realize
        return true;
    }
    catch{
        return false
    }
}
            

    就感觉这样的代码好高大上啊,一个方法可以根据传入的参数的不同类型来进行统一的实现,针方便呢!(泛型方法、泛型参数)

    于是便有了这篇博客(ps:至于为什么是几年前就知道,现在才想起来写博客,最大的原因就是懒吧,另一原因也是水平的限制)!

1.泛型

    先上微软文档中对泛型概念的描述(不是系统地学习,但要学会系统地查资料):泛型概念

*概述

●使用泛型类型可以最大限度地重用代码、保护类型安全性以及提高性能。

●泛型最常见的用途是创建集合类。

●.NET 类库在System.Collections.Generic命名空间中包含几个新的泛型集合类。 应尽可能使用这些类来代替某些类,如System.Collections命名空间中的ArrayList。

●可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。

●可以对泛型类进行约束以访问特定数据类型的方法。

●在泛型数据类型中所用类型的信息可在运行时通过使用反射来获取。

    对于概述的第一条,以下是自己的理解。

    可重用性,可以根据需要传入各种类型的参数,比如要对类型的所有属性名称、值进行获取(获取从文件中进行读取并对其赋值),可以通过反射获取该实例或该类型的type对象,然后对其属性进行遍历即可。如下代码:

private void InputPropertiesAndValue<T>(T obj)
{
    for (int i = 0; i < obj.GetType().GetProperties().Length; i++)
    {
        PropertyInfo _property = obj.GetType().GetProperties()[i];
        Type _property_type = _property.PropertyType;
        var _property_value = _property.GetValue(obj, null);
        Console.WriteLine(_property.Name + "=" + _property_value?.ToString());
    }
}

    类型安全性,在泛型方法使用时,需要将T指定为特定的类型,而在需要传入的对象并非指定类型时,编译器就会报错,导致无法编译通过,则保证了类型的安全性。如下代码:

List<int> aList = new List<int>();
aList.Add(3);
aList.Add(4);
//aList.Add(5.0);//若取消注释,则编译器在此处报错——System.InvalidCastException:指定转换无效
int totalList = 0;
foreach(int val in aList)
{
    totalList = totalList + val;
}
Console.WriteLine("Total is {0}", totalList);

    效率的话个人认为一方面是针对运行时中的泛型,即编译好的程序在运行时刻对内存的使用。是对与非泛型的对比而言的,见下文泛型和非泛型集合对比。

2.泛型类、泛型接口

*    泛型类封装不特定于特定数据类型的操作。(下文将对概念不再进行赘述)

    对于泛型类的继承,其中很重要的一句话:非泛型类(即具体类)可继承自封闭式构造基类,但不可继承自开放式构造类或类型参数,因为运行时客户端代码无法提供实例化基类所需的类型参数。

*    泛型接口,主要是为泛型集合类或表示集合中的项的泛型类定义接口。参考:泛型接口

    (协变、逆变,重点在于理解)

*    说起泛型类,就不得不说到泛型集合(常见的有List、Dictionary、Queue等),当然还有非泛型集合(ArrayList),如何正确地使用这两类集合?

    可以记住这么一句话,不用考虑使用非泛型集合。就性能而言,非泛型可能包含对object类型的装箱拆箱操作(消耗CPU);就易出错程度而言,非泛型集合的装箱拆箱导致的类型转换在运行时可能出错。举例如下:

ArrayList arr=new ArrayList();
arr.add(100);
arr.add("100");
foreach(int item in arr)//编译可以通过,但运行时会出错,但是另外的解决方法可以改为下方注释内容
//foreach(var item in arr)
{
    Console.WriteLine(item);
}

    参考:不应使用非泛型集合

3.泛型类型参数、类型参数约束、泛型方法

*   泛型类型参数

    将泛型类型作为方法、类、接口等的参数。

*   类型参数约束

    where的使用,是对类型参数进行约束,影响在于不符合约束的泛型类在实现时,编译器会报错。

*   泛型方法

    就是带有泛型类型参数的方法。

4.运行时中的泛型

    在上一小节“泛型类型参数”参考文档中提到了运行时中的泛型,其中主要内容可以理解为:

    泛型类型或方法在编译为微软中间语言MSIL时,它包含将其标识为具有类型参数的元数据。

泛型类型参数为值类型:

    运行时会为值类型的泛型生成不同的专用版的Stack<T>“模板”类,如根据需要创建了Stack<int>和Stack<double>类,所有泛型类型参数为int型的对象使用的是相同的Stack<int>类的实例,doule亦然,而两者使用的是不同“模板”(不同的地址)。

泛型类型参数为引用类型:

    如值类型,但是不同的是对所有引用类型,只会生成一个专用版的Stack<T>“模板”类,根据T为不同引用类型,使用的是同一个“模板”的不同实例(相同的地址)。

5.泛型委托、泛型和反射、泛型与特性

*   泛型委托,除了下面代码中使用委托定义事件,还包括Func、Action等形式的委托

/// <summary>
/// 根据典型设计模式定义事件时,泛型委托特别有用,因为发件人参数可以为强类型,无需在它和 Object 之间强制转换
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
delegate void StackEventHandler<T, U>(T sender, U eventArgs);

class Stacks<T>
{

    public class StackEventArgs : System.EventArgs { }
    public event StackEventHandler<Stacks<T>, StackEventArgs> stackEvent;

    public virtual void OnStackChanged(StackEventArgs a)
    {
        stackEvent(this, a);
    }
}

class SampleClass
{
    public void HandleStackChange<T>(Stacks<T> stack, Stacks<T>.StackEventArgs args)
    {
        // do work
    }
                
}

public static void Test()
{
    Stacks<double> s = new Stacks<double>();
    SampleClass o = new SampleClass();
    s.stackEvent += o.HandleStackChange;
    //触发事件
    s.OnStackChanged(new Stacks<double>.StackEventArgs());
}

*   泛型和反射

    参考“可重用性”代码,通过反射获取T的属性信息。

*   泛型和特性

    参考:特性利用特性扩展元数据

    使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。常见的特性使用场景有:DllImportAttribute调用非托管代码、SerializableAttribute将类或类成员标记为可序列化、调用 Attribute.GetCustomAttribute方法获取用户自定义特性(如对属性的描述)。

下面列出了代码中特性的一些常见用途:

  • 在 Web 服务中使用 WebMethod 特性标记方法,以指明方法应可通过 SOAP 协议进行调用。 有关详细信息,请参阅 WebMethodAttribute
  • 描述在与本机代码互操作时如何封送方法参数。 有关详细信息,请参阅 MarshalAsAttribute
  • 描述类、方法和接口的 COM 属性。
  • 使用 DllImportAttribute 类调用非托管代码。
  • 从标题、版本、说明或商标方面描述程序集。
  • 描述要序列化并暂留类的哪些成员。
  • 描述如何为了执行 XML 序列化在类成员和 XML 节点之间进行映射。
  • 描述的方法的安全要求。
  • 指定用于强制实施安全规范的特征。
  • 通过实时 (JIT) 编译器控制优化,这样代码就一直都易于调试。
  • 获取方法调用方的相关信息。

6.协变和逆变

    协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。实际上无论是逆变还是协变,都是对引用类型(数组、委托、泛型类型参数)的隐式转换。而我们自定义的父类子类实际上并不存在转换,只能作为支持协变、逆变的数组中、委托中、泛型类型参数的类型时。参考:协变和逆变

    如果泛型接口或委托的泛型参数被声明为协变或逆变,该泛型接口或委托则被称为“变体”。参考:具有协变、逆变类型参数的泛型接口泛型接口中的变体 委托中的变体

    可以参考另一篇不错的博客,对协变和逆变讲的比较清楚:协变、逆变

    看完这些知识内容,再看下面的代码,是不是豁然开朗,记住一句,协变和逆变,都只是进行了隐式引用转换。

public static void Main()
{
    IEnumerable<Base2> base2s = new List<Derived>();//IEnumerable是协变接口
    //IEnumerable<Derived> deriveds = base2s;//无法将类型            
    //“System.Collections.Generic.IEnumerable<Generic.Base2>”隐式转换为 
    //“System.Collections.Generic.IEnumerable<Generic.Derived>”。
    //存在一个显式转换(是否缺少强制转换?)
    //如要实现上述转换,需要自定义的逆变接口(即变体),并将Base2转换为Derived
    //(new一个Derived并根据Base2属性将其赋值),个人觉得实际上小工程中不存在这样的使用场景
    //(需要时直接返回派生对象即可)
    Action<Base2> action = new Action<Base2>((o) => Console.WriteLine(o.ToString()));
    action(new Base2());
    action(new Derived());
    Action<Derived> action1 = action;
    //action1(new Base2());//无法从“Generic.Base2”转换为“Generic.Derived”
    action1(new Derived());
}
public class Base2{}
public class Derived:Base2{}

7.Git参考

    具体代码及微软文档中简单demo可参考本人Github库:C#基础演练,参考其中的“Generic”工程,如果对您有帮助,还麻烦点个小小的Star,谢谢!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值