【转载】C#中的泛型 (From dotNet SDK 2.0 Beta1)

C# 泛型编程基础:从类到方法,全面解析 在 C# 中,泛型是指使用类型参数来创建类、方法或接口,而不需要在编写代码时指定具体的类型。类型参数可以是任何类型,直到代码实际执行时,类型才会被确定。例如,常见的泛型类List,其中T是类型参数,可以是任何数据类型,如intstringPerson类等。泛型使得代码可以处理不同的类型,避免了每次需要为每种类型编写不同的实现,同时也增强了类型安全性。泛型类是一个类,其中的类型参数可以在实例化时指定,从而使得类可以处理不同的数据类型。 阅读详情

 

 

    泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性。泛型为.NET框架引入了类型参数(type parameters)的概念。类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。这意味着使用泛型的类型参数T,写一个类MyList<T>,客户代码可以这样调用:MyList<int>, MyList<string>或 MyList<MyClass>。这避免了运行时类型转换或装箱操作的代价和风险。

目录

C#中的泛型. 1

一、泛型概述. 2


二、泛型的优点. 5


三、泛型类型参数. 7


四、类型参数的约束. 8

五、泛型类. 11

六、泛型接口. 13

七、泛型方法. 19

八、泛型委托. 21

九、泛型代码中的default关键字. 23

十、C++模板和C#泛型的区别. 24

十一、运行时中的泛型. 25

十二、基础类库中的泛型. 27


一、泛型概述

泛型类和泛型方法兼复用性、类型安全和高效率于一身,是与之对应的非泛型的类和方法所不及。泛型广泛用于容器(collections)和对容器操作的方法中。.NET框架2.0的类库提供一个新的命名空间System.Collections.Generic,其中包含了一些新的基于泛型的容器类。要查找新的泛型容器类(collection classes)的示例代码,请参见基础类库中的泛型。当然,你也可以创建自己的泛型类和方法,以提供你自己的泛化的方案和设计模式,这是类型安全且高效的。下面的示例代码以一个简单的泛型链表类作为示范。(多数情况下,推荐使用由.NET框架类库提供的List<T>类,而不是创建自己的表。)类型参数T在多处使用,具体类型通常在这些地方来指明表中元素的类型。类型参数T有以下几种用法:

l 在AddHead方法中,作为方法参数的类型。

l 在公共方法GetNext中,以及嵌套类Node的 Data属性中作为返回值的类型。

l 在嵌套类中,作为私有成员data的类型。

注意一点,T对嵌套的类Node也是有效的。当用一个具体类来实现MyList<T>时——如MyList<int>——每个出现过的T都要用int代替。

 

using System;
using System.Collections.Generic;

public class MyList<T> //type parameter T in angle brackets
{
   private Node head;
   //The nested type is also generic on T.

   private class Node 
   {
     private Node next;
     //T as private member data type:
     private T data; 
     //T used in non-generic constructor:

     public Node(T t) 
     {
      next = null;
      data = t;
     }

     public Node Next
     {
       get { return next; }
       set { next = value; }
     }

     //T as return type of property:
     public T Data 
     {
       get { return data; }
       set { data = value; }
     }
   }

   public MyList()
   {
     head = null;
   }

   //T as method parameter type:
   public void AddHead(T t) 
   {
     Node n = new Node(t);
     n.Next = head;
     head = n;
   }

   public IEnumerator<T> GetEnumerator()
   {
     Node current = head;
     while (current != null)
     {
       yield return current.Data;
       current = current.Next;
     }
   }
}

下面的示例代码演示了客户代码如何使用泛型类MyList<T>,来创建一个整数表。通过简单地改变参数的类型,很容易改写下面的代码,以创建字符串或其他自定义类型的表。

 

class Program{static void Main(string[] args)
{
    //int is the type argument.
    MyList<int> list = new MyList<int>(); 
    for (int x = 0; x < 10; x++)
      list.AddHead(x);
    foreach (int i in list)
      {Console.WriteLine(i);}
    Console.WriteLine("Done");}
}



 
 


二、泛型的优点

针对早期版本的通用语言运行时和C#语言的局限,泛型提供了一个解决方案。以前类型的泛化(generalization)是靠类型与全局基类System.Object的相互转换来实现。.NET框架基础类库的ArrayList容器类,就是这种局限的一个例子。ArrayList是一个很方便的容器类,使用中无需更改就可以存储任何引用类型或值类型。

 

//The .NET Framework 1.1 way of creating a list
ArrayList list1 = new ArrayList(); 
list1.Add(3); 
list1.Add(105); 
//...
ArrayList list2 = new ArrayList();
list2.Add(“It is raining in Redmond.”); 
list2.Add("It is snowing in the mountains.");
//...

 

但是这种便利是有代价的,这需要把任何一个加入ArrayList的引用类型或值类型都隐式地向上转换成System.Object。如果这些元素是值类型,那么当加入到列表中时,它们必须被装箱;当重新取回它们时,要拆箱。类型转换和装箱、拆箱的操作都降低了性能;在必须迭代(iterate)大容器的情况下,装箱和拆箱的影响可能十分显著。

 

另一个局限是缺乏编译时的类型检查,当一个ArrayList把任何类型都转换为Object,就无法在编译时预防客户代码类似这样的操作:

 

