Unity脚本系统学习(四):C#中泛型

问题:

1.为什么使用泛型

2.泛型的底层级制是怎么运行的

3.泛型中的类型约束和类型推断是什么

一、泛型的作用

1.最主要的目的:实现另一种形式的代码复用,即“逻辑复用”

2.泛型的好处:

  1. 类型安全:当使用不兼容类型的对象时编译器会报错,保证只有与制定数据类型兼容的对象才能用于该泛型类型或泛型逻辑
  2. 更加清晰的代码:不必进行很多强制类型转换
  3. 更加优秀的性能:防止通过Object类进行强制转换导致的装拆箱操作,与此同时MonoRunTime无须验证类型转换的安全性,这使得安全检查从RunTime转移到了CompileTime,提高了代码速度。(Why?)

二、泛型的底层实现(简单)

1.需要创建新的CIL指令,在CIL的层面实现识别类型实参的功能

2.修改引入泛型之前的元数据表的格式,使具有泛型参数的类型和方法能够正确表示

3.修改具体的编程语言,使之能够支持新的语法

4.修改编译器(将C#编译为CIL的编译器),使编程语言(C#)能够正确地被编译为对应的CIL代码

5.修改JIT编译器(将CIL代码编译为原生代码),是新创建的处理类型实参的CIL代码能够被正确地编译为对应平台的原生代码

6.创建新的反射成员,使开发人员能够查询类型和成员,同时判断它们是否具有泛型参数,同时使开发人员能够在运行时创建泛型类型和泛型方法

三、泛型类型T

1.开放类型:举例 Dictionary<,>

2.封闭类型:Dictionary<TKey,TValue>

3.泛型类型T的继承:Mono把你指定的T创建了一个新的类型,但并不影响继承关系

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
//定义一个部分指定类型参数的开放类型
public class DictionaryOnePara<TValue>:Dictionary<string,TValue>
{

}
public class TExample : MonoBehaviour
{
    private Dictionary<string, int> counts = new Dictionary<string, int>();
    // Start is called before the first frame update
    void Start()
    {
        object obj = null;
        //获得两个泛型参数都没有指定的开放类型Dictionary<,>的类型信息
        Type type1 =typeof(Dictionary<,>);
        //创建该类型的实例,报错
        obj = CreateInstance(type1);
        Debug.Log("对象类型" + obj);

        object obj2 = null;
        //获得有一个泛型参数没有指定的开放类型DictionaryOnePara<>的类型信息
        Type type2 = typeof(DictionaryOnePara<>);
        //创建该类型的实例,报错
        obj2 = CreateInstance(type2);
        Debug.Log("对象类型" + obj2);

        object obj3 = null;
        //获得有指定泛型参数的封闭类型DictionaryOnePara<>的类型信息
        Type type3 = typeof(DictionaryOnePara<int>);
        //创建该类型的实例,成功
        obj3 = CreateInstance(type3);
        Debug.Log("对象类型" + obj3);
    }

    private static object CreateInstance(Type type)
    {
        object obj = null;
        try
        {
            obj = Activator.CreateInstance(type);
            Debug.Log( type.ToString()+ "类的实例创建成功!");
        }
        catch(ArgumentException e)
        {
            Debug.Log(e.Message);
        }
        return obj;
    }
}

(DictionaryOnePara<int>继承与Dictionary<string,TValue>,我们可以看到前面的类已经确定了T的类型,但是它依旧继承于泛型类Dictionary)

(举例:List<String>和List<int>是两个不同的类型)

四、泛型接口和泛型委托

1.泛型接口的作用是防止使用非泛型接口操作值类型时可能引起的装箱操作

2.泛型委托

public delegate TResult Func<in T,out TResult>(T arg)

有in关键字,T是逆变量,有out关键字,TResult是协变量

(逆变量:泛型类型参数可以从一个类更换为该类的某个派生类(子类))

(协变量:泛型类型参数可以从一个类更换为该类的某个基类(父类))

实例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

//比较器,遇到自定义类排序时需要
public class BadGuy :IComparable<BadGuy>
{
    public string name;
    public int power;
     

    public BadGuy(string newName,int newPower)
    {
        name = newName;
        power = newPower;
    }

    public int CompareTo(BadGuy Other)
    {
        if(Other==null)
        {
            return 1;
        }

        return power - Other.power;
    }
}


public class Test : MonoBehaviour
{
    private void Start()
    {
        //创建一个新的List类实例,并且填充数据
        List<int> nums = new List<int>();
        nums.Add(1);
        nums.Add(9);
        nums.Add(8);
        nums.Add(9);
        //创建委托实例,该委托实现将int转换为string
        Converter<int, string> converter = this.TakeIntToString;
        //创建目标类型为string 的List
        List<string> strings = new List<string>();
        //指定ConvertAll<TOutPut>中的类型参数TOutput为string型的
        strings = nums.ConvertAll<string>(converter);
        foreach(string s in strings)
        {
            Debug.Log(s);
        }
    }
     public string TakeIntToString(int num)
    {
        return num.ToString();
    }
}

五、泛型方法

public List<TOutput> ConvertAll<TOutput>(Converter<T,TOutput> converter)
{
    if(converter ==null)
    {
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.converter)   
    }
    Contract.EndContractBlock();
    List<TOutput>list = new List<TOutput>(_size)
        for(int i=0;i<_size;i++)
        {
            list._items[i] = converter(_items[i]);
        }
        list._size =_size;
    return list
}

