C#之泛型详解

目录
  • 一.泛型的特性
    • 1.性能
    • 2.类型安全
    • 3.二进制代码的重用
    • 4.代码的扩展
    • 5.命名约定
  • 二.使用类型
    • 1.先创建一个非泛型的简化链表类。
    • 2.下面编写一个泛型版本
  • 三.泛型类的功能
    • 1.默认值
    • 2.约束
    • 3.继承
    • 4.静态成员
  • 四.泛型接口
  • 五.泛型结构
  • 六.泛型方法
    • 1.带约束的泛型方法
    • 2.带委托的泛型方法
    • 3.泛型方法规范

泛型不仅是C#的一部分,而且与程序集中的IL代码紧密地集成。有了泛型,就可以创建独立于被包含类型的类和方法。这样就可以不必给不同的类型编写功能相同的许多方法或类,只创建一个方法或类即可。
另一个减少代码的选项是使用Object类,因为Object类是不安全的。
泛型类使用泛型类型,并可以根据需要用特定的类型替换泛型类型。这就保证了类型安全性:如果某个类型不支持泛型类,编译器就会出现错误。
泛型不仅限于类,接口和方法也可以使用泛型。
泛型并不是一个全新的结构,在C++中模版就和泛型相似。泛型不仅是C#的一种结构,而且是CLR定义的。所以,即使泛型类在C#中定义,也可以VB中用一个特定的类型实例化该泛型。

一.泛型的特性

1.性能

对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作。虽然操作很容易,但性能损失比较大:

  var list = new ArrayList();
  list.Add(4);//装箱操作,ArrayList的Add方法参数是Object
  foreach(int i in list)
  {
    Console.WriteLine(i);//拆箱
  }

System.Collections和System.Collections.Generic是非泛型和泛型集合类。System.Collections.Generic中的List<T>类不使用对象,而是在使用时定义类型。

  var list = new List<int>();
  list.Add(4);
  foreach(int i in list)
  {
    Console.WriteLine(i);
  }

2.类型安全

使用ArrayList类在集合中可以添加任意类型。

  var list = new ArrayList();
  list.Add(4);
  list.Add(“66”);

在遍历时就会出现异常,因为不是所有元素都可以强制装换为INT类型:

  foreach(int i in list)
  {
    Console.WriteLine(i);
  }

错误应尽早发现。使用泛型类List<T>,泛型类型T定义了允许使用特定的类型。有了List<int>的定义,就只能把整数类型添加到集合中。

  var list = new ArrayList();
  list.Add(4);
  list.Add(66);

3.二进制代码的重用

泛型允许更好的重用二进制代码。泛型类可以定义一次,并且可以用许多不同的类型实例化。例如List<T>类可以用int,string,自定义类来实例化。
泛型类可以在一种语言中定义,在任何其它.NET语言中使用。

4.代码的扩展

因为泛型类的定义放在程序集中,所以用特定类型实例化泛型类不会在IL代码中复制这些类。但是,在JIT编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类。引用类型共享一个本地类的所有相同的实现代码。这是因为引用类型在实例化的泛型类中只需要4个字节的内存地址(32为系统),就可以引用一个引用类型。值类型包含在实例化的泛型类的内存中,同时因为每个值类型对内存的要求不同,所以要为每个值类型实例化一个新类。

5.命名约定