ArrayList list = new ArrayList(); 
//Okay. 
list.Add(3); 
//Okay, but did you really want to do this?
list.Add(.“It is raining in Redmond.”); 
int t = 0;
//This causes an InvalidCastException to be returned.
foreach(int x in list) 
{
   t += x;
}

虽然这样完全合法,并且有时是有意这样创建一个包含不同类型元素的容器,但是把string和int变量放在一个ArrayList中,几乎是在制造错误,而这个错误直到运行的时候才会被发现。

在1.0版和1.1版的C#语言中,你只有通过编写自己的特定类型容器,才能避免.NET框架类库的容器类中泛化代码(generalized code)的危险。当然,因为这样的类无法被其他的数据类型复用,也就失去泛型的优点,你必须为每个需要存储的类型重写该类。

ArrayList和其他相似的类真正需要的是一种途径,能让客户代码在实例化之前指定所需的特定数据类型。这样就不需要向上类型转换为Object,而且编译器可以同时进行类型检查。换句话说,ArrayList需要一个类型参数。这正是泛型所提供的。在System.Collections.Generic命名空间中的泛型List<T>容器里,同样是把元素加入容器的操作,类似这样:

The .NET Framework 2.0 way of creating a list

List<int> list1 = new List<int>();

//No boxing, no casting:

list1.Add(3);

//Compile-time error:

list1.Add("It is raining in Redmond.");

 与ArrayList相比,在客户代码中唯一增加的List<T>语法是声明和实例化中的类型参数。代码略微复杂的回报是,你创建的表不仅比ArrayList更安全,而且明显地更加快速,尤其当表中的元素是值类型的时候。

 

三、泛型类型参数

 

在泛型类型或泛型方法的定义中,类型参数是一个占位符(placeholder),通常为一个大写字母,如T。在客户代码声明、实例化该类型的变量时,把T替换为客户代码所指定的数据类型。泛型类,如泛型概述中给出的MyList<T>类,不能用作as-is,原因在于它不是一个真正的类型,而更像是一个类型的蓝图。要使用MyList<T>,客户代码必须在尖括号内指定一个类型参数,来声明并实例化一个已构造类型(constructed type)。这个特定类的类型参数可以是编译器识别的任何类型。可以创建任意数量的已构造类型实例,每个使用不同的类型参数,如下:

 MyList<MyClass> list1 = new MyList<MyClass>();

MyList<float> list2 = new MyList<float>();

MyList<SomeStruct> list3 = new MyList<SomeStruct>();

 

在这些MyList<T>的实例中,类中出现的每个T都将在运行的时候被类型参数所取代。依靠这样的替换,我们仅用定义类的代码,就创建了三个独立的类型安全且高效的对象。有关CLR执行替换的详细信息,请参见运行时中的泛型。

 

四、类型参数的约束

 

若要检查表中的一个元素,以确定它是否合法或是否可以与其他元素相比较,那么编译器必须保证:客户代码中可能出现的所有类型参数,都要支持所需调用的操作或方法。这种保证是通过在泛型类的定义中,应用一个或多个约束而得到的。一个约束类型是一种基类约束,它通知编译器,只有这个类型的对象或从这个类型派生的对象,可被用作类型参数。一旦编译器得到这样的保证,它就允许在泛型类中调用这个类型的方法。上下文关键字where用以实现约束。下面的示例代码说明了应用基类约束,为MyList<T>类增加功能。

 

public class Employee
{
   public class Employee 
   {
     private string name;
     private int id;

     public Employee(string s, int i)
     {
       name = s;
       id = i;
     }

     public string Name
     {
       get { return name; }
       set { name = value; }
     }

     public int ID
     {
       get { return id; }
       set { id = value; }
     }
   }
}

class MyList<T> where T: Employee
{
   //Rest of class as before.
   public T FindFirstOccurrence(string s)
   {
     T t = null;
     Reset();
     while (HasItems())
     {
       if (current != null)
       {
         //The constraint enables this:
         if (current.Data.Name == s)
         {
           t = current.Data;
           break;
         }
         else
         {
           current = current.Next;
         }
      } //end if
    } // end while
    return t;
   }
}

约束使得泛型类能够使用Employee.Name属性,因为所有为类型T的元素,都是一个Employee对象或是一个继承自Employee的对象。

 同一个类型参数可应用多个约束。约束自身也可以是泛型类,如下:

 class MyList<T> where T: Employee, IEmployee, IComparable<T>, new()

{…}

 

下表列出了五类约束:

约束
描述

where T: struct
类型参数必须为值类型。

where T : class
类型参数必须为类型。

where T : new()
类型参数必须有一个公有、无参的构造函数。当于其它约束联合使用时,new()约束必须放在最后。

where T : <base class name>
类型参数必须是指定的基类型或是派生自指定的基类型。

where T : <interface name>
类型参数必须是指定的接口或是指定接口的实现。可以指定多个接口约束。接口约束也可以是泛型的。

 

类型参数的约束,增加了可调用的操作和方法的数量。这些操作和方法受约束类型及其派生层次中的类型的支持。因此,设计泛型类或方法时,如果对泛型成员执行任何赋值以外的操作,或者是调用System.Object中所没有的方法,就需要在类型参数上使用约束。

 

无限制类型参数的一般用法

