Unity中泛型<T>和泛型约束where 的使用

一、什么是泛型?

泛型(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的默认值。 default 约束表示基方法,但不包含 class 或 struct 约束。 有关详细信息,请参阅default约束规范建议。

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 : UU代表我们自定义的类型。 在可为 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);
    }
}

*如果以上有什么问题欢迎大家留言指正*

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值