unity的C#学习——泛型的创建与继承泛型集合类泛型中的约束和反射

Posted 时生丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity的C#学习——泛型的创建与继承泛型集合类泛型中的约束和反射相关的知识,希望对你有一定的参考价值。

文章目录


泛型

C#泛型是C#2.0中引入的一个非常重要的新功能。它允许开发人员编写具有通用行为的类、接口和方法,这些类、接口和方法可以用于不同的数据类型,而无需针对每种类型编写不同的代码。泛型为C#编程带来了许多优势,如更高的代码重用性、更好的性能和更好的类型安全性。


1、泛型的名称由来

泛型的英文名称是“Generics”

“Generics"一词源于拉丁语中的"generalis”,意思是一般的或通用的。在计算机科学领域,泛型是指一种编程语言的特性,可以在代码编写时不指定具体的数据类型,而是使用类型参数来表示。这样可以编写通用的代码,适用于多种不同的数据类型,提高了代码的复用性和灵活性。

在中文中,泛型通常被称为“范型”“通用类型”。其中"范型"一词可能来自于"范式",即规范或模板的意思,而"通用类型"则强调了泛型的通用性和灵活性。

简单来说,泛型意味着当前代码可以广泛的被各种数据类型通用,通过替换掉代码中的泛型类型参数


2、泛型的创建与调用

我们可以创建自己的泛型接口、泛型类、泛型方法、泛型事件泛型委托,通过在接口名、类名等后方使用<>来声明泛型类型参数,并将其作为数据类型实现参数传递、变量创建、对象实例化等操作。

下面的实例创建了一个泛型方法Swap,其中 T 作为泛型类型参数,被用来声明了 lhs、rhs 这两个引用传递的参数,并创建了一个变量 temp

static void Swap<T> (ref T lhs, ref T rhs) 
 
    T temp; 
    temp = lhs; 
    lhs = rhs; 
    rhs = temp; 

开发人员可以在调用方法时为 T 指定实际的类型,同样需要在方法名后使用<>。例如,下面的示例演示了使用 int 类型调用 Swap 方法的方式:

public static void TestSwap() 
 
    int a = 1; 
    int b = 2; 
    Swap<int> (ref a, ref b); 
    System ... 

在声明泛型类、接口或方法时,可以使用逗号分隔的方式指定多个泛型类型参数。例如在下面这段代码中,MyClass 类、IMyInterface 接口和 MyMethod 方法都有两个泛型参数 T1T2

// 泛型类
public class MyClass<T1, T2>

    public T1 MyProperty  get; set;  // 泛型类的自动属性
    public T2 MyMethod(T1 arg)	// 泛型类的方法
    
        // implementation
    

// 泛型接口
public interface IMyInterface<T1, T2>

    void MyMethod(T1 arg1, T2 arg2);

// 泛型方法
public void MyMethod<T1, T2>(T1 arg1, T2 arg2)

    // implementation


3、泛型类与非泛型类的继承

  • 泛型类可继承自非泛型类、泛型类的封闭式构造类型(指定类型参数,如:Node<int>)或开放式构造类型(不指定类型参数,如:Node<T>)的基类:
class BaseNode   // 非泛型类
class BaseNodeGeneric<T>    // 泛型类

// 继承自非泛型类
class NodeConcrete<T> : BaseNode  

// 继承自泛型类的封闭式构造基类
class NodeClosed<T> : BaseNodeGeneric<int>  

// 继承自泛型类的开放式构造基类
class NodeOpen<T> : BaseNodeGeneric<T>  
  • 非泛型类可继承自封闭式构造基类,但不可继承自开放式构造类类型参数,因为运行时客户端代码无法提供实例化基类所需的类型参数:
// 继承自泛型类的封闭式构造基类
class Node1 : BaseNodeGeneric<int>  

// 错误继承
//class Node2 : BaseNodeGeneric<T> 

// 错误继承
//class Node3 : T 
  • 继承自开放式构造类型的泛型类,父类中必须对子类中未共享的类型参数提供具体的数据类型,如下方代码所示:
// 泛型类
class BaseNodeMultiple<T, U>  

// 继承自泛型类的开放式构造基类,只共享了类型参数 T,那么需要为类型参数 U 指定具体的数据类型
class Node4<T> : BaseNodeMultiple<T, int>  