没有约束的类型参数,如公有类MyClass<T>{...}中的T, 被称为无限制类型参数(unbounded type parameters)。无限制类型参数有以下规则:

l 不能使用运算符 != 和 == ,因为无法保证具体的类型参数能够支持这些运算符。

l 它们可以与System.Object相互转换,也可显式地转换成任何接口类型。

l 可以与null比较。如果一个无限制类型参数与null比较,当此类型参数为值类型时,比较的结果总为false。

 

 无类型约束

当约束是一个泛型类型参数时,它就叫无类型约束(Naked type constraints)。当一个有类型参数成员方法,要把它的参数约束为其所在类的类型参数时,无类型约束很有用。如下例所示:

 

class List<T>

{

//...

void Add<U>(List<U> items) where U:T {…}

}

 

在上面的示例中, Add方法的上下文中的T,就是一个无类型约束;而List类的上下文中的T,则是一个无限制类型参数。

 无类型约束也可以用在泛型类的定义中。注意,无类型约束一定也要和其它类型参数一起在尖括号中声明:

//naked type constraint

public class MyClass<T,U,V> where T : V

 

因为编译器只认为无类型约束是从System.Object继承而来,所以带有无类型约束的泛型类的用途十分有限。当你希望强制两个类型参数具有继承关系时,可对泛型类使用无类型约束。

 

五、泛型类

 

 泛型类封装了不针对任何特定数据类型的操作。泛型类常用于容器类,如链表、哈希表、栈、队列、树等等。这些类中的操作,如对容器添加、删除元素,不论所存储的数据是何种类型,都执行几乎同样的操作。

 

对大多数情况,推荐使用.NET框架2.0类库中所提供的容器类。有关使用这些类的详细信息,请参见基础类库中的泛型。

 

通常,从一个已有的具体类来创建泛型类,并每次把一个类型改为类型参数,直至达到一般性和可用性的最佳平衡。当创建你自己的泛型类时,需要重点考虑的事项有:

l 哪些类型应泛化为类型参数。一般的规律是,用参数表示的类型越多,代码的灵活性和复用性也就越大。过多的泛化会导致代码难以被其它的开发人员理解。

l 如果有约束,那么类型参数需要什么样约束。一个良好的习惯是,尽可能使用最大的约束,同时保证可以处理所有需要处理的类型。例如,如果你知道你的泛型类只打算使用引用类型,那么就应用这个类的约束。这样可以防止无意中使用值类型,同时可以对T使用as运算符,并且检查空引用。

l 把泛型行为放在基类中还是子类中。泛型类可以做基类。同样非泛型类的设计中也应考虑这一点。泛型基类的继承规则 。

l 是否实现一个或多个泛型接口。例如,要设计一个在基于泛型的容器中创建元素的类,可能需要实现类似IComparable<T>的接口,其中T是该类的参数。

 

泛型概述中有一个简单泛型类的例子。

 

类型参数和约束的规则对于泛型类的行为(behavior)有一些潜在的影响,——尤其是对于继承和成员可访问性。在说明这个问题前,理解一些术语十分重要。对于一个泛型类Node<T>,客户代码既可以通过指定一个类型参数来创建一个封闭构造类型(Node<int>),也可以保留类型参数未指定,例如指定一个泛型基类来创建开放构造类型(Node<T>)。泛型类可以继承自具体类、封闭构造类型或开放构造类型:

 

// concrete type

class Node<T> : BaseNode

//closed constructed type

class Node<T> : BaseNode<int>

//open constructed type

class Node<T> : BaseNode<T>

 

非泛型的具体类可以继承自封闭构造基类,但不能继承自开放构造基类。这是因为客户代码无法提供基类所需的类型参数。

 

//No error.

class Node : BaseNode<int>

//Generates an error.

class Node : BaseNode<T>

 

泛型的具体类可以继承自开放构造类型。除了与子类共用的类型参数外,必须为所有的类型参数指定类型,如下代码所示:

//Generates an error.

class Node<T> : BaseNode<T, U> {…}

//Okay.

class Node<T> : BaseNode<T, int>{…}

 

继承自开放结构类型的泛型类,必须指定:

Generic classes that inherit from open constructed types must specify must specify constraints that are a superset of, or imply, the constraints on the base type:

 

class NodeItem<T> where T : IComparable<T>, new() {…}

class MyNodeItem<T> : NodeItem<T> where T : IComparable<T> , new(){…}

 

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

class KeyType<K,V>{…}

class SuperKeyType<K,V,U> where U : IComparable<U>, where V : new(){…}

 

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

void Swap<T>(List<T> list1, List<T> list2){…}

void Swap(List<int> list1, List<int> list2){…}

 

六、泛型接口

不论是为泛型容器类,还是表示容器中元素的泛型类,定义接口是很有用的。把泛型接口与泛型类结合使用是更好的用法,比如用IComparable<T>而非IComparable,以避免值类型上的装箱和拆箱操作。.NET框架2.0类库定义了几个新的泛型接口,以配合System.Collections.Generic中新容器类的使用。

 

当一个接口被指定为类型参数的约束时,只有实现该接口的类型可被用作类型参数。下面的示例代码显示了一个从MyList<T>派生的SortedList<T>类。更多信息,请参见泛型概述。SortedList<T>增加了约束where T : IComparable<T>。

