【C#进阶】C# 泛型

序号 系列文章
17 【C#进阶】C# 委托
18 【C#进阶】C# 事件
19 【C#进阶】C# 集合类

文章目录

  • 前言
  • 1、泛型的概念
  • 2、泛型的使用
    • 2.1、类型参数的命名
    • 2.2、泛型类
    • 2.3、泛型接口
    • 2.4、泛型方法
    • 2.5、泛型委托
  • 3、泛型参数的约束
    • 3.1、值类型约束
    • 3.2、引用类型约束
  • 4、泛型的特性总结
  • 结语

前言

hello大家好啊,我是哈桑c,本文为大家介绍 C# 中的泛型。


1、泛型的概念

泛型允许延迟指定一个或多个变量的类型。泛型指的是类或其它数据类型在定义时不会指定类中的属性或方法参数的具体类型,而是在实例化类对象时再确定参数的具体类型。

以一个程序示例展示泛型的基本用法,并借此讨论泛型的使用。

代码示例

using System;
using System.Collections.Generic;

public class MyGenericList<T>
{
    private List<T> myList;

    public MyGenericList()
    {
        myList = new List<T> { };
    }

    // 增加元素
    public void AddElement(T value)
    {
        myList.Add(value);
    }

    // 删除元素
    public void RemoveElement(T value)
    {
        myList.Remove(value);
    }
    
    // 更改元素
    public void SetElement(int index, T value)
    {
        myList[index] = value;
    }

    // 查找元素
    public T GetElement(int index)
    {
        return myList[index];
    }
}

public class Program
{
    static void Main(string[] args)
    {
        MyGenericList<int> list = new MyGenericList<int> { }; 
        
        for(int e = 0; e < 10; e++)
            list.AddElement(e);

        Console.WriteLine("list列表中的元素有:");
        for (int i = 0; i < 10; i++)
            Console.WriteLine(list.GetElement(i));
    }
}

运行结果:

【C#进阶】C# 泛型_第1张图片
在上面的示例中,类中出现的每个 T 在运行时均会被替换为 int 类型参数。 通过这种替换,我们就可以通过单个泛型定义来确定类中四个增删改查方法中的参数类型,并成功输出 int 类型的列表变量。

2、泛型的使用

在 C# 中,只要涉及到使用类型参数的地方基本都可以使用泛型。泛型常用于类、方法、接口和委托等等。适当的使用泛型有利于提高代码的灵活性、重用性等。

2.1、类型参数的命名

关于类型参数的命名官方的建议可以总结为以下四点:

  • 和参数命名一样,请使用描述性名称命名泛型类型参数,除非单个字母名称完全具有自我说明性且描述性名称不会增加任何作用
public interface ISessionChannel<TSession> { /*...*/ }
  • 对具有单个字母类型参数的类型,习惯使用 T 作为类型参数名称。
public class MyGenericList<T>
  • 在类型参数描述性名称前添加前缀 “T”。
public interface ISessionChannel<TSession>
{
    TSession Session { get; }
}
  • 请考虑在参数名称中指示出类型参数的约束。 例如,约束为 ISession 的参数可命名为 TSession。
// 泛型T被约束为ISession
public T MySession(T TSession){/*...*/} 

接下里介绍泛型常见的几个用法:

2.2、泛型类

泛型类表示类中有一个或多个参数的类型为泛型。泛型类最常见用法是用于链接列表、哈希表、堆栈、队列和数等集合。而且无论存储数据的类型如何,在数据操作方面等执行方式都基本相同。

通常创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。同时创建泛型类需要注意以下四点:

  • 选择需要泛化的类型参数:通常可泛化的类型越多,代码就越灵活、可重用性就越高。但过度泛化会导致代码可读性变低。
  • 是否使用泛型约束:合理的使用泛型类型参数约束,可防止数据类型的误用。
  • 实现一个或多个泛型接口:泛型可用于接口,如果需要实现在泛型的集合中创建项的类,则可以创建一个或多个泛型接口。
  • 是否将泛型行为分解为基类和子类:泛型类可用作基类,但是泛型类作为基类或子类需要遵循一定的继承规则。

代码示例:(泛型类的继承行为)、

泛型类可继承自具体的封闭式构造或开放式构造基类:

class BaseNode { }
class BaseNodeGeneric<T> { }

// 具体类型
class NodeConcrete<T> : BaseNode { }

// 封闭构造类型 
class NodeClosed<T> : BaseNodeGeneric<int> { }

// 开放构造类型 
class NodeOpen<T> : BaseNodeGeneric<T> { }

非泛型类不可继承自开放式构造类或类型参数,因为运行时无法提供基类所需的类型参数。