// 继承自泛型类的开放式构造基类,共享了类型参数 T 和 U
class Node5<T, U> : BaseNodeMultiple<T, U>  

// 错误的基础,只共享了类型参数 T,却未指定 U 的具体数据类型
//class Node6<T> : BaseNodeMultiple<T, U> 
  • 泛型接口与非泛型接口的继承规则与上述相同。

4、泛型集合类

C# 中的 System.Collections.Generic 命名空间包含了许多新的泛型集合类。下面列举并介绍其中一些:

  • List<T>:动态数组,支持增删改查等操作,是最常用的集合类之一。
  • Dictionary<TKey, TValue>:键值对集合,根据键快速查找值,用于存储一组相关联的数据。
  • HashSet<T>:无序不重复集合,支持集合操作如并集、交集、差集等。
  • Queue<T>:队列,先进先出(FIFO)。
  • Stack<T>:栈,后进先出(LIFO)。
  • LinkedList<T>:链表,支持添加、删除、查找等操作,比 List 更适合频繁的插入和删除操作。
  • SortedList<TKey, TValue>:有序键值对集合,根据键排序,支持快速访问和查找。
  • SortedSet<T>:有序不重复集合,根据元素的比较器进行排序。

以上是一些常用的泛型集合类,还有许多其他的泛型集合类,比如 BitArrayQueue<T>Stack<T>HashSet<T> 等,具体可以参考官方文档

C#的泛型集合类和普通集合类最主要的区别在于类型安全性和性能方面:

  • 泛型集合类是在.NET Framework 2.0引入的,主要在System.Collections.Generic命名空间中实现。泛型集合类在编译时提供类型安全性检查,可以避免在运行时出现类型转换错误,提高了程序的稳定性和可维护性。此外,泛型集合类也可以提高程序的性能,因为它们避免了装箱和拆箱操作,减少了内存分配和垃圾回收的负担。

  • 普通集合类是在.NET Framework 1.0中引入的,主要在System.Collections命名空间中实现。普通集合类使用Object类型存储集合中的元素,这意味着需要在运行时进行类型转换,容易出现类型错误。此外,普通集合类也不能避免装箱和拆箱操作,导致程序性能较低。

5、泛型中约束与反射的应用

5.1 约束——限定可使用的数据类型

在 C# 中,可以使用约束(constraint)对泛型类进行限制,以访问特定数据类型的方法和属性。

例如,可以将类型参数约束为某个特定的接口或基类,以便在泛型类中访问该接口或基类中定义的方法和属性。约束语法使用 where 关键字,如下所示:

public class MyClass<T> where T : MyBaseClass, IMyInterface

    // 泛型类的成员

在上面的例子中,MyClass<T> 是一个泛型类,其中 T 是类型参数。where T : MyBaseClass, IMyInterface 是约束语法,它指定 T 必须继承自 MyBaseClass 类,并实现 IMyInterface 接口(即 T 是一个满足上述条件的类对象)。因此,在泛型类 MyClass<T> 中,可以访问 MyBaseClassIMyInterface 中定义的方法和属性。

5.2 反射——获取要使用的数据类型

泛型数据类型中使用的类型信息可以在运行时使用反射来获取

反射是.NET框架中一种强大的机制,可以在运行时查看、创建和调用类型、方法和属性等成员。在使用反射时,可以使用 Type 类来获取类型信息,并使用 MethodInfo 类来获取方法信息。

using System;
using System.Reflection;

public class MyClass<T>

    public T MyProperty  get; set;  // 泛型类的自动属性