这使得SortedList<T>中的BubbleSort方法可以使用表中的元素的IComparable<T>.CompareTo方法。在这个例子中,表中的元素是简单类——实现IComparable<Person>的Person类。

 

using System;
using System.Collections.Generic;

//Type parameter T in angle brackets.

public class MyList<T> 
{
  protected Node head;
  protected Node current = null;

  // Nested type is also generic on T
  protected class Node 
  {
    public Node next;
    //T as private member datatype.
    private T data; 

    //T used in non-generic constructor.
    public Node(T t) 
    {
      next = null;
      data = t;
    }

    public Node Next
    {
      get { return next; }
      set { next = value; }
    }

    //T as return type of property.
    public T Data 
    {
      get { return data; }
      set { data = value; }
    }
  }

  public MyList()
  {
    head = null;
  }

  //T as method parameter type.
  public void AddHead(T t) 
  {
    Node n = new Node(t);
    n.Next = head;
    head = n; 
  }

  // Implement IEnumerator<T> to enable foreach
  // iteration of our list. Note that in C# 2.0 
  // you are not required to implment Current and
  // GetNext. The compiler does that for you.
  public IEnumerator<T> GetEnumerator()
  {
    Node current = head;
    while (current != null)
    {
      yield return current.Data;
      current = current.Next;
    }
  }
}

 
public class SortedList<T> : MyList<T> where T : 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;
        }
      }// end while
    } 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 : 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(string[] args)
  {
    //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)
    {
      Console.WriteLine(p.ToString());
    }

    //Sort the list.
    list.BubbleSort();

    //Print out sorted list.
    foreach (Person p in list)
    {
      Console.WriteLine(p.ToString());
    }

    Console.WriteLine("Done");
  }
}

可以在一个类型指定多个接口作为约束,如下:

class Stack<T> where T : IComparable<T>, IMyStack1<T>{}

 

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

 

IDictionary<K,V>

 

接口和类的继承规则相同:

//Okay.

IMyInterface : IBaseInterface<int>

//Okay.

IMyInterface<T> : IBaseInterface<T>

 

//Okay.

IMyInterface<T>: IBaseInterface<int>

//Error.

IMyInterface<T> : IBaseInterface2<T, U>

 

具体类可以实现封闭构造接口,如下:

class MyClass : IBaseInterface<string>

 

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

//Okay.

class MyClass<T> : IBaseInterface<T>

//Okay.

class MyClass<T> : IBaseInterface<T, string>

 

泛型类、泛型结构,泛型接口都具有同样方法重载的规则。详细信息,请参见泛型方法。

 

七、泛型方法

 泛型方法是声名了类型参数的方法,如下:

 

void Swap<T>( ref T lhs, ref T rhs)

{

   T temp;

   temp = lhs;

   lhs = rhs;

   rhs = temp;

}

 

 

下面的示例代码显示了一个以int作为类型参数,来调用方法的例子:

 

int a = 1;

int b = 2;

//…

Swap<int>(a, b);

 

也可以忽略类型参数,编译器会去推断它。下面调用Swap的代码与上面的例子等价:

Swap(a, b);

 

 

静态方法和实例方法有着同样的类型推断规则。编译器能够根据传入的方法参数来推断类型参数;而无法单独根据约束或返回值来判断。因此类型推断对没有参数的方法是无效的。类型推断发生在编译的时候,且在编译器解析重载方法标志之前。编译器对所有同名的泛型方法应用类型推断逻辑。在决定(resolution)重载的阶段,编译器只包含那些类型推断成功的泛型类。更多信息,请参见C# 2.0规范,20.6.4类型参数推断

 

在泛型方法中,非泛型方法能访问所在类中的类型参数,如下:

class MyClass<T>

{

  //…

  void Swap (ref T lhs, ref T rhs){…}

}

 

不能[JX1] 定义一个泛型方法,和其所在的类具有相同的类型参数;试图这样做,编译器会产生警告CS0693。

 

class MyList<T>

{

  // CS0693

  void MyMethod<T>{...}

}

 

class MyList<T>

{

  //This is okay, but not common.

  void SomeMethod<U>(){...}

}

 

使用约束可以在方法中使用更多的类型参数的特定方法。这个版本的Swap<T>称为SwapIfGreater<T>,它只能使用实现了IComparable<T>的类型参数。

void SwapIfGreater<T>( ref T lhs, ref T rhs) where T: IComparable<T>

{

T temp;

if(lhs.CompareTo(rhs) > 0)

{

   temp = lhs;

   lhs = rhs;

   rhs = temp;

}

}

 

泛型方法通过多个类型参数来重载。例如,下面的这些方法可以放在同一个类中:

void DoSomething(){}

void DoSomething<T>(){}

void DoSomething<T,U>(){}

 

 

八、泛型委托

无论是在类定义内还是类定义外,委托可以定义自己的类型参数。引用泛型委托的代码可以指定类型参数来创建一个封闭构造类型,这和实例化泛型类或调用泛型方法一样,如下例所示:

 

public delegate void MyDelegate<T>(T item);

public void Notify(int i){}

//...

MyDelegate<int> m = new MyDelegate<int>(Notify);

 

C#2.0版有个新特性称为方法组转换(method group conversion),具体代理和泛型代理类型都可以使用。用方法组转换可以把上面一行写做简化语法:

