【.net6第一章】泛型Generic

从这一篇文章开始进入.net6的文章之旅!!!!!


我们先留一个问题提供大家思考,答案会在文章末尾公布
问:这样子会不会报错?,如果不报错控制台打印输出的 值=多少?:
在这里插入图片描述

前言

什么是泛型?
泛型是.NET 6中的一个重要特性,它允许你编写更灵活、可重用和类型安全的代码。泛型允许你在定义类、接口、方法等时使用参数化的类型,这使得你可以编写与数据类型无关的代码,提高了代码的灵活性和可维护性。例如:List,其中"T"表示的是类型的占位符也叫类型参数或类型变量,参数类型在声明的时候就确定,调用按照类型传递参数即可。不一定为T,使用其他字符也可以,但是不要使用关键字,也可以有多个类型,例如 public class GenericClass<A,B,C,D>{…}。

一、泛型

泛型的出现是为了解决在编程中需要编写灵活、通用、可重用的代码时所遇到的一些问题,例如:代码重复,类型安全,代码灵活性,性能优化,集合类的类型安全等。

总的来说,泛型的出现使得编写更具有通用性、可重用性和类型安全性的代码成为可能,提高了代码的质量和性能。

这是最简单的泛型方法

/// <summary>
/// 泛型方法<T>
/// </summary>
/// <typeparam name="T">类型变量  类型参数</typeparam>
/// <param name="tParameter"></param>
public static void GenericTypeType<T>(T tParameter)
{
    Console.WriteLine($"This is {typeof(CommonMethod).Name},parameter={tParameter.GetType().Name},type={tParameter}");
}

在调用的时候我们就能看需要带入对应的参数类型

int iValue = 123;
string sValue = "456";
DateTime dtValue = DateTime.Now;
object oValue = "678";
Console.WriteLine("***************************");
GenericMethod.GenericTypeType(iValue); //不允许--如果可以通过参数推导出类型---尖括号可以省略
GenericMethod.GenericTypeType(sValue);// 省略类型--语法糖
GenericMethod.GenericTypeType<DateTime>(dtValue);
GenericMethod.GenericTypeType<object>(oValue);

性能测试结果:
在这里插入图片描述
从简单的测试中可以看出使用泛型跟定义强类型方法性能无二!!!

问:
在C#中,object类型作为所有类型的基类,任何子类出现的地方都可以用父类来代替,那么就会有一个疑问,为什么不能使用object类型来接受参数呢?
答:
1.会出现类型安全问题,
2.性能问题,会出现装箱拆箱的性能问题,在C#语法中,是按照声明时决定类型,object是引用类型,当你传入值类型时,会将值类型转成引用类型,涉及到内存拷贝,就出现引用指向问题—装箱(值类型封装为引用类型的过程),引用类型是存储在托管堆,值类型是存储在线程栈中
3.使用object类型安全问题,代码会出现安全隐患,编译器是检测不到的!!!!

泛型的设计思想:
1.延迟声明,我不需要直接定义类型参数,在调用之前确定参数类型就可以了。
2.核心在于“代码重用”与“类型安全”。通过泛型,可以创建能够与任何数据类型一起工作的类、方法、接口和委托,而不需要牺牲类型安全

二、泛型的原理和应用

1.泛型的原理

1.
首先声明,泛型不是语法糖,他是框架升级后底层支持对泛型的编译!!
1.定义的泛型T,在计算机执行的执行的时候,是一个具体的类型。因为在编译的过程中,会生成具体的类型在DLL或者EXE中
2.在底层中,例如List,Dictionary<>,中,在IL中就可以看到生成结果List1[T] ,Dictionary2[TKey,TValue],它们都来自自 System.Collections.Generic类

2.泛型的引用

泛型方法

public static T GenericstaticMethod<T>(){....}

泛型接口

public interface GenericInterfac<T>{...}

泛型委托

public delegate void GenericDelegate<T>();

泛型类

public class GenericClass<T>{......}

