当前位置: 首页 > news >正文

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>
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<T1,T2,TResult>。其中,第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类型的两个参数,返回一个小数。

相关文章:

  • 【云原生kubernetes从入门到实践系列教程 ] 四.docker volumes持久化
  • 如何用python自动化微信小程序
  • javaH5醉美南湾湖网站设计计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
  • Windows11+VS2019驱动开发环境搭建
  • 文件防泄密系统如何保障企业文档的安全性?
  • 【数据结构与算法】ArrayList与顺序表(上)
  • 【前端进阶】-TypeScript类型声明文件详解及使用说明
  • [Spring boot] Spring boot 实现发送邮件功能
  • 万字指针超详细总结
  • 列表页常见 hook 封装
  • 集合_HashSet(HashMap)扩容机制源码简析
  • Spring注解@Qualifier的详细用法你知道几种「扩展点实战系列」- 第444篇
  • uni-app 微信小程序中关于 map 地图使用案例分享
  • 工业级成熟航运港口人工智能产品全球前三船公司及港口码头落地,中国上海人工智能独角兽中集飞瞳全球应用最广规模最大最先进港航AI企业
  • CSS基础篇---02选择器进阶、背景样式、显示模式
  • 2017前端实习生面试总结
  •  D - 粉碎叛乱F - 其他起义
  • JavaScript 基本功--面试宝典
  • js
  • Linux Process Manage
  • ng6--错误信息小结(持续更新)
  • supervisor 永不挂掉的进程 安装以及使用
  • Twitter赢在开放,三年创造奇迹
  • WePY 在小程序性能调优上做出的探究
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 前端学习笔记之观察者模式
  • 树莓派 - 使用须知
  • 正则学习笔记
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • #pragma once
  • #图像处理
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (9)STL算法之逆转旋转
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (四)JPA - JQPL 实现增删改查
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)负载均衡,回话保持,cookie
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .NET Project Open Day(2011.11.13)
  • .NET 的程序集加载上下文
  • .Net 高效开发之不可错过的实用工具
  • .net 简单实现MD5
  • @TableLogic注解说明,以及对增删改查的影响
  • [.net] 如何在mail的加入正文显示图片
  • [100天算法】-目标和(day 79)
  • [C++基础]-初识模板
  • [daily][archlinux][game] 几个linux下还不错的游戏
  • [docker] Docker的数据卷、数据卷容器,容器互联
  • [GDMEC-无人机遥感研究小组]无人机遥感小组-000-数据集制备
  • [hdu 1711] Number Sequence [kmp]