MyDelegate<int> m = Notify;

 

在泛型类中定义的委托,可以与类的方法一样地使用泛型类的类型参数。

 

class Stack<T>

{

T[] items;

int index

//...

public delegate void StackDelegate(T[] items);

}

 

引用委托的代码必须要指定所在类的类型参数,如下:

 

Stack<float> s = new Stack<float>();

Stack<float>.StackDelegate myDelegate = StackNotify;

 

泛型委托在定义基于典型设计模式的事件时特别有用。因为sender具有强壮的类型[JX2] ,而再也不用与Object相互转换。

 

public void StackEventHandler<T,U>(T sender, U eventArgs);
class Stack<T>
{
  //…
  public class StackEventArgs : EventArgs{...}
  public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;

  protected virtual void OnStackChanged(StackEventArgs a)
  {
    stackEvent(this, a);
  }
}

class MyClass
{
  public static void HandleStackChange<T>(Stack<T> stack, StackEventArgs args){...};
}

Stack<double> s = new Stack<double>();

MyClass mc = new MyClass();

s.StackEventHandler += mc.HandleStackChange;

九、泛型代码中的default关键字

 

在泛型类和泛型方法中会出现的一个问题是,如何把缺省值赋给参数化类型,此时无法预先知道以下两点:

l T将是值类型还是引用类型

l 如果T是值类型,那么T将是数值还是结构

 

对于一个参数化类型T的变量t,仅当T是引用类型时,t = null语句才是合法的; t = 0只对数值的有效,而对结构则不行。这个问题的解决办法是用default关键字,它对引用类型返回空,对值类型的数值型返回零。而对于结构,它将返回结构每个成员,并根据成员是值类型还是引用类型,返回零或空。下面MyList<T>类的例子显示了如何使用default关键字。更多信息,请参见泛型概述。

 

public class MyList<T>
{
  //...
  public T GetNext()
  {
    T temp = default(T);
    if (current != null)
    {
      temp = current.Data;
      current = current.Next;
    }
    return temp;
  }
}

十、C++模板和C#泛型的区别

(未翻译)

 

C# Generics and C++ templates are both language features that provide support for parameterized types. However, there are many differences between the two. At the syntax level, C# generics are a simpler approach to parameterized types without the complexity of C++ templates. In addition, C# does not attempt to provide all of the functionality that C++ templates provide. At the implementation level, the primary difference is that C# generic type substitutions are performed at runtime and generic type information is thereby preserved for instantiated objects. For more information, see Generics in the Runtime.

 

The following are the key differences between C# Generics and C++ templates:

· C# generics do not provide the same amount of flexibility as C++ templates. For example, it is not possible to call arithmetic operators in a C# generic class, although it is possible to call user defined operators.

· C# does not allow non-type template parameters, such as template C<int i> {}.

· C# does not support explicit specialization; that is, a custom implementation of a template for a specific type.

· C# does not support partial specialization: a custom implementation for a subset of the type arguments.

· C# does not allow the type parameter to be used as the base class for the generic type.

· C# does not allow type parameters to have default types.

· In C#, a generic type parameter cannot itself be a generic, although constructed types can be used as generics. C++ does allow template parameters.

· C++ allows code that might not be valid for all type parameters in the template, which is then checked for the specific type used as the type parameter. C# requires code in a class to be written in such a way that it will work with any type that satisfies the constraints. For example, in C++ it is possible to write a function that uses the arithmetic operators + and - on objects of the type parameter, which will produce an error at the time of instantiation of the template with a type that does not support these operators. C# disallows this; the only language constructs allowed are those that can be deduced from the constraints.

 

十一、运行时中的泛型

Specialized generic types are created once for each unique value type used as a parameter.

 

当泛型类或泛型方法被编译为微软中间语言(MSIL)后,它所包含的元数据定义了它的类型参数。根据所给的类型参数是值类型还是引用类型,对泛型类型所用的MSIL也是不同的。

当第一次以值类型作为参数来构造一个泛型类型,运行时用所提供的参数或在MSIL中适当位置被替换的参数,来创建一个专用的泛型类型。运行时会为每个唯一的作为参数的值类型创建一个的专用泛型类型。[JX3]

 

例如,假设你的程序代码声名一个由整型构成的栈,如:

 

Stack<int> stack;

 

此时,运行时用整型恰当地替换了它的类型参数,生成一个专用版本的栈。此后,程序代码再用到整型栈时,运行时复用已创建的专用的栈。下面的例子创建了两个整型栈的实例,它们共用一个Stack<int>代码实例:

 

Stack<int> stackOne = new Stack<int>();