误区:
假如我们有一个抽象类

 public abstract class GenericAbstractClass<T>{.....}
 //我们是不可以这样直接继承的,因为我们要在使用前确定参数类型
 public class ChildClass : GenericAbstractClass<T> 
 //但是可以这样
  public class ChildClass : GenericAbstractClass<int> 
  //或者这样提前声明好类型是可以的,父类的类型参数跟随子类的类型参数是一致的
  public class ChildClass<T> : GenericAbstractClass<T> 

三、泛型约束

在.NET中,泛型约束是泛型编程的一个关键特性,它允许你指定一个泛型类型参数必须满足的条件。通过泛型约束,你可以限制泛型类型参数可以代表的数据类型范围,这样可以确保泛型类或方法在使用特定类型时能够正常工作。

1.类型约束:可以指定泛型类型参数必须是特定的类型或继承自某个类型。

public class MyClass<T> where T : SomeBaseClass {...}

2.接口约束:指定泛型类型参数必须实现特定的接口。

public class MyClass<T> where T : InterfaceClass {...}

3.无参构造函数约束:要求泛型类型参数有一个无参的构造函数。

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

4.引用类型约束:指定泛型类型参数必须是引用类型。

public class MyClass<T> where T :class {...}

5.值类型约束:指定泛型类型参数必须是值类型。

public class MyClass<T> where T :struct {...}

6.枚举约束

public class MyClass<T> where T :Enum{...}

7.其他泛型类型约束:可以指定泛型类型参数必须是另一个泛型参数的子类型。

public class MyClass<T> where T :A {...}

四、泛型缓存和协变与逆变

1.泛型缓存

泛型缓存通常指的是.NET运行时对泛型类型和方法进行的一种优化机制。在.NET中,泛型类型和泛型方法的实例化是按需进行的,且这些实例化结果会被缓存,以便在后续需要时可以直接重用,提高性能。

简单来说,泛型缓存会针对不同的类型参数生成不同的副本进行缓存,只会执行一次构造函数,第二次或第n次进来就会不在执行构造函数
在这里插入图片描述

2.协变与逆变

在.NET中,泛型的协变(covariance)和逆变(contravariance)是处理泛型接口和委托时类型兼容性的重要概念。它们使得你能够使用更具体或更抽象的类型替换原本指定的泛型类型参数,从而提供更大的灵活性和表达力。

协变(Covariance)
协变允许你将一个泛型类型参数替换为更派生的类型。在泛型接口或泛型委托中,如果它们的泛型类型参数标记为协变(使用 out 关键字),那么你可以将此接口或委托的实例赋给其泛型参数为基类的相应接口或委托。

IEnumerable<Derived> derivedCollection = new List<Derived>();
IEnumerable<Base> baseCollection = derivedCollection; // 协变

逆变(Contravariance)
逆变允许你使用更抽象的类型(基类)替换泛型类型参数。如果泛型接口或泛型委托的泛型类型参数标记为逆变(使用 in 关键字),那么你可以将此接口或委托的实例赋给其泛型参数为派生类的相应接口或委托

Action<Base> baseAction = (Base b) => { /* ... */ };
Action<Derived> derivedAction = baseAction; // 逆变

协变和逆变在泛型编程中非常重要,它们提供了额外的灵活性,特别是在你需要将泛型集合或委托作为方法参数或返回类型时。这样可以使你的代码更加灵活和通用,同时保持类型安全。

简单理解,一条狗它是一只动物,但是一只动物就不一定是一条狗了,也可能是猫。
代码假设:Animal 类是Dog类的父类

//C#中任何子类都可以使用父类来声明,这样可以
Animal animal=new Dog();
//但是这样就不行啦
Dog dog=new Animal(); xxx

但是问题来了,如果一堆动物可以使一群狗吗???

List<Animal> list= new List<Dog>(); //可否???

很显然,不行!!首先是语法不允许,其次泛型类声明不同类型会生成不同的类型,List 并不是List的父类,二者不存在父子级关系的哟!!!

这样就引入了协变和逆变

协变:

IEnumerable<Animal> an1=new List<Animal>();
IEnumerable<Animal> an2=new List<Dog>();  //这样左边就可以使用父类了

