欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-09-07 20:05:10
泛型类 泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等。像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存...

泛型类

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

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

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

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

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> { }

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

//no error
class node1 : basenodegeneric<int> { }

//generates an error
//class node2 : basenodegeneric<t> {}

//generates an error
//class node3 : t {}

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

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> {} 

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

class nodeitem<t> where t : system.icomparable<t>, new() { }
class specialnodeitem<t> : nodeitem<t> where t : system.icomparable<t>, new() { }

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

class superkeytype<k, v, u>
  where u : system.icomparable<u>
  where v : new()
{ }

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

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>。

//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");
  }
}

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

class stack<t> where t : system.icomparable<t>, ienumerable<t>
{
}

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

interface idictionary<k, v>
{
}

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

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。
具体类可以实现已关闭的构造接口,如下所示:

interface ibaseinterface<t> { }

class sampleclass : ibaseinterface<string> { }


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

interface ibaseinterface1<t> { }
interface ibaseinterface2<t, u> { }

class sampleclass1<t> : ibaseinterface1<t> { }     //no error
class sampleclass2<t> : ibaseinterface2<t, string> { } //no error