Stack<int> stackTwo = new Stack<int>();

 然而,如果由另一种值类型——如长整型或用户自定义的结构——作为参数,在代码的其他地方创建另一个栈,那么运行时会生成另一个版本的泛型类型。这次是把长整型替换到MSIL中的适当的位置。由于每个专用泛型类原本就包含值类型,因此不需要再转换。

 对于引用类型,泛型的工作略有不同。当第一次用任何引用类型构造泛型类时,运行时在MSIL中创建一个专用泛型类,其中的参数被对象引用所替换。之后,每当用一个引用类型作为参数来实例化一个已构造类型时,就忽略其类型,运行时复用先前创建的专用版本的泛型类。这可能是由于所有的引用的大小都相同。

 例如,假如你有两个引用类型,一个Customer类和一个Order类;进一步假设你创建了一个Customer的栈:

 Stack<Customer> customers;

 此时,运行时生成一个专用版本的栈,用于稍后存储对象的引用,而不是存储数据。假如下一行代码创建了一个另一种引用类型的栈,名为Order:

 Stack<Order> orders = new Stack<Order>();

 和值类型不同,运行时并没有为Order类型创建另一个栈的专用版本。相反,运行时创建了一个专用版本栈实例,并且变量orders指向这个实例。如果之后是一行创建Customer类型的栈的代码:

 customers = new Stack<Customer>();

 和之前以Order类型创建的栈一样,创建了专用栈的另一个实例,并且其中所包含的指针指向一块大小与Customer类一致的内存。由于不同程序间引用类型的数量差别很大,而编译器只为引用类型的泛型类创建一个专用类,因此C#对泛型的实现极大地降低了代码膨胀。

此外,当用类型参数实现一个泛型C#类时,想知道它是指类型还是引用类型,可以在运行时通过反射确定它的真实类型和它的类型参数。

 

 十二、基础类库中的泛型

2.0版的.NET框架类库提供了一个新的命名空间,System.Collections.Generic,其中包含了一些已经可以使用的泛型容器类和相关的接口。和早期版本的.NET框架提供的非泛型容器类相比,这些类和接口更高效且是类型安全的。在设计、实现自定义的容器类之前,请你考虑是否使用或继承所列出类中的一个。

 

下面的表格列出了新的泛型类和接口,旁边是对应的非泛型类和接口。在一些地方要特别注意,如List<T>和Dictionary<T>,新泛型类的行为(behavior)与它们所替换的非泛型类有些不同,也不完全兼容。更详细的内容,请参考System.Collections.Generic的文档

 

泛型类或接口
描述
对应的非泛型类型

Collection<T>

ICollection<T>
为泛型容器提供基类
CollectionBase

ICollection

Comparer<T>

IComparer<T>

IComparable<T>
比较两个相同泛型类型的对象是否相等、可排序。
Comparer

IComparer

IComparable

Dictionary<K, V>

IDictionary<K,V>
表示用键组织的键/值对集合。
Hashtable

IDictionary

Dictionary<K, V>.KeyCollection
表示Dictionary<K, V>中键的集合。
None.

Dictionary<K, V>.ValueCollection
表示Dictionary<K, V>中值的集合。
None.

IEnumerable<T>

IEnumerator<T>
表示可以使用foreach 迭代的集合。
IEnumerable

IEnumerator

KeyedCollection<T, U>
表示有键值的集合。
KeyedCollection

LinkedList<T>
表示双向链表。
None.

LinkedListNode<T>
表示LinkedList<T>中的节点。
None.

List<T>

IList<T>
使用大小可按需动态增加的数组实现 IList 接口
ArrayList

IList

Queue<T>
表示对象的先进先出集合。
Queue

ReadOnlyCollection<T>
为泛型只读容器提供基类。
ReadOnlyCollectionBase

SortedDictionary<K, V>
表示键/值对的集合,这些键和值按键排序并可按照键访问,实现IComparer<T>接口。
SortedList

Stack<T>
表示对象的简单的后进先出集合。
Stack

原载地址:http://www.alixixi.com/Dev/Web/ASPNET/aspnet3/2007/200702067168.html

 

C#泛型实例详解 本文以实例形式讲述了C#泛型的用法,有助于读者深入理解C#泛型的原理,具体分析如下: 首先需要明白什么时候使用泛型: 当针对不同的数据类型,采用相似的逻辑算法,为了避免重复,可以考虑使用泛型。 一、针对类的泛型 针对不同类型的数组,写一个针对数组的”冒泡排序”。 1.思路 ● 针对类的泛型,泛型打在类旁。 ● 由于在”冒泡排序”中需要对元素进行比较,所以泛型要约束成实现IComparable接口。 class Program { static void Main(string[] args) { SortHelper isorter = new 立即下载

相关推荐

USB2.0信号完整性测试之眼图

名称线缆颜色引脚描述红色绿色白色GND黑色。

qq_53766840的博客 3287

C#中的泛型

编写程序时,经常遇到两个模块的功能非常相似,区别只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,我们只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。ArrayList不是类型安全的,使用时很可能出现类型不匹配的错误,就算都插入了同一类型的数据,使用时我们也需要将它们转化为对应的原类型来处理。泛型类的定义与泛型方法类似,是在泛型类的名称后面加上<T>,当然,也可以定义多个类型,即“<T1,T2,・・・>“ArrayList 存储在装箱和拆箱操作,导致其性能低下。

AuraroTeen的博客 5206

Multisim 14.0元件库下载权威指南:官方与社区资源

详解Multisim元件库下载的官方途径与可靠社区资源,帮助用户快速获取Multisim 14.0所需元件模型,提升电路仿真效率。

weixin_32925455的博客 752

C#泛型的理解

