服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - 深入解析C#中的泛型类与泛型接口

深入解析C#中的泛型类与泛型接口

2021-11-11 14:45C#教程网 C#

这篇文章主要介绍了C#中的泛型类与泛型接口,对泛型的支持是C#语言的重要特性,需要的朋友可以参考下

泛型类

泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等。像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。
对于大多数需要集合类的方案,推荐的方法是使用 .NET Framework 类库中所提供的类。

  • 一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项:
  • 将哪些类型通用化为类型参数。
  • 通常,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。
  • 如果存在约束,应对类型参数应用什么约束。
  • 一条有用的规则是,应用尽可能最多的约束,但仍使您能够处理必须处理的类型。例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。
  • 是否将泛型行为分解为基类和子类。
  • 由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。请参见本主题后面有关从泛型基类继承的规则。
  • 是否实现一个或多个泛型接口。

例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能必须实现一个接口,如 IComparable<T>,其中 T 是您的类的类型。

类型参数和约束的规则对于泛型类行为有几方面的含义,特别是关于继承和成员可访问性。您应当先理解一些术语,然后再继续进行。对于泛型类 Node&lt;T&gt;,客户端代码可通过指定类型参数来引用该类,以便创建封闭式构造类型 (Node<int>)。或者可以让类型参数处于未指定状态(例如在指定泛型基类时)以创建开放式构造类型 (Node<T>)。泛型类可以从具体的、封闭式构造或开放式构造基类继承:

?
1
2
3
4
5
6
7
8
9
10
11
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> { }

非泛型类(换句话说,即具体类)可以从封闭式构造基类继承,但无法从开放式构造类或类型参数继承,因为在运行时客户端代码无法提供实例化基类所需的类型参数。

?
1
2
3
4
5
6
7
8
//No error
class Node1 : BaseNodeGeneric<int> { }
 
//Generates an error
//class Node2 : BaseNodeGeneric<T> {}
 
//Generates an error
//class Node3 : T {}

从开放式构造类型继承的泛型类必须为任何未被继承类共享的基类类型参数提供类型变量,如以下代码所示:

?
1
2
3
4
5
6
7
8
9
10
class BaseNodeMultiple<T, U> { }
 
//No error
class Node4<T> : BaseNodeMultiple<T, int> { }
 
//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }
 
//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}

从开放式构造类型继承的泛型类必须指定约束,这些约束是基类型约束的超集或暗示基类型约束:

?
1
2
class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }

泛型类型可以使用多个类型参数和约束,如下所示:

?
1
2
3
4
class SuperKeyType<K, V, U>
  where U : System.IComparable<U>
  where V : new()
{ }

开放式构造类型和封闭式构造类型可以用作方法参数:

?
1
2
3
4
5
6
7
8
9
void Swap<T>(List<T> list1, List<T> list2)
{
  //code to swap items
}
 
void Swap(List<int> list1, List<int> list2)
{
  //code to swap items
}

如果某个泛型类实现了接口,则可以将该类的所有实例强制转换为该接口。
泛型类是不变的。也就是说,如果输入参数指定 List<BaseClass>,则当您尝试提供 List<DerivedClass> 时,将会发生编译时错误。


泛型接口
为泛型集合类或表示集合中项的泛型类定义接口通常很有用。对于泛型类,使用泛型接口十分可取,例如使用 IComparable<T> 而不使用 IComparable,这样可以避免值类型的装箱和取消装箱操作。.NET Framework 类库定义了若干泛型接口,以用于 System.Collections.Generic 命名空间中的集合类。
将接口指定为类型参数的约束时,只能使用实现此接口的类型。下面的代码示例显示从 SortedList<T> 类派生的 GenericList<T> 类。
 SortedList<T> 添加约束 where T : IComparable<T>。这将使 SortedList<T> 中的 BubbleSort 方法能够对列表元素使用泛型 CompareTo 方法。在此示例中,列表元素为简单类,即实现 Person 的 IComparable<Person>。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//Type parameter T in angle brackets.
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
  protected Node head;
  protected Node current = null;
 
  // Nested class is also generic on T
  protected class Node
  {
    public Node next;
    private T data; //T as private member datatype
 
    public Node(T t) //T used in non-generic constructor
    {
      next = null;
      data = t;
    }
 
    public Node Next
    {
      get { return next; }
      set { next = value; }
    }
 
    public T Data //T as return type of property
    {
      get { return data; }
      set { data = value; }
    }
  }
 
  public GenericList() //constructor
  {
    head = null;
  }
 
  public void AddHead(T t) //T as method parameter type
  {
    Node n = new Node(t);
    n.Next = head;
    head = n;
  }
 
  // Implementation of the iterator
  public System.Collections.Generic.IEnumerator<T> GetEnumerator()
  {
    Node current = head;
    while (current != null)
    {
      yield return current.Data;
      current = current.Next;
    }
  }
 
  // IEnumerable<T> inherits from IEnumerable, therefore this class
  // must implement both the generic and non-generic versions of
  // GetEnumerator. In most cases, the non-generic method can
  // simply call the generic method.
  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    return GetEnumerator();
  }
}
 
public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
  // A simple, unoptimized sort algorithm that
  // orders list elements from lowest to highest:
 
  public void BubbleSort()
  {
    if (null == head || null == head.Next)
    {
      return;
    }
    bool swapped;
 
    do
    {
      Node previous = null;
      Node current = head;
      swapped = false;
 
      while (current.next != null)
      {
        // Because we need to call this method, the SortedList
        // class is constrained on IEnumerable<T>
        if (current.Data.CompareTo(current.next.Data) > 0)
        {
          Node tmp = current.next;
          current.next = current.next.next;
          tmp.next = current;
 
          if (previous == null)
          {
            head = tmp;
          }
          else
          {
            previous.next = tmp;
          }
          previous = tmp;
          swapped = true;
        }
        else
        {
          previous = current;
          current = current.next;
        }
      }
    } while (swapped);
  }
}
 