因为在IEnumerable中它拥有out关键字修饰
在这里插入图片描述
那我们也来自定义一个,照猫画虎,照葫芦画瓢

    /// <summary>
    /// out 协变 只能是返回结果
    /// 泛型T 就只能做返回值; 不能做参数; 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IListOut<out T>
    {
        T Get();
    }
    //继承了之后就有了父子级关系
    public class ListOut<T> : IListOut<T>
    {
        public T Get()
        {
            return default(T);
        }
    }
    //然后我们就可以调用了
    IListOut<Animal> an=new IListOut<Dog>();
    an.Show(new Dog());

问:
为什么不能当参数?
答:
加上out关键字后,左边确实可以使用父类右边使用子类,那么我实例化的是子类,但是 an.Show(new Animal()); 它可以传入父类,那么问题来了,我实例化构造函数参数类型是子类,但是调用方法传入父类的话类型就不一样了,所以不允许这种情况,所以T只能做返回值。模糊????

那么看代码:

    /// <summary>
    /// out 协变 只能是返回结果
    /// 泛型T 就只能做返回值; 不能做参数; 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IListOut<out T>
    {
        T Get();
        void test(T t);
    }
    //继承了之后就有了父子级关系
    public class ListOut<T> : IListOut<T>
    {
        public T Get()
        {
            return default(T);
        }
        public void test(T t)
        {
           .....
		}
    }
    //然后我们就可以调用了
    IListOut<Animal> an=new IListOut<Dog>();
    an.Show(new Dog());
  
  // 我实例化一个Dog
 IListOut<Animal> an=new IListOut<Dog>();
 //此时确定类型是Dog了。这时候问题他爹跑进来了
 an.Show(new Animal()); //可否???
 //很显然,不行。所以不能做参数,只能做返回值。
   

那么我就要传父类,咋滴,我就想传父类怎么办???

逆变:
可以左边使用子类,右边使用父类
in关键字修饰后:要求类型参数只能为输入参数,不能为返回值

//本来我们是不可以这样子的
List<Dog> list=new List<Animal>();


//但是...我们有in


	/// <summary>
	/// T 就只能做参数  不能做返回值
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public interface IListIn<in T>
	{
	    void Show(T t);
	}

	public class ListIn<T> : IListIn<T>
	{
	    public void Show(T t)
	    {
			....
	    }
	}

	//就可以这样子
   IListIn<Dog> list=new ListIn<Animal>();

问:
为什么不能当返回值?
答:
加上in关键字修饰后,左边子类右边父类,那么可能出现这种情况:我左边接口要求返回的类型是子类,可能出现你拿父类去实例化后返回一个父类给我,明显不能这么玩,所以不能做返回值。。。

协变逆变:
上面我们出现问题,out只能作为返回值,in只能做参数,很明显不能够满足。那么就协变+逆变

    public interface IMyList<in inT, out outT>
    {
        void Show(inT t);
        outT Get();
        outT Do(inT t);
    }
    
 /// <summary>
 /// out 协变 只能是返回结果 
 /// in  逆变 只能是参数 
 /// </summary>
 /// <typeparam name="T1"></typeparam>
 /// <typeparam name="T2"></typeparam>

 public class MyList<T1, T2> : IMyList<T1, T2>
 {
     public void Show(T1 t)
     {
         Console.WriteLine(t.GetType().Name);
     }

     public T2 Get()
     {
         Console.WriteLine(typeof(T2).Name);
         return default(T2);
     }

     public T2 Do(T1 t)
     {
         Console.WriteLine(t.GetType().Name);
         Console.WriteLine(typeof(T2).Name);
         return default(T2);
     }
 }
	//协变逆变的存在,就是为了满足常规场景添加一个避开风险的约束; 
    IMyList<Dog, Animal> myList1 = new MyList<Dog, Animal>();
    IMyList<Dog, Animal> myList2 = new MyList<Dog, Dog>();//协变 
    IMyList<Dog, Animal> myList3 = new MyList<Animal, Animal>();//逆变 
    IMyList<Dog, Animal> myList4 = new MyList<Animal, Dog>();//协变+逆变
    

OK!!!!!!!!!!!结束

顶部问题答案揭晓:值=1

如有补充或者有差异的地方,麻烦再评论下面指出,共勉!!!!!

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值