泛型的概念 定义:泛型允许我们延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程 序中使用它的时候。(也就是说泛型是可以与任何数据类型一起工作的类或方法)模块内高 内聚,模块间低耦合。 泛型的使用:当我们的类/方法不需要关注调用者传递的实体是什么(公共基类工具 类),这个时候就可以使用泛型。 注意: 集合中的项允许是 object 型的值,因此可以存放任意类型的值,无法确保存入集合中 的值都是同一类型,而导致在处理时发生异常。 可空类型 对于引用类型的变量来说,如果未对其赋值,在默认情况下

qq_57767445的博客 2450

【生成C01/C10材料常数excel表】

计算橡胶件的邵尔硬度和C01/C10材料常数

weixin_46153372的博客 1934

PowerDesigner16.5下载和安装教程

一、软件下载 1.下载地址(不用复制,直接点击下载链接即可):链接:https://pan.baidu.com/s/1MSnWHWt3SO2Uz5dt9AANAg提取码:z9kk 二、软件安装 1.下载安装包(包含安装文件、汉化包、注册文件) 2.下载后文件内容如下 3....

cicong2374的博客 10万+

C# 泛型详解(泛型类,方法,接口,委托,约束,反射 )

泛型(Generic),是将不确定的类型预先定义下来的一种C#高级语法,我们在使用一个类,接口或者方法前,不知道用户将来传什么类型,或者我们写的类,接口或方法相同的代码可以服务不同的类型,就可以定义为泛型。这会大大简化我们的代码结构,同时让后期维护变得容易。...............

熊思雨的博客 2万+

Scissor算法实战:利用bulkRNA表型数据精准定位单细胞亚群

本文详细介绍了Scissor算法的实战应用,该算法通过整合bulkRNA-seq的丰富表型信息与单细胞数据,精准识别与特定临床结局(如不良预后)相关的单细胞亚群。文章深入解析了算法三步走的核心思想,并提供了适配Seurat v5的完整代码解决方案,帮助研究者从海量数据中提炼具有生物学和临床价值的洞见。

ww90123的博客 254

C#中的泛型 (From dotNet SDK 2.0 Beta1)

引用:http://www.newasp.net/tech/net/13588.html string[] arr = new string[] { "string1", "string2", "string3" };InsertMethod<string>(arr); private void InsertMethod<T>(T...

weixin_30457065的博客 69

华为MetaERP国资委语境下的 DRP(全域数字化资源管理平台/Digitalized Resource-management Platform)与传统 ERP(以 SAP、Oracle 为代表)的

【摘要】国资委推动的DRP(全域数字化资源管理平台)正在重构央国企数智化转型路径,其本质是通过"ERP管交易+DRP管管控"的双层架构实现升维替代。与传统ERP相比,DRP具有三大突破:采用国产分布式数据库实现全级次穿透监管、构建数据中台打破信息孤岛、以实时分析替代事后报表。当前政策明确2026-2030年分阶段建设目标,这将重塑产业格局——倒逼传统ERP厂商转型,为云原生/AI/信创企业创造新机遇。需注意DRP并非简单替代ERP,而是通过治理模式变革实现"交易系统做薄、管控中

金牌架构师 286

Linux LT8912b调试记录(MIPI转LVDS/HDMI)

本文记录了在RK3588平台上使用LT8912B芯片实现MIPI转LVDS/HDMI的调试过程。主要包含设备树配置、驱动代码实现及常见问题解决。调试重点包括:1)正确配置1280x800分辨率的LVDS屏幕参数;2)确保I2C总线正常通信;3)解决花屏、图像抖动、黑屏等问题,涉及lane-rate计算、同步信号极性调整及MIPI模式选择。文中提供了详细的寄存器配置代码和故障排查方法,特别强调了I2C通信验证和电压匹配的重要性。通过参数优化和多种模式测试,最终实现了稳定的视频输出转换。

qq_45147279的博客 1088

Visual Studio 2005 Express Download

转自博客堂:留着自己方便用: DotNET Framework Version 2.0 Redistributable Package Beta 1 (x86) - 24Mhttp://go.microsoft.com/fwlink/?linkid=30093&clcid=0x409 DotNET Framework 2.0 SDK Beta 1 x86 - 229Mhttp://ww...

weixin_34248849的博客 167

DeepSeek+即梦:AI视频创作从0到1全突破

本文为 DeepSeek 与即梦生成视频全攻略,从前期注册登录、熟悉工具界面及硬件网络准备入手,助读者开启 AI 视频创作。详细阐述利用 DeepSeek 构思脚本、即梦实现画面生成与静态图转动态视频的方法,包括提示词编写、参数调整等技巧。同时介绍后期合成音画、添加字幕特效及多平台适配要点,结合实战案例解析全流程。还分享进阶提示词优化、版权注意等避坑指南,为创作者提供从 0 到 1 的 AI 视频创作完整解决方案。

邓邓子的博客 8111

老赵点滴:十五年.NET技术博客的沉淀逻辑与人格化写作

技术博客的本质是知识沉淀与认知传递,而非信息搬运或流量收割。在.NET生态快速迭代的背景下,深度理解C#语言哲学、ASP.NET演化史和运行时底层机制,远比追逐语法糖更具长期价值。真正的技术影响力源于对问题本质的持续追问——比如async/await状态机如何影响高并发稳定性,或HttpClient生命周期为何引发连接耗尽。这类穿透API层、直抵JIT编译与内存模型的分析,构建了可迁移的工程判断力。它服务于从初学者到金融级架构师的全谱系开发者,核心不在‘教你怎么写’,而在‘帮你建立技术决策的参照系’。老赵点

