• C#基础--泛型


    泛型

    概述

    泛型允许我们延迟编写类或方法中的参数类型,直到在程序中使用它的时候,模块内高内聚,模块间低耦合

    性能

    示例:

    var list= new ArrayList();
    list.Add(44);//装箱
    int il=int)list[0]//拆箱
    foreachint i2 in list)
    {
    	Console.writeLine(i2);// 拆箱
    }
    

    装箱和拆箱操作很容易使用,但性能损失比较大,遍历许多项时尤其如此。
    System.Collections.Generic名称空间中的ListT>类不使用对象,而是在使用时定义类型。在下面的例子中ListT>类的泛型类型定义为int,所以int类型在JITJust-In-Time)编译器动态生成的类中使用,不再进行装箱和
    拆箱操作:

    var list = new List<int>();
    list.Add(44)//不再装箱存在List
    int i1 = list[0]// 不再拆箱无需转换
    foreachint i2 in list)
    {
    	Console.WriteLine(i2)}
    

    类型安全

    泛型的另一个特性是类型安全。与ArrayList类一样,如果使用对象,就可以在这个集合中添加任意类型。下面的例子在ArrayList类型的集合中添加一个整数、一个字符串和一个MyClass类型的对象:

    var list=new ArrayList();
    list.Add(44);
    list.Add("mystring");
    list.Add(new MyClass());
    foreachint i2 in list)
    {
    	Console.WriteLine(i2); //运行时异常
    }
    

    在泛型类List中,泛型类型T定义了允许使用的类型。有了List的定义,就只能把整数类型添加到集合中。编译器不会编译这段代码,因为Add0方法的参数无效:

    var list=new List<int>();
    list.Add(44);
    list.Add(”mystring”);//无法编译
    list.Add(new MyClass());//无法编译
    

    二进制代码的重用

    泛型允许更好地重用二进制代码。泛型类可以定义一次,并且可以用许多不同的类型实例化。不需要像C++模板那样访问源代码。例如,System.Collections.Generic名称空间中的List类用一个int、一个字符串和一个MyClass类型实例化:

    var list=new List<int>();
    list.Add(44);
    var stringList=new List<string>();
    stringList.Add(”mystring”);
    var myClassList = new List<MyClass>();
    myClassList.Add(new MyClass());
    

    代码的扩展

    在用不同的特定类型实例化泛型时,会创建多少代码?因为泛型类的定义会放在程序集中,所以用特定类型实例化泛型类不会在Ⅱ代码中复制这些类。但是,在ⅡIT编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类。引用类型共享同一个本地类的所有相同的实现代码。这是因为引用类型在实例化的泛型类中只需要4个字节的内存地址(32位系统),就可以引用一个引用类型。
    值类型包含在实例化的泛型类的内存中,同时因为每个值类型对内存的要求都不同,所以要为每个值类型实例化一个新类。

    命名约定

    泛型类型的名称用字母T作为前缀,如果没有特殊的要求,泛型类型允许用任意类替代,且只使用了一个泛型类型,就可以用字符T作为泛型类型的名称。
    public class List{}
    public class LinkedList{}
    如果泛型类型有特定的要求(例如,它必须实现一个接口或派生自基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称

    public delegate void EventHandler<TEventArgs> (object sender , TEventArgs e);
    public delegate Toutput Converter<TInput,Toutput>(TInput from);
    public class SortedList<TKey,TValue>{ }
    

    泛型类

    泛型类的定义与一般类类似,只是要使用泛型类型声明。之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型。LinkedListNode类用一个泛型类型T声明。属性Value的类型是T,而不是object。构造函数也变为可以接受T类型的对象。也可以返回和设置泛型类型,所以属性Next和Prev的类型是LinkedListNode。

     public class LinkedListNode<T>
     {
         public LinkedListNode(T value) =>
             Value = value;
    
         public T Value { get; }
         public LinkedListNode<T> Next { get; internal set; }
         public LinkedListNode<T> Prev { get; internal set; }
     }
    

    泛型类的功能

    默认值

    泛型类型既可以实例化为引用类型也可以实例化为值类型。而null只能赋予引用类型,0只能赋予值类型
    这个问题,可以使用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。

    public T GetDocument()
    {
    	T doc = default;
    	return doc;
    }
    

    注意:
    default关键字根据上下文可以有多种含义。switch语句使用default定义默认情况。在泛型中,取决于泛型类型是引用类型还是值类型,泛型default将泛型类型初始化为null或0。

    约束

    如果泛型类需要调用泛型类型中的方法,,就必须添加约束.
    在这里插入图片描述
    注意:
    只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束
    使用泛型类型还可以合并多个约束。whereT:IFoo,new() 约束和MyClass声明指定,类型T必须实现IFoo接口,且必须有一个默认构造函数。

    public class MyClass<T> where T:IFoo,new()
    {
    	
    }
    

    继承

    泛型类型可以实现泛型接口,也可以派生自一个类。泛型类可以派生自泛型基类

    public class Base<T>{}
    public class Derived<T>:Base<T>{}
    

    其要求是必须重复接口的泛型类型,或者必须指定基类的类型,如下例所示:

    public class Base<T>{}
    public class Derived<T>: Base<string>{}
    

    于是,派生类可以是泛型类或非泛型类。例如,可以定义一个抽象的泛型基类,它在派生类中用一个具体
    的类实现。这允许对特定类型执行特殊的操作:

    public abstract class Calc<T>
    {
    	public abstract T Add(T x,T y);
    	public abstract T Sub(T x,T y);
    }
    public class IntCalc: Calc<int>
    {
    	public override int Add(int x,int y)=>x+y;
    	public override int Sub(int x,int y)=>x-y;
    }
    

    还可以创建一个部分的特殊操作,如从Query中派生StringQuery类,只定义一个泛型参数,如字符串
    TResult。要实例化StringQuery,只需要提供TRequest的类型:

    public class Query<TRequest,TResult>
    {
    	
    }
    public StringQuery<TRequest>:Query<TRequest , string>
    {
    	
    }
    

    静态成员

    泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。下面看一个例子,其中
    StaticDemo类包含静态字段x:

    public class StaticDemo<T>
    {
    	public static int x;
    }
    

    由于同时对一个string类型和一个int类型使用了StaticDemo类,因此存在两组静态字段

    StaticDemo<string>.x=4;
    StaticDemo<int>.x=5;
    Console.WriteLine(StaticDemo<string>.x); //4
    

    泛型接口

    使用泛型可以定义接口,在接口中定义的方法可以带泛型参数。在链表的示例中,实现了IEnumerable
    接口,它定义了GetEnumerator(0方法,以返回IEnumerator。NET为不同的情况提供了许多泛型接口,例
    如,IComparable、ICollection和IExtensibleObiect。

    协变和抗变

    如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。接口Index与类型T是协变的,并从一个只读索引器中返回这个类型。

    public interface IIndex<out T>
    {
        T this[int index] { get; }
        int Count { get; }
    }
    

    如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入

     public interface IDisplay<in T>
     {
         void Show(T item);
     }
    

    泛型结构

    与类相似, 结构也可以是泛型的。它们非常类似于泛型类,只是没有继承特性。Nullable,它由.NETFramework定义。NETFramework中的一个泛型结构是Nullable。数据库中的数字和编程语言中的数字结构Nullable定义了一个约束:其中的泛型类型T必须是一个结构。把类定义为泛型类型后,就没有低系统开销这个优点了,而且因为类的对象可以为空,所以对类使用Nullable类型是没有意义的。除了Nullable定义的T类型之外,唯一的系统开销是hasValue布尔字段,它确定是设置对应的值,还是使之为空。除此之外,泛型结构还定义了只读属性HasValue和Value,以及一些运算符重载。把Nullable类型强制转换为T类型的运算符重载是显式定义的,因为当hasValue为false时,它会抛出一个异常。强制转换为Nullable类型的运算符重载定义为隐式的,因为它总是能成功地转换.

    泛型方法

    在泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义。
    Swap0方法把T定义为泛型类型,该泛型类型用于两个参数和一个变量temp:

    void Swap<T>(ref T x,ref Ty)
    {
    	T temp;
    	temp=x;
    	x=y;
    	y=temp;
    }
    

    带约束的泛型方法

    泛型类型可以用where子句来限制。用于泛型类的这个子句也可以用于泛型方法。

    public interface IAccount
    {
        decimal Balance { get; }
        string Name { get; }
    }
    
    public class Account : IAccount
    {
        public string Name { get; }
        public decimal Balance { get; }
    
        public Account(string name, Decimal balance)
        {
            Name = name;
            Balance = balance;
        }
    }
    public static class Algorithms
    {
        public static decimal AccumulateSimple(IEnumerable<Account> source)
        {
            decimal sum = 0;
            foreach (Account a in source)
            {
                sum += a.Balance;
            }
            return sum;
        }
    
        public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source)
            where TAccount : IAccount
        {
            decimal sum = 0;
    
            foreach (TAccount a in source)
            {
                sum += a.Balance;
            }
            return sum;
        }
    }
    

    带委托的泛型方法

    如何通过传递一个泛型委托来修改Accumulat()方法。这个Accumulate0方法使用两个泛型参数TI和T2。第一个参数TI用于实现IEnumerable参数的集合,第二个参数使用泛型委托Func。其中,第2个和第3个泛型参数都是T2类型。需要传递的方法有两个输入参数(T1和T2)和一个T2类型的返回值:

    public static T2 Accumulate<T1, T2>(IEnumerable<T1> source, Func<T1, T2, T2> action)
    {
        // TODO: update to C# 7.1 syntax
        T2 sum = default(T2);
        foreach (T1 item in source)
        {
            sum = action(item, sum);
        }
        return sum;
    }
    

    在调用这个方法时,需要指定泛型参数类型,因为编译器不能自动推断出该类型。对于方法的第1个参数
    所赋予的accounts集合是IEnumerableAccount>类型。对于第2个参数,使用一个lambda表达式来定义Account
    和decimal类型的两个参数,返回一个小数。

  • 相关阅读:
    FTP文件传输服务
    后端框架有哪些
    农村科学实验杂志农村科学实验杂志社农村科学实验编辑部2022年第12期目录
    JS原生-弹框+阿里巴巴矢量图
    【redis】--springboot集成redisson
    Ue5 websocket控制Character前后左右动作
    LeetCode每日一题(2196. Create Binary Tree From Descriptions)
    【web渗透思路】框架敏感信息泄露(特点、目录、配置)
    【iOS自动化测试】第二章:环境安装
    BAT大厂Java面试,如何抓住面试重点知识?收割大厂offer
  • 原文地址:https://blog.csdn.net/huan13479195089/article/details/126931046