// A simple class that implements IComparable<T> using itself as the
// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
  string name;
  int age;
 
  public Person(string s, int i)
  {
    name = s;
    age = i;
  }
 
  // This will cause list elements to be sorted on age values.
  public int CompareTo(Person p)
  {
    return age - p.age;
  }
 
  public override string ToString()
  {
    return name + ":" + age;
  }
 
  // Must implement Equals.
  public bool Equals(Person p)
  {
    return (this.age == p.age);
  }
}
 
class Program
{
  static void Main()
  {
    //Declare and instantiate a new generic SortedList class.
    //Person is the type argument.
    SortedList<Person> list = new SortedList<Person>();
 
    //Create name and age values to initialize Person objects.
    string[] names = new string[]
    {
      "Franscoise",
      "Bill",
      "Li",
      "Sandra",
      "Gunnar",
      "Alok",
      "Hiroyuki",
      "Maria",
      "Alessandro",
      "Raul"
    };
 
    int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
 
    //Populate the list.
    for (int x = 0; x < 10; x++)
    {
      list.AddHead(new Person(names[x], ages[x]));
    }
 
    //Print out unsorted list.
    foreach (Person p in list)
    {
      System.Console.WriteLine(p.ToString());
    }
    System.Console.WriteLine("Done with unsorted list");
 
    //Sort the list.
    list.BubbleSort();
 
    //Print out sorted list.
    foreach (Person p in list)
    {
      System.Console.WriteLine(p.ToString());
    }
    System.Console.WriteLine("Done with sorted list");
  }
}

可将多重接口指定为单个类型上的约束,如下所示:

?
1
2
3
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}

一个接口可定义多个类型参数,如下所示:

?
1
2
3
interface IDictionary<K, V>
{
}

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

?
1
2
3
4
5
6
interface IMonth<T> { }
 
interface IJanuary   : IMonth<int> { } //No error
interface IFebruary<T> : IMonth<int> { } //No error
interface IMarch<T>  : IMonth<T> { }  //No error
//interface IApril<T> : IMonth<T, U> {} //Error

如果泛型接口为逆变的,即仅使用其类型参数作为返回值,则此泛型接口可以从非泛型接口继承。在 .NET Framework 类库中,IEnumerable<T> 从 IEnumerable 继承,因为 IEnumerable<T> 只在 GetEnumerator 的返回值和 Current 属性 getter 中使用 T。
具体类可以实现已关闭的构造接口,如下所示:

?
1
2
3
interface IBaseInterface<T> { }
 
class SampleClass : IBaseInterface<string> { }


只要类参数列表提供了接口必需的所有参数,泛型类便可以实现泛型接口或已关闭的构造接口,如下所示:

?
1
2
3
4
5
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
 
class SampleClass1<T> : IBaseInterface1<T> { }     //No error
class SampleClass2<T> : IBaseInterface2<T, string> { } //No error

延伸 · 阅读

精彩推荐
  • C#三十分钟快速掌握C# 6.0知识点

    三十分钟快速掌握C# 6.0知识点

    这篇文章主要介绍了C# 6.0的相关知识点,文中介绍的非常详细,通过这篇文字可以让大家在三十分钟内快速的掌握C# 6.0,需要的朋友可以参考借鉴,下面来...

    雨夜潇湘8272021-12-28
  • C#SQLite在C#中的安装与操作技巧

    SQLite在C#中的安装与操作技巧

    SQLite,是一款轻型的数据库,用于本地的数据储存。其优点有很多,下面通过本文给大家介绍SQLite在C#中的安装与操作技巧,感兴趣的的朋友参考下吧...

    蓝曈魅11162022-01-20
  • C#利用C#实现网络爬虫

    利用C#实现网络爬虫

    这篇文章主要介绍了利用C#实现网络爬虫,完整的介绍了C#实现网络爬虫详细过程,感兴趣的小伙伴们可以参考一下...

    C#教程网11852021-11-16
  • C#如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    这篇文章主要给大家介绍了关于如何使用C#将Tensorflow训练的.pb文件用在生产环境的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴...

    bbird201811792022-03-05
  • C#深入理解C#的数组

    深入理解C#的数组

    本篇文章主要介绍了C#的数组,数组是一种数据结构,详细的介绍了数组的声明和访问等,有兴趣的可以了解一下。...

    佳园9492021-12-10
  • C#C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    这篇文章主要介绍了C#设计模式之Strategy策略模式解决007大破密码危机问题,简单描述了策略模式的定义并结合加密解密算法实例分析了C#策略模式的具体使用...

    GhostRider10972022-01-21
  • C#VS2012 程序打包部署图文详解

    VS2012 程序打包部署图文详解

    VS2012虽然没有集成打包工具,但它为我们提供了下载的端口,需要我们手动安装一个插件InstallShield。网上有很多第三方的打包工具,但为什么偏要使用微软...

    张信秀7712021-12-15
  • C#C#微信公众号与订阅号接口开发示例代码

    C#微信公众号与订阅号接口开发示例代码

    这篇文章主要介绍了C#微信公众号与订阅号接口开发示例代码,结合实例形式简单分析了C#针对微信接口的调用与处理技巧,需要的朋友可以参考下...

    smartsmile20127762021-11-25