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

扫描二维码关注公众号,回复: 14594095 查看本文章
// 泛型类
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);

猜你喜欢

转载自blog.csdn.net/qq_50571974/article/details/129891993