【C# 】泛型

泛型类和泛型方法、泛型接口

泛型向 .NET 引入了类型参数的概念。 泛型支持设计类和方法
可在在代码中使用该类或方法时,再定义一个或多个类型参数的规范
在编译过程中将泛型类型参数替换为类型参数



一、什么是泛型?

泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。
泛型将类型参数的概念引入 .NET Framework;
类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。
例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险。

系统自定义的泛型:

  • List<string>
  • List<int>
  • List<object>
  • Dictionary<Key ,Value> 泛型字典;Key是键不能重复,不是序号;
    <这里的类型是不限制的 >
    List<object> objList = new List<object>(){}; //object
// Declare the generic class.
public class GenericList<T>
{
    public void Add(T input) { }
}
class TestGenericList
{
    private class ExampleClass { }
    static void Main()
    {
        // Declare a list of type int.
        GenericList<int> list1 = new GenericList<int>();
        list1.Add(1);

        // Declare a list of type string.
        GenericList<string> list2 = new GenericList<string>();
        list2.Add("");

        // Declare a list of type ExampleClass.
        GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
        list3.Add(new ExampleClass());
    }
}

二、泛型的用途

  1. 使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
  2. 泛型最常见的用途是创建集合类。
  3. .NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList。
  4. 使用者可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
  5. 可以对泛型类进行约束以访问特定数据类型的方法。
  6. 关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。

三、泛型类

  • 封装不特定于特定数据类型的类

优点:
通用性;
可以由多个参数;
class MyGeneric<T1, T2, T3>

注意事项:

  1. 要将哪些类型泛化为类型参数。

通常,可参数化的类型越多,代码就越灵活、其可重用性就越高。 但过度泛化会造成其他开发人员难以阅读或理解代码。

  1. 要将何种约束(如有)应用到类型参数(请参阅类型参数的约束)。

其中一个有用的规则是,应用最大程度的约束,同时仍可处理必须处理的类型。 例如,如果知道泛型类仅用于引用类型,则请应用类约束。 这可防止将类意外用于值类型,并使你可在 T 上使用 as 运算符和检查 null 值。

  1. 是否将泛型行为分解为基类和子类。

因为泛型类可用作基类,所以非泛型类的相同设计注意事项在此也适用。 请参阅本主题后文有关从泛型基类继承的规则。

  1. 实现一个泛型接口还是多个泛型接口。

例如,如果要设计用于在基于泛型的集合中创建项的类,则可能必须实现一个接口,例如 IComparable,其中 T 为类的类型。

1.自定义一个泛型类

MyGeneric<string> myGeneric = new MyGeneric<string>("自定义泛型!");
Console.WriteLine(myGeneric.ToString());

class MyGeneric<T>
{
Private T param;
public MyGeneric(T p)
{
this.p = param;
}
}

MyGeneric<int> myGeneric = new MyGeneric<int>(227);
Console.WriteLine(myGeneric.ToString());

四.泛型方法

写一个类出来,可以共用;

class BaseNode { } //基类
class BaseNodeGeneric<T> { } //泛型基类

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

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

//open constructed type  开放构造类型
class NodeOpen<T> : BaseNodeGeneric<T> { }
 		public void Show<T, K,S>(T t,K k,S s)
        {
            Console.WriteLine(t.ToString());
        }
 		
//控制器
 public class FirstController : Controller
    {
        // GET: First
        public ActionResult Index()
        {
		base.ViewBag.showstr = Show<string>("泛型方法show<>()");
		base.ViewBag.showint = Show(123);

 		public string Show<T>(T t)
        {
            return t.ToString();
        }
        }
        }
//视图
@{
    ViewBag.Title = "Index";
}

<h2>this is the first Index:表现层</h2>
<h3>showstr = @base.ViewBag.showstr</h3>
<h3>showint = @base.ViewBag.showint</h3>

五、泛型接口

3.利用泛型的优点和原理、

优点:

  • 通用性
  • 一开始不用确定参数类型,使用的时候再确定传递的参数类型
  • object 类型做数据转换的时候,有拆箱装箱的操作,有性能损耗,要少用
  • object 类型参数可以传递引用类型和值类型,容易引发异常
  • 泛型可以规定允许传什么类型的参数的

六、泛型的约束

1.约束告知编译器类型参数必须具备的功能。
2.在没有任何约束的情况下,类型参数可以是任何类型。
3.编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。
4.如果客户端代码使用不满足约束的类型,编译器将发出错误。
5.通过使用 where 上下文关键字指定约束。

1.基类约束

class A { public void Function1() { ……} }
class B { public void Function2() { ……} }
class C<S ,T> 
where S:A   //S继承自A
where T:B    //T继承自B
{
		// 可以在类型为S的变量上调用方法Function1
		// 可以在类型为T的变量上调用方法Function2
}

2. 各种类型的约束:

约束说明
where T : struct类型参数必须是不可为 null 的值类型,其中包含 record struct 类型。 有关可为 null 的值类型的信息,请参阅可为 null 的值类型。 由于所有值类型都具有可访问的
where T : class类型参数必须是引用类型。 此约束还应用于任何类、接口、委托或数组类型。 在可为 null 的上下文中,T 必须是不可为 null 的引用类型。
where T : class?类型参数必须是可为 null 或不可为 null 的引用类型。 此约束还应用于任何类、接口、委托或数组类型(包括记录)。
where T : notnull类型参数必须是不可为 null 的类型。 参数可以是不可为 null 的引用类型,也可以是不可为 null 的值类型。
where T : unmanaged类型参数必须是不可为 null 的非托管类型。 unmanaged 约束表示 struct 约束,且不能与 struct 约束或 new() 约束结合使用。
where T : new()类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定。 new() 约束不能与 struct 和 unmanaged 约束结合使用。
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 : U为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 在可为 null 的上下文中,如果 U 是不可为 null 的引用类型,T 必须是不可为 null 的引用类型。 如果 U 是可为 null 的引用类型,则 T 可以是可为 null 的引用类型,也可以是不可为 null 的引用类型。
where T : default重写方法或提供显式接口实现时,如果需要指定不受约束的类型参数,此约束可解决歧义。 default 约束表示基方法,但不包含 class 或 struct 约束。 有关详细信息,请参阅default约束规范建议。

