一、什么是泛型?
泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。
您可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。
官方描述总是很抽象的,在Unity中List<T>、GetComponent<T> 、Dictionary<TKey,Tvalue>和KeyValue<T,T>这些都是我们常用的泛型,其中T、Tkey、Tvalue起到了占位符的作用。里面可以书写任意字符(如:A、B、C这都可以),规范化默认写为T。
怕有些小伙伴不懂什么是占位符,简单说一下,占位符顾名思义就是帮你的字符占一个位置,举个我们常用的占位符例子:Console.WriteLine("我是占位符: {0}, 我是占位符: {1}", "0", "1"); 这里{0},{1}就是占位符,替0,1占了一个位置。
1.泛型的简单使用
定义一个动物类,age年龄定义为泛型,这样的好处是我们在实例化不同类时,根据需要可以重新定义age的参数类型,这样能大大提高类的复用性。
(1)泛型类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenericityTest : MonoBehaviour
{
void Start()
{
//定义string类型
Animal<string> dog = new Animal<string>("dog","5");
Debug.Log(dog.name+"年龄:"+dog.age);
//定义int类型
Animal<int> cat = new Animal<int>("cat",3);
Debug.Log(cat.name + "年龄:" + cat.age);
}
}
// 动物类
public class Animal<T>
{
public string name;
public T age;
public Animal(string _name,T _age)
{
name = _name;
age = _age;
}
}
(2)泛型方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
//使用
Debug.Log(AA("你好"));
Debug.Log(BB<int>());
CC(123);
Debug.Log(FistFunction(123));
}
void Update()
{
}
//有参有返回值
public T AA<T>(T value)
{
T qwer = value;
return qwer;
}
//无参有返回值
public T BB<T>()
{
T qwer = default(T); //default(T)类型的默认值
return qwer;
}
//无参有返回值+ 泛型约束struct不为空的值类型
public void CC<T>(T one) where T : struct
{
Debug.Log(one);
}
#region 泛型方法中调用其他泛型方法
public T FistFunction<T>(T a)
{
return SecondFunction(a);
}
public T SecondFunction<T>(T b)
{
return b;
}
#endregion
}
2.多泛型使用
上面我们把动物的年龄用T来表示了,如果我们动物的名称也需要开放出来怎么办?其实<T>泛型和多维数组类似只需要在Animal类中在添加一个泛型<T,K>即可。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenericityTest : MonoBehaviour
{
void Start()
{
Animal<string,string> dog = new Animal<string,string>("dog","5");
Debug.Log(dog.name+"年龄:"+dog.age);
Animal<string,int> cat = new Animal<string,int>("cat",3);
Debug.Log(cat.name + "年龄:" + cat.age);
}
}
// 动物类
public class Animal<T,K>
{
public T name;
public K age;
public Animal(T _name,K _age)
{
name = _name;
age = _age;
}
}
二、 泛型约束
1.各种泛型约束
泛型各类约束:类型参数约束 - C# 编程指南 - C# | Microsoft Learn
泛型类型 | 功能 |
where T : struct | 限定当前参数类型必须是不为null的值类型。若想写入null值可以参考可为空的值类型。所有值类型都是具有可访问的无参数构造函数,所以使用 struct 约束等于 new() 约束。 struct约束不能与 new() 、unmanaged 约束结合使用。 |
where T : class | 必须是引用类型,也可用于任何类、接口、数组和委托。在可为 null 的上下文中,T 必须是不为 null 的引用类型。 |
where T : class? | 类型参数必须是可为 null 或不可为 null 的引用类型。 此约束还应用于任何类、接口、委托或数组类型。 |
where T : notnull | 类型参数必须是不可为 null 的类型。 参数可以是不可为 null 的引用类型,也可以是不可为 null 的值类型。 |
where T : default | 设置设置参数化类型T的默认值。 |
where T : unmanaged | 类型参数必须是不可为 null 的非托管类型。 unmanaged 约束表示 struct 约束,且不能与 struct 约束或 new() 约束结合使用。 |
where T : new() | 限定当前参数类型必须有一个无参构造,使用有参构造会报错。 与其他约束一起使用时,new() 约束必须最后指定 |
where T :<基类名> | 类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中,T 必须是从指定基类派生的不可为 null 的引用类型。 |
where T :<基类名>? | 类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中,T 可以是从指定基类派生的可为 null 或不可为 null 的类型。 |
where T :<接口名> | 类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在的可为 null 的上下文中,T 必须是实现指定接口的不可为 null 的类型。 |
where T :<接口名>? | 类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在可为 null 的上下文中,T 可以是可为 null 的引用类型、不可为 null 的引用类型或值类型。 T 不能是可为 null 的值类型。 |
where T : U | U代表我们自定义的类型。 在可为 null 的上下文中,如果 U 是不可为 null 的引用类型,T 必须是不可为 null 的引用类型。 如果 U 是可为 null 的引用类型,则 T 可以是可为 null 的引用类型,也可以是不可为 null 的引用类型。 |
下面主要讲一些常用约束
(1)where T:struct 限定当前参数类型必须是值类型
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.struct值类型约束
public class StructClass<T> where T : struct
{
#region 值不能为空的约束
T t;
public StructClass(T _t)
{
t = _t;
}
public void DebugValue()
{
Debug.Log("值类型约束:" + t);
}
#endregion
#region 值能为空的约束
//T? t;
//public Animal(T? _t)
//{
// t = _t;
//}
//public void DebugValue()
//{
// Debug.Log("值类型约束:" + t);
//}
#endregion
}
//2.使用
public class GenericityTest : MonoBehaviour
{
void Start()
{
#region 值为null会报错
//Animal<int> animal = new Animal<int>(null);
//若想写入null值则需要将T改为T?方式。将上方的struct"值能为空的约束"取消注释即可
#endregion
#region 正确使用方式
//int
StructClass<int> structClass = new StructClass<int>(111);
structClass.DebugValue();
//float
StructClass<float> structClass2 = new StructClass<float>(111.1f);
//bool
StructClass<bool> structClass3 = new StructClass<bool>(false);
//注意:string是引用类型不可作为值使用可采用class约束
//StructClass<string> structClass = new StructClass<string>("123");
#endregion
}
}
(2)where T:class 限定当前类型参数类型必须是引用类型,也可用于类、接口、委托和数组
①类约束
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.定义动物类
public class AnimalClass
{
public string name;
public int age;
public AnimalClass(string _name, int _age)
{
name = _name;
age = _age;
}
}
// 2.定义动物类泛型约束
//Where T:AnimalClass 约束T值只能是AnimalClass类,其他类型不行
public class Animal<T> where T : AnimalClass
{
public T baseData;
public Animal(T _baseData)
{
baseData = _baseData;
}
}
//3.类约束创建使用
public class GenericityTest : MonoBehaviour
{
void Start()
{
AnimalClass dogBaseData = new AnimalClass("狗", 5);
Animal<AnimalClass> dog = new Animal<AnimalClass>(dogBaseData);
Debug.Log(dog.baseData.name + "年龄:" + dog.baseData.age);
}
}
②接口约束
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.创建动物行为接口
public interface AnimalAction
{
void Action();
}
//2.接口约束类
//Where T:AnimalAction 约束T值只能是AnimalAction接口,其他类型不行
public class InterfaceConstraint<T> where T : AnimalAction
{
T AnimalAction; //接口
//构造
public InterfaceConstraint(T _AnimalAction)
{
AnimalAction= _AnimalAction;
}
//打印接口Action方法
public void DebugAction()
{
AnimalAction.Action();
}
}
#region 3.继承和未继承AnimalAction接口的类
//继承接口的类
public class AnimalActionClass : AnimalAction
{
public void Action()
{
Debug.Log("我是AnimalActionClass类,继承AnimalAction接口");
}
}
//未继承接口的类
public class NotAnimalActionClass
{
public void Action()
{
Debug.Log("我是AnimalActionClass类,未继承AnimalAction接口");
}
}
#endregion
//4.使用
public class GenericityTest : MonoBehaviour
{
private void Start()
{
//①创建继承和未继承的类
AnimalActionClass animalActionClass = new AnimalActionClass();
NotAnimalActionClass notAnimalActionClass = new NotAnimalActionClass();
//②使用情况
//InterfaceConstraint<int> interfaceConstraint_1=new InterfaceConstraint<int>(new AnimalActionClass());//泛型T值类型不是定义的接口会报错。
//InterfaceConstraint<AnimalAction> interfaceConstraint_2 = new InterfaceConstraint<AnimalAction>(new NotAnimalActionClass()); //构造中写入的类没有继承AnimalAction接口会报错
InterfaceConstraint<AnimalAction> interfaceConstraint_3 = new InterfaceConstraint<AnimalAction>(animalActionClass); //正确使用方式
interfaceConstraint_3.DebugAction(); //调用方法
}
}
③数组约束
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//数组约束
public class GenericityTest : MonoBehaviour
{
BaseData[] array;
void Start()
{
//给数组赋值
BaseData basedata1 = new BaseData { name = "dog", age = 5 };
BaseData basedata2 = new BaseData { name = "cat", age = 3 };
array[0] = basedata1;
array[1] = basedata2;
Animal<BaseData> animal = new Animal<BaseData>() { m_array = array };
animal.OutPut();
}
}
//数组泛型约束
public class Animal<T> where T : BaseData
{
public T[] m_array;
public void OutPut()
{
foreach (T item in m_array)
{
Debug.Log(item.name + " " + item.age);
}
}
}
//基础属性类
public class BaseData
{
public string name;
public int age;
}
(3)where T : notnull 类型参数必须是不可为 null 的类型
使用此约束值类型为空会报错,引用类型必须配合 #nullable enable使用。否则引用类型为null是可以的。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.struct值类型约束
public class StructClass<T> where T : notnull
{
public T t { get; set; }
}
//2.使用
public class GenericityTest : MonoBehaviour
{
void Start()
{
#region 引用类型string
#nullable enable // 启用非空引用类型检查,不能赋值为null
StructClass<string> structClass = new StructClass<string>();
structClass.t = null;
#nullable disable // 禁用非空引用类型检查,可以赋值为null
StructClass<string> structClass2 = new StructClass<string>();
structClass.t = null;
#endregion
//int值类型
StructClass<int> structClass3 = new StructClass<int>();
structClass3.t = null;//为空时报错
structClass3.t = 1;//不为空时报错
}
}
(4)where T : default 设置参数化类型T
因为T在实例化之前是不知道它的类型的,T在初始化之前想要将其赋值为默认值就需要使用default。
注意:where T : default 这种写法必须在C#9.0或更高版本上使用
default(T)得到的默认值参考表:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.struct值类型约束
public class StructClass<T>
{
public T t=default;
}
//2.使用
public class GenericityTest : MonoBehaviour
{
void Start()
{
StructClass<int> structClass= new StructClass<int>();
Debug.Log(structClass.t);
}
}
(5)where T:new() 限定当前参数类型必须有一个无参构造
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.new()泛型约束
public class StructClass<T> where T : new()
{
public T t;
}
//2.无参构造函数
public class StructureClass
{
//默认有个无参构造
}
//3.有参构造
public class StructureClass2
{
public StructureClass2(int a)
{
}
}
public class GenericityTest : MonoBehaviour
{
void Start()
{
StructClass<StructureClass> structClass = new StructClass<StructureClass>();
StructClass<StructureClass2> structClass2 = new StructClass<StructureClass2>(); //有参构造报错
}
}
(6)where T: U 自定义类型约束
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.new()泛型约束
public class StructClass<T> where T : DefinedClass
{
public T t;
}
//2.自定义类
public class DefinedClass
{
public void DebugValue()
{
Debug.Log("自定义类型约束");
}
}
public class GenericityTest : MonoBehaviour
{
void Start()
{
StructClass<DefinedClass> StructClass=new StructClass<DefinedClass>(); //自定义T只能使用DefinedClass
StructClass.t.DebugValue();
//StructClass<int> StructClass2=new StructClass<int>(); //自定义T使用其他类型会报错
}
}
2.多泛型约束
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.多泛型约束
public class StructClass<T,T1>
where T : struct
where T1 : new()
{
public T t;
public T1 t1;
}
public class GenericityTest : MonoBehaviour
{
void Start()
{
//int是值类型 List<int>引用类型带构造
StructClass<int,List<int>> structClass=new StructClass<int, List<int>>();
}
}
三、 委托+构造仿造一个事件监听
using nuitrack;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
//1.定义值 泛型约束
public class EventTest<T0, T1>
where T0 : struct
where T1 : struct
{
protected event UnityAction<T0, T1> Delegate; //委托存储方法
//添加方法监听
public void AddListener(UnityAction<T0, T1> call)
{
Delegate += call;
}
//移除方法
public void RemoveListener(UnityAction<T0, T1> call)
{
//寻找添加过的方法找到后从委托移除
Delegate[] eventSubscribers = Delegate.GetInvocationList();
foreach (Delegate subscriber in eventSubscribers)
{
if (subscriber.Target == Delegate.Target && subscriber.Method == Delegate.Method)
{
// 方法已经在事件中注册
Console.WriteLine("方法已经在事件中注册");
Delegate -= call;
break;
}
}
}
//使用
public void Invoke(T0 arg0, T1 arg1)
{
Delegate.Invoke(arg0,arg1);
}
}
//2.自定义EventTest类 泛型约束
public class EventTest : EventTest<long, int> { }
public class GenericityTest : MonoBehaviour
{
//3.将EventTest类实例化,当方法容器用
public EventTest ADDAction = new EventTest();
void Start()
{
//添加方法
ADDAction.AddListener(Action);
ADDAction.AddListener(Action2);
//使用
ADDAction.Invoke(1,2);
//移除
//ADDAction.RemoveListener(Action);
//ADDAction.RemoveListener(Action2);
}
//方法
void Action(long a, int b)
{
Debug.Log("添加的方法1:"+a+b);
}
//方法2
void Action2(long a, int b)
{
Debug.Log("添加的方法2:" + a + b);
}
}
*如果以上有什么问题欢迎大家留言指正*