class Program

    static void Main()
    
        // 创建 泛型类型 的Type实例
        Type myType = typeof(MyClass<>);  
		// 创建一个Type数组,内部存储了一个 int类型 的Type实例 
        Type[] typeArgs =  typeof(int) ; 
		// 创建 具有指定类型参数的泛型类型 的Type实例
        Type constructed = myType.MakeGenericType(typeArgs); 
		// 创建 具有指定类型参数的 泛型类型实例
        object myInstance = Activator.CreateInstance(constructed); 
		// 创建 MyProperty属性 的PropertyInfo实例
        PropertyInfo myPropertyInfo = constructed.GetProperty("MyProperty"); 
		// 设置属性值
        myPropertyInfo.SetValue(myInstance, 42); 
		// 输出属性值到控制台
        Console.WriteLine(myPropertyInfo.GetValue(myInstance)); 
    

  • Activator.CreateInstance(type, args) 方法是一种在运行时创建类实例的方法,它允许我们在不知道具体类型的情况下创建实例。

    • type参数是要创建实例的类型。
    • args是对象数组,用于在创建实例时传递给构造函数的参数。
    • 在上述代码中,使用该方法相当于创建了一个 MyClass<int> 的实例对象。
  • MakeGenericType用于创建具有指定类型参数的泛型类型的 Type 实例。

    • 该方法需要一个 Type 数组作为参数,用于指定创建的泛型类型的类型参数。
    • 在上述代码中,使用该方法相当于为 Type 实例 myType 指定了实际的类型为 int,然后返回。

上述两个方法通常一起使用,可以创建一个具有指定类型参数的泛型对象的实例。例如,如果要创建一个具有两个类型参数的泛型类型 List<T1, T2> 的实例对象 listInstance,可以使用以下代码:

Type[] typeArgs =  typeof(int), typeof(string) ;
Type listType = typeof(List<,>).MakeGenericType(typeArgs);
object listInstance = Activator.CreateInstance(listType);

带有泛型的 C# 不寻常的继承语法

【中文标题】带有泛型的 C# 不寻常的继承语法【英文标题】:C# unusual inheritance syntax w/ generics 【发布时间】:2011-06-05 15:42:42 【问题描述】:

我在 NHibernate 类定义中遇到了这个问题:

public class SQLiteConfiguration : PersistenceConfiguration<SQLiteConfiguration>

所以这个类继承自一个基类,该基类由...派生类参数化?我的头刚刚爆炸。

有人能解释一下这是什么意思以及这种模式有什么用处吗?

(顺便说一句,这不是一个特定于 NHibernate 的问题。)

【问题讨论】:

这是***.com/questions/3783321/…的副本 Eric:我在我的 cmets 中向 Lambert 引用了这个 SO 问题。 我很久以前读过这个帖子,但现在才遇到这个特定的场景。我通过搜索“我的头刚刚爆炸”再次找到了该主题。哈哈。好东西是你写的,否则我可能再也找不到了。 :-) “Curiously Recurring Template Pattern”是个很无聊的名字。我在此将这种模式重新命名为“颅骨破裂模板模式”。 【参考方案1】:

That's a funny Curiously Recurring Template Pattern, isn't it?

【讨论】:

嗯。我从来没有想过我会发现自己对继承语法大笑。这是另一个有用的 SO 问题:***.com/questions/1327568/… 这是另一个很棒的讨论:***.com/questions/3783321/…。另外:fernandof.wordpress.com/2007/09/23/…【参考方案2】:

我在开发双链树时使用了相同的模式。每个节点有 1 个父节点和 0 个子节点

class Tree<T> where T : Tree<T>

    T parent;
    List<T> children;
    T Parent  get; set; 
    protected Tree(T parent) 
    
        this.parent = parent; 
        parent.children.Add(this);
    
    // lots more code handling tree list stuff

实现

class Coordinate : Tree<Coordinate>

    Coordinate(Coordinate parent) : this(parent)  
    static readonly Coordinate Root = new Coordinate(null);
    // lots more code handling coordinate systems

用法

Coordinate A = Coordinate.Root;
Coordinate B = new Coordinate(A);

B.Parent // returns a Coordinate type instead of a Node<>.

所以从Tree&lt;&gt; 继承的任何东西都将包含适当类型的父对象和子对象的属性。这个技巧对我来说是纯粹的魔法

【讨论】:

谢谢你,jalexiou,非常具体的回复!我不得不承认,我需要一段时间才能理解这一点。 @anon - 试试看!启动旧的 C# 开发并编写类似这样的代码。 @anon - 当然,它缺少构造函数、属性访问器等等。它只是骨架代码。

以上是关于unity的C#学习——泛型的创建与继承泛型集合类泛型中的约束和反射的主要内容,如果未能解决你的问题,请参考以下文章

泛型的使用

C#学习笔记8

Java泛型学习笔记 - 泛型的继承

Java泛型的使用

C# 集合与泛型

C#泛型Dictionary的用法实例详解