在程序中使用泛型,在区分泛型类型和非泛型类型时使用命名约定就会有一定帮助。
泛型类型的命名规则:

  • *泛型类型的名称用字母T作为前缀。
  • *如果没有特殊的要求,泛型类型允许用任意类代替,且如果只能使用一个泛型类型,就可以用T作为泛型类型的名称。
    Public class Person{}
  • *如果泛型类型有特定的要求(比如它必须实现某个接口或派生自基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称:
    public class SortedList{}

二.使用类型

1.先创建一个非泛型的简化链表类。

public class LinkedListNode
    {
        public LinkedListNode(object value)
        {
            this.Value = value;
        }

        public object Value { get; private set; }//value对外密封,只可以读取,设置值通过构造函数
        public LinkedListNode Next { get; internal set; }//记录当前节点的下一节点
        public LinkedListNode Prev { get; internal set; }//记录当前节点的上一节点
    }

public class LinkedList : IEnumerable
    {
        public LinkedListNode First { get; private set; }//记录链表的第一个节点,外部只可以读取,设置值通过内部方法
        public LinkedListNode Last { get; private set; }

        public LinkedListNode AddLast(object node)
        {
            //实例化一个LinkedListNode
            var newNode = new LinkedListNode(node);

            //判断链表是否含有元素,如果没有就把newNode设置为链表的第一个元素
            if (First == null)
            {
                First = newNode;
                Last = newNode;
            }
            else
            {
                LinkedListNode previous = Last;//获取链表的最后一个元素
                Last.Next = newNode;//将newNode赋予最后一个元素的下一个元素
                Last = newNode;//重新设置链表的最后一个元素
                Last.Prev = previous;//newNode成为链表的最后一个元素,将原来的最后一个元素设置为newNode的上一个元素

                //注意上述顺序
            }
            return newNode;
        }

        //实现IEnumerable接口的GetEnumerator()方法,这样外部代码就可以用foreach语句遍历链表
        public IEnumerator GetEnumerator()
        {
            LinkedListNode current = First;
            while (current != null)
            {
                yield return current.Value;//yield创建一个枚举器的状态机
                current = current.Next;
            }
        }
    }

客户端代码:

  var list1 = new LinkedList();
  list1.AddLast(2);
  list1.AddLast(4);
  list1.AddLast("22");

  foreach (object i in list1)
  {
    Console.WriteLine(i.ToString());
  }

需要进行装箱拆箱操作。

2.下面编写一个泛型版本

public class LinkedListNode<T>
    {
        public LinkedListNode(T value)
        {
            this.Value = value;
        }

        public T Value { get; private set; }//value对外密封,只可以读取,设置值通过构造函数
        public LinkedListNode<T> Next { get; internal set; }//记录当前节点的下一节点
        public LinkedListNode<T> Prev { get; internal set; }//记录当前节点的上一节点
    }

//该类实现IEnumerable<T>接口,IEnumerable<T>继承自IEnumerable接口,所以需要实现GetEnumerator()方法和IEnumerator<T> GetEnumerator()方法
public class LinkedList<T> : IEnumerable<T>
    {
        public LinkedListNode<T> First { get; private set; }//记录链表的第一个节点,外部只可以读取,设置值通过内部方法
        public LinkedListNode<T> Last { get; private set; }

        public LinkedListNode<T> AddLast(T node)
        {
            //实例化一个LinkedListNode
            var newNode = new LinkedListNode<T>(node);

            //判断链表是否含有元素,如果没有就把newNode设置为链表的第一个元素
            if (First == null)
            {
                First = newNode;
                Last = newNode;
            }
            else
            {
                LinkedListNode<T> previous = Last;//获取链表的最后一个元素
                Last.Next = newNode;//将newNode赋予最后一个元素的下一个元素
                Last = newNode;//重新设置链表的最后一个元素
                Last.Prev = previous;//newNode成为链表的最后一个元素,将原来的最后一个元素设置为newNode的上一个元素

                //注意上述顺序
            }
            return newNode;
        }

        //IEnumerable<T>接口继承IEnumerable
        //实现IEnumerable<T>接口的GetEnumerator()方法,这样外部代码就可以用foreach语句遍历链表
        public IEnumerator<T> GetEnumerator()
        {
            LinkedListNode<T> current = First;
            while (current != null)
            {
                yield return current.Value;//yield创建一个枚举器的状态机
                current = current.Next;
            }
        }

        //实现IEnumerable接口的GetEnumerator()方法
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

客户端代码:

  var list2 = new LinkedList<int>();
  list2.AddLast(2);
  list2.AddLast(4);
  list2.AddLast(44);

  foreach (int i in list2)
  {
    Console.WriteLine(i);
  }

三.泛型类的功能

在创建泛型类时,有时需要一些C#关键字。
通过一个例子来介绍这些关键字:

public class DocumentManager<TDocument>
      where TDocument : IDocument
  {
    private readonly Queue<TDocument> documentQueue = new Queue<TDocument>();

    public void AddDocument(TDocument doc)
    {
      lock (this)
      {
        documentQueue.Enqueue(doc);
      }
    }

    public bool IsDocumentAvailable
    {
      get { return documentQueue.Count > 0; }
    }

    public void DisplayAllDocuments()
    {
      foreach (TDocument doc in documentQueue)
      {
        Console.WriteLine(doc.Title);
      }
    }

    public TDocument GetDocument()
    {
      TDocument doc = default(TDocument);
      lock (this)
      {
        doc = documentQueue.Dequeue();
      }
      return doc;
    }

  }

1.默认值

在GetDocument()方法中,需要把TDocument指定为null。但是,不能把null赋予泛型类型。因为泛型类型也可以实例化为值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。通过default,将null赋予引用类型,将0赋予值类型。

2.约束

如果泛型类需要调用泛型类型中的方法,就必须添加约束。

public interface IDocument
  {
    string Title { get; set; }
    string Content { get; set; }
  }

  public class Document : IDocument
  {
    public Document()
    {
    }

    public Document(string title, string content)
    {
      this.Title = title;
      this.Content = content;
    }

    public string Title { get; set; }
    public string Content { get; set; }
  }

在方法中遍历显示时,需要使用Document类的属性Title,这就需要约束泛型类型TDocument中包含这个属性,这里使用泛型类型TDocument必须实现IDocument接口。where子句指定了该约束。

泛型支持的约束类型:

约束 说明
where T:struct 对于结构约束,类型T必须是值类型
where T:class 类约束指定类型T必须是引用类型
where T:IFoo 指定类型T必须实现接口IFoo
where T:Foo 指定类型T必须派生自Foo
where T:new() 构造函数约束,指定类型T必须有一个默认构造函数
where T1:T2 这个约束指定类型T1派生自泛型类型T2。该约束也称为裸类型约束。

使用泛型类型也可以合并多个约束。where T:IFoo,new(),MyClass

3.继承

泛型类型可以实现泛型接口,也可以派生自一个类。泛型类可以派生自泛型基类。要求是必须重复接口的泛型类型,或者必须指定基类的类型。
派生类可以是泛型类或非泛型类。

4.静态成员

泛型类的静态成员和非泛型类的静态成员有区别,泛型类的静态成员只能在类的一个实例中共享:

  public class StaticDemo<T>
  {
    public static int x;
  }

客户端代码:

  StaticDemo<string>.x = 4;
  StaticDemo<int>.x = 5;
  //存在两组静态字段。

四.泛型接口

使用泛型可以定义接口,在接口中定义的方法可以带泛型参数。

五.泛型结构

泛型结构类似于泛型类,只是没有继承特性。

六.泛型方法

在泛型方法中,泛型类型用方法来定义。泛型方法可以在非泛型类中定义。

  void Swap<T>(ref T x,ref T y)
  {
    T temp;
    temp = x;
    x=y;
    y=temp;
  }

  int i=4;
  int j = 5;
  Swap<int>(ref i,ref j);

因为C#编译器会通过调用Swap()方法来获取参数的类型,所以不需要把泛型类型赋予方法调用:

  int i=4;
  int j = 5;
  Swap(ref i,ref j);

1.带约束的泛型方法

  public static class Algorithm
  {

    public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source)
    where TAccount : IAccount
    {
      decimal sum = 0;

      foreach (TAccount a in source)
      {
        sum += a.Balance;
      }
      return sum;
    }

  }

  public interface IAccount
  {
    decimal Balance { get; }
    string Name { get; }
  }

  public class Account : IAccount
  {
    public string Name { get; private set; }
    public decimal Balance { get; private set; }

    public Account(string name, Decimal balance)
    {
      this.Name = name;
      this.Balance = balance;
    }
  }

客户端代码:

  var accounts = new List<Account>()
  {
    new Account("Christian", 1500),
    new Account("Stephanie", 2200),
    new Account("Angela", 1800),
    new Account("Matthias", 2400)
  };

  decimal amount = Algorithm.Accumulate<Account>(accounts);

2.带委托的泛型方法

  public static class Algorithm
  {
    public static T2 Accumulate<T1, T2>(IEnumerable<T1> source, Func<T1, T2, T2> action)
    {
      T2 sum = default(T2);
      foreach (T1 item in source)
      {
        sum = action(item, sum);
      }
      return sum;
    }

  }

  decimal amount = Algorithm.Accumulate<Account, decimal>(accounts, (item, sum) => sum += item.Balance);

3.泛型方法规范

泛型方法可以重载,为特定的类型定义规范。这样适用于带参数的方法。

  public class MethodOverloads
  {
    public void Foo<T>(T obj)
    {
      Console.WriteLine("Foo<T>(T obj), obj type: {0}", obj.GetType().Name);
    }

    public void Foo(int x)
    {
      Console.WriteLine("Foo(int x)");
    }

    public void Bar<T>(T obj)
    {
      Foo(obj);
    }
  }

如果传递了一个int,就选择带int参数的方法。对于其它参数类型,编译器会选择泛型方法。

  var test = new MethodOverloads();
  test.Foo(33);
  test.Foo("abc");

输出:

Foo(int x)
Foo<T>(T obj), obj type: String

所调用的方法是在编译期间定义的,而不是运行期间。

test.Bar(44);

输出:

Foo<T>(T obj), obj type: Int32

Bar()方法选择了泛型Foo()方法,而不是int参数重载的方法。因为编译器是在编译期间选择Bar()方法调用的Foo()方法。由于Bar()方法定义了一个泛型参数,而且泛型Foo()方法匹配这个类型,所以调用Foo()方法。

到此这篇关于C#之泛型的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详细介绍C# 泛型

    在C#开发中,必不可少的要用到泛型.泛型是.NET2.0版本就有的,它广泛应用于C#框架中容器的使用中.下面我们来详细介绍一下. 一.泛型的主要优势 1.性能更高. 2.类型更安全. 3.代码更多的重用和扩展性. 二.泛型的基本使用 泛型的一个主要优点是性能,我们来看下面的例子: static void Main(string[] args) { //不是泛型的集合类 ArrayList list = new ArrayList(); //添加一个值类型 装箱操作 list.Add(12); /

  • C#泛型接口的协变和逆变

    1.什么是协变.逆变? 假设:TSub是TParent的子类.协变:如果一个泛型接口IFoo<T>,IFoo<TSub>可以转换为IFoo<TParent>的话,我们称这个过程为协变,IFoo支持对参数T的协变.逆变:如果一个泛型接口IFoo<T>,IFoo<TParent>可以转换为IFoo<TSub>的话,我们称这个过程为逆变,IFoo支持对参数T的逆变. 2.为什么要有协变.逆变? 通常只有具备继承关系的对象才可以发生隐式类型转

  • C#泛型详解

    这篇文章主要讲解C#中的泛型,泛型在C#中有很重要的地位,尤其是在搭建项目框架的时候. 一.什么是泛型 泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能. 我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样.但我们没有办法,只能分别写多个方法来处理不同的数据类型.这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的. 二.为什么使用泛型 先来看下面一个例子: using System

  • 详解c# 泛型类的功能

    在泛型类中,由于不知道泛型参数T是什么类型,可能是引用类型,也可能是值类型,因此不能将null等赋予泛型类型.如何对泛型对象赋初值.如何保证泛型的正确性等,以使用泛型文档管理器为例: 文档管理器用于从队列中读写文档.首先创建一个泛型管理器AddDocument()方法添加一个文档到队列中,IsDocumentAvailabe只读属性指示队列中是否还有文档. public class DocumentManager<T> { private readonly Queue<T> doc

  • C#泛型的使用及示例详解

    目录 一.什么是泛型 二.为什么使用泛型 三.泛型类型参数 四.泛型类 五.泛型约束 六.泛型的协变和逆变 七.泛型缓存 这篇文章主要讲解C#中的泛型,泛型在C#中有很重要的地位,尤其是在搭建项目框架的时候. 一.什么是泛型 泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能. 我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样.但我们没有办法,只能分别写多个方法来处理不同的数据类型.这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种

  • C#泛型运作原理的深入理解

    前言# 我们都知道泛型在C#的重要性,泛型是OOP语言中三大特征的多态的最重要的体现,几乎泛型撑起了整个.NET框架,在讲泛型之前,我们可以抛出一个问题,我们现在需要一个可扩容的数组类,且满足所有类型,不管是值类型还是引用类型,那么在没有用泛型方法实现,如何实现? 一.泛型之前的故事# 我们肯定会想到用object来作为类型参数,因为在C#中,所有类型都是基于Object类型的.因此Object是所有类型的最基类,那么我们的可扩容数组类如下: public class ArrayExpandab

  • C#泛型类型知识讲解

    概述 泛型类和泛型方法兼具可重用性.类型安全性和效率,这是非泛型类和非泛型方法无法实现的 泛型通常与集合以及作用于集合的方法一起使用 泛型所属命名空间:System.Collections.Generic 可以创建自定义泛型接口.泛型类.泛型方法.泛型事件和泛型委托,以提供自己的通用解决方案,设计类型安全的高效模式 泛型允许编写一个可以与任何数据类型一起工作的类或方法 示例 using System; using System.Collections.Generic; namespace Gen

  • C#泛型详解及关键字作用

    这篇文章主要来讲讲c#中的泛型,因为泛型在c#中有很重要的位置,对于写出高可读性,高性能的代码有着关键的作用. 一.什么是泛型? 泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个非常重要的新功能. 我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样.但我们没有办法,只能分别写多个方法来处理不同的数据类型.这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的,可以看出,微软还是很贴心的.

  • 实例讲解C# 泛型(Generic)

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

  • C#语法之泛型的多种应用

    本篇文章主要介绍泛型的应用. 泛型是.NET  work 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性.类型安全性和效率. 泛型的定义 下面定义了一个普通类和一个泛型类,我们可以明确看到泛型类和普通类最大的区别就是多了一个<T>. 所以,这个<T>就标记了,这个类是泛型类.其中这个T,也可以写成A,B,C,D或其他字符. public class Generic { public String Name; } public class Generic<T>

随机推荐