其中List<TOutput>是返回类型,ConvertAll是方法名,紧跟方法名的是泛型类型参数<TOutput>,最后是泛型委托Converter<T,TOutput>和其参数converter

六、泛型中的类型约束和类型推断

1.使用where关键字来限制泛型的类型

struct Example<T> where T:class  //引用类型约束,T必须是引用类型
    
struct Example<T> where T:struct  //值类型约束,T必须是值类型
    
struct T CreateGameInstance<T>() where T :new() //构造函数类型约束,T必须拥有构造函数
    
//转换类型约束,T必须是指定类型能够进行转换的类型或其本身
class Example<T> where T:Stream 
Example<Stream>  //一致性转换

public class FatherExample { }
public class ChildrenExample : FatherExample { }
public class Example<T> where T:FatherExample
Example<ChildrenExample>  //引用转换

public class Example<T> where T:IComparable<T>
Example<int>//装箱转换

public class Example<T> where T:IComparable<T>,FatherExample,IDisposable//转换类型约束只能指定一个类,却可以指定多个接口

2.约束的组合

  1. 主要约束:引用类型,值类型,类的转换类型约束
  2. 次要约束:接口或其它类型参数的转换类型 
  3. 构造函数约束
  4. 主要约束每次只能选一个,次要约束可以有多个

3.类型推断(只适用于泛型方法

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

//比较器,遇到自定义类排序时需要
public class FatherExample { }
public class ChildrenExample : FatherExample { }
public class Example<T> where T:IComparable<T>,FatherExample,IDisposable
{ 

}


public class Test : MonoBehaviour
{


    private void Start()
    {
        int num1 = 1;
        int num2 = 2;
        //能够正确的进行类型推断,因为方法中的实参类型一致,都为int
        GenericExampleMethod(num1,num2);

        string str1 = "hello world";
        float numF = 0.1f;
        //不能够正确的进行类型推断,因为方法中的实参类型不一致,编译器推断不出来
        GenericExampleMethod(str1, numF);
    }

    public static void GenericExampleMethod<T>(T obj1, T obj2)
    {
        Debug.Log(obj1.ToString());
        Debug.Log(obj2.ToString());
    }
}

 

posted @ 2022-04-21 10:43  不进育碧不改名  阅读(410)  评论(0)    收藏  举报