3.使用约束的原因

  1. 约束指定类型参数的功能和预期。
  2. 声明这些约束意味着开发者可以使用约束类型的操作和方法调用。
  3. 如果泛型类或方法对泛型成员使用除简单赋值之外的任何操作,包括调用 System.Object 不支持的任何方法,则对类型参数应用约束。 例如,基类约束告诉编译器,只有此类型的对象或派生自此类型的对象可替换该类型参数。
  4. 编译器有了此保证后,就能够允许在泛型类中调用该类型的方法。

以下代码示例演示可通过应用基类约束添加到(泛型介绍中的)GenericList 类的功能。

public class Employee
{
    public Employee(string name, int id) => (Name, ID) = (name, id);
    public string Name { get; set; }
    public int ID { get; set; }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        public Node(T t) => (Next, Data) = (null, t);

        public Node? Next { get; set; }
        public T Data { get; set; }
    }

    private Node? head;

    public void AddHead(T t)
    {
        Node n = new Node(t) { Next = head };
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node? current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T? FindFirstOccurrence(string s)
    {
        Node? current = head;
        T? t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property.
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}

约束使泛型类能够使用 Employee.Name 属性。 约束指定类型 T 的所有项都保证是 Employee 对象或从 Employee 继承的对象。

4.可以对同一类型参数应用多个约束,并且约束自身可以是泛型类型,

如下所示:

class EmployeeList<T> where T : Employee, System.Collections.Generic.IList<T>, IDisposable, new()
{
    // ...
}

5.在应用 where T : class 约束时,请避免对类型参数使用 == 和 != 运算符,

因为这些运算符仅测试引用标识而不测试值相等性。 即使在用作参数的类型中重载这些运算符也会发生此行为。 下面的代码说明了这一点;即使 String 类重载 == 运算符,输出也为 false。

public static void OpEqualsTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}

private static void TestStringEquality()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpEqualsTest<string>(s1, s2);
}

编译器只知道 T 在编译时是引用类型,并且必须使用对所有引用类型都有效的默认运算符。 如果必须测试值相等性,请应用 where T : IEquatable 或 where T : IComparable 约束,并在用于构造泛型类的任何类中实现该接口。

6.约束多个参数

可以对多个参数应用多个约束,对一个参数应用多个约束,如下例所示:

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new()
{ }

7.未绑定的类型参数

没有约束的类型参数(如公共类 SampleClass{} 中的 T)称为未绑定的类型参数。 未绑定的类型参数具有以下规则:

不能使用 != 和 == 运算符,因为无法保证具体的类型参数能支持这些运算符。
可以在它们与 System.Object 之间来回转换,或将它们显式转换为任何接口类型。
可以将它们与 null 进行比较。 将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较始终返回 false。

8.类型参数作为约束

在具有自己类型参数的成员函数必须将该参数约束为包含类型的类型参数时,将泛型类型参数用作约束非常有用,如下例所示:

public class List<T>
{
    public void Add<U>(List<U> items) where U : T {/*...*/}
}

类型参数还可在泛型类定义中用作约束。 必须在尖括号中声明该类型参数以及任何其他类型参数:

//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }

类型参数作为泛型类的约束的作用非常有限,因为编译器除了假设类型参数派生自 System.Object 以外,不会做其他任何假设。 如果要在两个类型参数之间强制继承关系,可以将类型参数用作泛型类的约束。

notnull 约束
可以使用 notnull 约束指定类型参数必须是不可为 null 的值类型或不可为 null 的引用类型。 与大多数其他约束不同,如果类型参数违反 notnull 约束,编译器会生成警告而不是错误。

notnull 约束仅在可为 null 上下文中使用时才有效。 如果在过时的可为 null 上下文中添加 notnull 约束,编译器不会针对违反约束的情况生成任何警告或错误。

class 约束
可为 null 的上下文中的 class 约束指定类型参数必须是不可为 null 的引用类型。 在可为 null 上下文中,当类型参数是可为 null 的引用类型时,编译器会生成警告。

default 约束
添加可为空引用类型会使泛型类型或方法中的 T? 使用复杂化。 T? 可以与 struct 或 class 约束一起使用,但必须存在其中一项。 使用 class 约束时,T? 引用了 T 的可为空引用类型。 可在这两个约束均未应用时使用 T?。 在这种情况下,对于值类型和引用类型,T? 解读为 T?。 但是,如果 T 是 Nullable的实例,则 T? 与 T 相同。 换句话说,它不会成为 T??。

由于现在可在没有 class 或 struct 约束的情况下使用 T?,因此在重写或显式接口实现中可能会出现歧义。 在这两种情况下,重写不包含约束,但从基类继承。 当基类不应用 class 或 struct 约束时,派生类需要通过某种方式在不使用任一种约束的情况下指定应用于基方法的重写。 派生方法应用 default 约束。 default 约束不阐明 class 和 struct 约束。

总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值