// 没有问题
class Node1 : BaseNodeGeneric<int> { }

// 产生 CS0246 编译错误
//class Node2 : BaseNodeGeneric { }

点击了解更多泛型类的继承行为。

2.3、泛型接口

泛型接口表示接口中有一个或多个参数的类型为泛型。 使用泛型接口有很多好处,例如可以避免对值类型执行装箱和取消装箱操作。 当一组表示抽象规范的接口需要针对多种类型时就可以使用泛型接口。

代码示例:

// 具体类可实现封闭式构造接口 

// 基础接口 
interface IBaseInterface<T>
{
    public T MyMethod(T value);
}

// 实现接口的方法
class SampleClass : IBaseInterface<int>
{
    // 实现接口中的方法
    public int MyMethod(int value)
    {
        return value;
    }
} 

适用于类的继承规则也适用于接口:

interface IMonth<T> { }

interface IJanuary : IMonth<int> { }  //没有错误
interface IFebruary<T> : IMonth<int> { }  //没有错误
interface IMarch<T> : IMonth<T> { }    //没有错误

//interface IApril : IMonth { }  // 编译错误 CS0305

2.4、泛型方法

泛型方法表示方法中有一个或多个参数的类型为泛型。在前面的示例中,我们已经使用了泛型类,可以在泛型类中顺利通过类型声明泛型方法。当一组操作针对多种类型参数时需要使用泛型方法。

代码示例:

using System;
using System.Collections.Generic;

public class SamplesGenericMethod
{
    static void Main(string[] args)
    {
        int a = 10;
        int b = 20;

        Swap<int>(ref a, ref b);
        Console.WriteLine($"交换之后的a和b的值分别为{a}{b}");
    }

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

运行结果
【C#进阶】C# 泛型_第2张图片

2.5、泛型委托

不仅是在方法和类,委托也可以定义它自己的类型参数。泛型委托表示在定义中使用泛型代替具体类型的委托。引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,同时也可以不指定具体类型参数创建开放式构造类型。当一组引用类型需要封装多种类型方法时就可以使用泛型委托。

代码示例:

public class SamplesGenericDelegate
{ 
    public delegate void Del<T>(T item);

    public static void Notify(int i) 
    {
        Console.WriteLine($"使用了泛型委托,并接受了整数参数{i}");
    }

    static void Main(string[] args)
    {
        Del<int> m1 = Notify;
        m1(11); 
    }
}

运行结果:
【C#进阶】C# 泛型_第3张图片

3、泛型参数的约束

泛型约束就是告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。当分配给泛型的类型参数不满足约束的类型时,编译器会报出 Compiler Error CS0452 的错误。在 C# 中允许使用 where 上下文关键字指定约束。

【C#进阶】C# 泛型_第4张图片

3.1、值类型约束

where T : struct 表示类型参数必须是不可为 null 的值类型。

这里的示例代码记得改类名

代码示例:

// 值类型约束
public class MyGenericList<T> where T : struct
{
	// 类成员... 
}

由于所有值类型都具有可访问的无参数构造函数,因此 struct 约束也表示 new() 约束,所以 struct 约束不能与 new() 约束和 unmanaged 约束一起使用。

3.2、引用类型约束

where T : class 表示类型参数必须是的引用类型。

代码示例:

// 引用类型约束
public class MyGenericList<T> where T : class
{
	// 类成员... 
}

此约束还应用于任何类、接口、委托或数组类型。 在可为 null 的上下文中,T 必须是不可为 null 的引用类型。

泛型约束的介绍独立出一篇文章来了,点击了解更多泛型约束的使用。

4、泛型的特性总结

在 C# 中泛型是一种增强程序功能的技术,具体体现为以下几点:

  • 泛型的好处: 正确地使用泛型可以提高代码的可读性、重用性以及灵活性,可以在一定程度上提高程序的性能。
  • 泛型的坏处: 错误或者过度地使用泛型容易导致类型异常、使代码变得复杂(可读性变低)以及很难确定参数真正的类型等问题。
  • 用途广泛: 除了文中提到泛型的用法之外,还可以创建自定义的泛型数组、泛型事件以及泛型和反射的结合等。
  • 泛型集合类:.NET 框架在 System.Collections.Generic 命名空间中提供了泛型集合类,其中包含用于定义泛型集合的接口和类,可允许用户创建强类型集合,以提供比非泛型强类型集合更好的类型安全性和性能。

点击了解更多泛型的使用。


结语

⛽️ 以上就是 C# 泛型的介绍啦,希望对大家有所帮助。感谢大家的支持。

你可能感兴趣的:(从基础到进阶系列,c#,.netcore,asp.net,微软,.net)