weixin_40192258的博客 480

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Visual Studio 接入多种不同 AI 大模型进行开发的全面详细配置指南

本文详细介绍Visual Studio 接入AI大模型的完整配置方案,涵盖系统要求、GitHub Copilot集成、多模型接入等核心内容。重点包括:1)最低配置要求(Win10/8GB内存);2)Copilot安装与模型选择(支持GPT-4/Claude/Gemini等);3)自定义模型配置方法(需API Key);4)MCP服务器设置与Agent模式优化。提供从环境准备到实战应用的全流程指导,帮助开发者高效利用AI辅助编程,提升开发效率与代码质量。

独坐一隅,凝神遐想,是种幸福! —— 独隅 5464

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

C#高级--泛型详解

C#高级–泛型详解 一、泛型是什么? 泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。 我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的。 使用泛型是一种增强程序功能的技术,具体表现在以下几个方面: 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。 您可以创建泛型集合类。

李宥的博客 1万+

C# 泛型概念及用法详解

泛型(generic)是C# 2.0推出的新语法,它是专门为处理多段代码在不同的数据类型上执行相同的指令的情况而设计的。比如说编程时,碰到功能非常相似的模块,只是它们所处理的数据类型不同,然而我们却需要写不同的方法来实现它,很明显,这加大了我们的工作量,也很乏味。有没有什么办法能够解决这个问题呢?它就是泛型了,可以让多个类型共享一组代码。通过压栈例子可以更清楚的了解泛型{int[] arr;//将int类型的值压栈//将float类型的值压栈。

u011555996的博客 1万+

图解C#高级教程(三):泛型

在使用泛型类创建变量时,可以使用关键字var,让编译器根据右边的类型自动推断变量的类型。set;set;// 让编译器推断类型103.14Hellotrue需要注意的是,为具体类分配的内存是存储在堆上的。

聚焦C++/Python编程语言、大模型应用开发 4706

C# 泛型详解

1、泛型概述 2、定义泛型 3、泛型的特性 4、泛型委托 5、泛型的优点

这个人好有趣....hh 4660

C# 泛型(Generic)

泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。 您可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。下面这个简单的实例将有助于您理解这个概念: 实例 using System; using System.Collections.Generic; namespace GenericApplication { pu

weixin_43055166的博客 562

C#之泛型

一、概述一、概述泛型是用于处理算法、数据结构的一种编程方法。泛型的目标是采用广泛适用和可交互性的形式来表示算法和数据结构,以使它们能够直接用于软件构造。泛型类、结构、接口、委托和方法可以根据它们存储和操作的数据的类型来进行参数化。泛型能在编译时提供强大的类型检查,减少数据类型之间的显示转换、装箱操作和运行时的类型检查。泛型类和泛型方法同时具备可重用性、类型安全和效率高等特性,这是非泛型类和非泛型方法无法具备的。泛型通常用在集合和在集合上运行的方法中。泛型主要是提高了代码的重要性。

互联网底层民工的博客 2945

C# 泛型

为什么要有泛型: 我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。 你可能会想到用object,来解决这个问题。但是,缺陷的: 1.会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。 2.在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。 介绍泛型: C.

LiKe11807的博客 9528

C#中泛型

泛型(generic)是C# 2.0推出的新语法,并不是语法糖,它是专门为处理多段代码在不同的数据类型上执行相同的指令的情况而设计的。泛型定义时,是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型。比如:方法和泛型混到一起,叫泛型方法,类和泛型混在一起叫泛型类,接口和泛型混到一起,叫泛型接口,等等。只是方法签名,没有方法体,默认是公开的,不能添加修改符。在C#中应用比较广的泛型:泛型方法,泛型类,泛型接口。泛型是一个复合类型,把多个类型混合一起作用,

2401_84642440的博客 2150

C# 泛型(Generics)详解

类型安全:编译时检查类型,避免运行时类型转换错误。性能优化:减少装箱 / 拆箱操作,尤其对值类型更高效。代码复用:一套逻辑适配多种类型,减少重复代码。灵活性:结合约束、协变、逆变等特性,可适应复杂场景。

m0_57417006的博客 1914

深入解析 C# 中的泛型:概念、用法与最佳实践

泛型使得你能够编写能够操作多种数据类型的代码,而不需要在代码中硬编码具体的数据类型。通过类型参数(例如T),你可以在运行时决定具体的类型,从而提高代码的重用性和灵活性。泛型类泛型方法泛型接口泛型委托泛型集合泛型是 C# 中的一项强大特性,能够让你编写类型安全、灵活、可重用且高效的代码。通过泛型,开发者可以避免在类型转换时出现的错误,并且能够编写高度通用的类、方法、接口等。掌握泛型的使用,能够帮助开发者在处理复杂数据结构和编写高效代码时更得心应手。

net core ,前端 ,python 全栈开发 2005
上一篇: 【转载】c#中Dictionary、ArrayList、Hashtable和数组的区别是什么?
下一篇: 【转载】C# 文件操作 全收录 追加、拷贝、删除、移动文件、创建目录、递归删除文件夹及文件....
kevindend
博客等级 码龄19年 17粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值