[译]聊聊C#中的泛型的使用(新手勿入)

写在前面

今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发现了一些问题,因此也进行了纠正,当然,原文的地址我放在最下面,如果你的英文水平比较好的话,可以直接直接阅读全文。同时最近建了一个.NET Core实战项目交流群637326624,有兴趣的朋友可以来相互交流。目前.NET Core实战项目之CMS的教程也已经更新了6篇了,目前两到三天更新一篇。

作者:依乐祝
原文地址:https://www.cnblogs.com/yilezhu/p/10029782.html

介绍

C#和.NET中的泛型程序具有强类型集合的许多优点,并为代码提供更高质量和性能提升。泛型是C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概念引入.NET Framework。类型参数使得设计某些类和方法成为可能,例如,通过使用泛型类型参数T,可以大大简化类型之间的强制转换或装箱操作的过程(装箱、拆箱问题)。说白了,泛型就是通过参数化类型来实现在同一份代码上操作多种数据类型,利用“参数化类型”将类型抽象化,从而实现灵活的复用。每个集合的详细规范可以在System.Collection.Generic名称空间下找到。

装箱和拆箱

.Net定义了两种主要的数据类型来表示变量,也就是传说中的值类型和引用类型。这是需要装箱和拆箱的地方。装箱是一种通过将变量存储到System.Object中来显式地将值类型转换为引用类型的机制。当您装入值时,CLR会将新对象分配到堆中,并将值类型的值复制到该实例中。例如,您创建了一个int类型的变量:

int a = 20;
object b = a; //装箱

相反的操作是拆箱,它是将引用类型转换回值类型的过程。此过程验证接收数据类型是否与装箱类型一致;

int c = (int)b; // 拆箱

C#编译器可以看到从int到object的赋值,反之亦然。当编译该程序并通过IL解析器检查IL生成的代码时,您会注意到当b被赋值为a时,程序通过在IL中自动插入一个box指令来响应,当c被赋值为b时如下;

代码加载常量20并将其存储在本地插槽中;它将值20加载到堆栈中并将其装箱。最后,它将被装箱的20返回到堆栈上,并将其拆箱为int类型

这个过程.NET CLR执行了一系列操作,例如,首先在托管堆中分配一个对象,然后在装箱中将值转换为内存位置,并在拆箱期间将值存储在堆上并且必须转回到堆栈。因此,从性能的角度来看,装箱和拆箱过程在泛型中具有非常重要的意义,因为这个过程如果不使用泛型的话会耗费更多地资源。

泛型类

可以通过在类名后面加上符号来定义泛型类。这里没有强制必须将“T”字放在泛型的定义中。您可以在TestClass <>类声明中使用任何单词。

public class TestClass<T> { }  

System.Collection.Generic命名空间下还定义了许多实现了这些关键字接口的类型。下表列出了此命名空间的核心类类型。

泛型类 描述
Collection 泛型集合的基类,可以比较两个泛型对象是否相等
Dictionary<TKey, TValue> 键值对的泛型集合
List 可动态调整列表项的大小
Queue 先进先出(FIFO)列表的泛型实现
Stack 后进先出(LIFO)列表的泛型实现

简单的泛型类示例

以下示例显示了一个简单的泛型类型的操作。TestClass 定义一个长度为5的泛型类型数组。Add()方法负责将任何类型的对象添加到集合中,而Indexer属性是循环语句迭代的实现。最后在主类中,我们使用整形类型来实例化TestClass 类,并使用Add()方法将一些整数类型数据添加到集合中。

using System;
using System.Collections.Generic;  

namespace GenericApp
{
    public class TestClass<T>
    {
        // 定义一个长度为5的泛型类型的数组
        T[] obj = new T[5];
        int count = 0;  

        // 向反省类型添加数据
        public void  Add(T item)
        {
            //checking length
            if (count + 1 < 6)
            {
                obj[count] = item;  

            }
            count++;
        }
        //foreach语句迭代索引
        public T this[int index]
        {
            get { return obj[index]; }
            set { obj[index] = value; }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //用整形来实例化泛型类
            TestClass<int> intObj = new TestClass<int>();  

            //向集合中添加int数据
            intObj.Add(1);
            intObj.Add(2);
            intObj.Add(3);     //没有装箱
            intObj.Add(4);
            intObj.Add(5);  

            //遍历显示数据
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(intObj[i]);   //没有拆箱
            }
            Console.ReadKey();
        }
    }
}  

在构建并运行该程序之后,程序的输出如下所示;

泛型的主要特性

泛型类型的一些重要特征使它们相比传统的非泛型类型具有如下的显著特征:

  • 类型安全
  • 性能
  • 二进制代码复用

类型安全

泛型最重要的特征之一是类型安全性。对于非泛型ArrayList类,如果使用对象类型,则可以向集合中添加任何类型,这些类型有时会导致严重的问题。下面的示例显示向ArrayList类型的集合添加一个整数、字符串和对象;

ArrayList obj = new ArrayList();
obj.Add(50);
obj.Add("Dog");
obj.Add(new TestClass());  

现在,如果使用整数对象来使用foreach语句进行遍历的话,当编译器接受到代码,但是因为集合中的所有元素都不是整数,所以会导致运行时异常;

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

编程的经验法则是应该尽早检测到错误。对于泛型类Test,泛型类型T定义允许哪些类型。通过使用Test的定义,只能向集合添加整型类型的数据。这时候当Add()方法具有以下无效参数的时候编译器将不编译代码;

Test<int> obj = new Test<int>();
obj.Add(50);
obj.Add("Dog");            //编译错误
obj.Add(new TestClass());  //编译错误

性能

在下面的示例中,ArrayList类存储对象,并且定义了Add()方法来存储一些整型参数。因此,整数类型被装箱。当使用foreach语句读取ArrayList中的值时,将发生拆箱。

ArrayList  obj = new ArrayList();
obj.Add(50);    //装箱- 值类型转换成引用类型
int x= (int)obj[0]; //拆箱
foreach(int i in obj)
{
   Console.WriteLine(i);   // 拆箱
} 

注意:泛型比其他集合(如ArrayList)更快。

代替使用对象类型,TestClass类的泛型类型被定义为int,因此在从编译器动态生成的类中将使用int类型。所以将不会发生装箱和拆箱,如下所示;

TestClass<int> obj = new TestClass<int>();
obj.Add(50);    //没有装箱
int x= obj[0]; // 没有拆箱
foreach(int i in obj)
{
   Console.WriteLine(i);   //没有拆箱
}  

二进制代码复用

泛型类型提供了一种源代码保护机制。泛型类可以定义一次,并且可以使用许多不同类型来进行实例化。泛型可以在一种CLR支持的语言中定义,并可以被另一种.NET语言使用。以下TestClass 使用int和string类型进行实例化:

TestClass<int> obj = new TestClass<int>();
obj.Add(50);  

TestClass<string> obj1 = new TestClass<string>();
Obj1.Add("hello");  

通用方法

虽然大多数开发人员通常会使用基类库中的现有泛型类型,但也有可能会构建自己的泛型成员和自定义的泛型类型。

本示例的目的是构建一个交换方法,该方法可以使用单个类型参数对任何可能的数据类型(基于值或基于引用)进行操作。由于交换算法的性质,传入的参数将作为使用ref关键字修饰的引用类型来进行发送。

using System;
using System.Collections.Generic;  

namespace GenericApp
{
    class Program
    {
        //泛型方法
        static void Swap<T>(ref T a, ref T b)
        {
            T temp;
            temp = a;
            a = b;
            b = temp;
        }
        static void Main(string[] args)
        {
            //交换两个整形数据
            int a = 40, b = 60;
            Console.WriteLine("Before swap: {0}, {1}", a, b);  

            Swap<int>(ref a, ref b);  

            Console.WriteLine("After swap: {0}, {1}", a, b);  

            Console.ReadLine();
        }
    }
}  

编译此泛型方法实现的程序后,输出如下所示;

字典

字典也被称为映射或散列表。它表示允许您基于关键字来访问元素的数据结构。字典的一个重要特征是更快的查找; 您可以添加或删除选项而不会产生性能开销。

.Net提供了几个字典类,例如Dictionary <TKey,TValue>。类型参数TKey和TValue分别表示关键字的类型和它可以存储的值。

简单的字典示例

以下示例演示使用泛型的简单字典集合。在此程序中,将创建一个Dictionary类型对象,该对象接受int作为键,字符串作为值。然后我们将一些字符串值添加到字典集合中,最后显示字典集合元素。

using System;
using System.Collections.Generic;  

namespace GenericApp
{
    public class Program
    {
        static void Main(string[] args)
        {
            //定义一个字典集合
            Dictionary<int,string> dObj = new Dictionary<int,string>(5);  

            //向字典中添加类型

            dObj.Add(1,1,"Tom");
            dObj.Add(2,"John");
            dObj.Add(3, "Maria");
            dObj.Add(4, "Max");
            dObj.Add(5, "Ram");  

            //输出数据
            for (int i = 1; i <= dObj.Count;i++)
            {
                Console.WriteLine(dObj[i]);
            }
            Console.ReadKey();
        }
    }
}  

以下示例通过定义附加类emp来描述一些更复杂的问题,其中我们覆盖ToString()方法以显示特定员工的名称和薪水。稍后在Main()方法中,创建一个新的Dictionary <TKey,TValue)的实例,其中键的类型为string,值为emp类型。构造函数分配2个元素的容量。emp对象和作为键的字符串值被添加到字典集合中。最后,使用foreach语句迭代集合元素并显示在屏幕上。

using System;
using System.Text;
using System.Collections.Generic;  

namespace GenericApp
{
    public class emp
    {
        private string name;
        private int salary;  

        public emp(string name,int salary)
        {
            this.name = name;
            this.salary = salary;
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder(200);
            sb.AppendFormat("{0},{1}",name,salary);  

            return sb.ToString();
        }  

    }
    public class Program
    {
        static void Main(string[] args)
        {
            //定义一个字典集合
            Dictionary<string, emp> dObj = new Dictionary<string, emp>(2);  

            //向字典中添加元素
            emp tom = new emp("tom", 2000);
            dObj.Add("tom",tom);   // 键,值
            emp john = new emp("john", 4000);
            dObj.Add("john",john);  

            //print data
            foreach(Object str in dObj.Values)
            {
               Console.WriteLine(str);
            }  

            Console.ReadKey();
        }
    }
}  

队列

队列是一种特殊类型的容器,可确保以FIFO(先进先出)方式访问元素。队列集合最适合实现消息传递的组件。我们可以使用以下语法定义Queue集合对象:

Queue qObj = new Queue();

Queue集合的属性,方法和其他规则定义都位于Sysyem.Collection命名空间下。下表定义了关键成员;

System.Collection.Queue成员 定义
Enqueue() 将对象添加到队列的末尾。
Dequeue() 从队列的开头删除对象。
Peek() 返回队列开头的对象而不删除它。

下面演示了一个基本的队列类型的集合,将一些字符串类型值添加到集合中,最后使用while语句来显示整个集合中的数据 。

using System;
using System.Collections;  

namespace GenericApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义一个队列
            Queue qObj = new Queue();  

            //向队列中添加字符串数据
            qObj.Enqueue("Tom");
            qObj.Enqueue("Harry");
            qObj.Enqueue("Maria");
            qObj.Enqueue("john");  

            //显示队列中的数据
            while(qObj.Count !=0 )
            {
                Console.WriteLine(qObj.Dequeue());
            }  

            Console.ReadKey();
        }
    }
}  

堆栈

Stack集合是LIFO的抽象(后进先出)。我们可以使用以下语法定义Stack集合对象:

Stack qObj = new Stack();

下表说明了堆栈的关键成员;

System.Collection.Stack成员 定义
Contains() 如果在集合中找到特定元素,则返回true。
Clear() 删除集合的所有元素。
Peek() 预览堆栈中的最新元素。
Push() 它将元素推入堆栈。
Pop() 返回并删除堆栈的顶部元素。

以下演示了堆栈集合。首先,将数组类型对象引用到堆栈集合中。然后使用Pop()方法从堆栈中删除集合中元素的值并显示在屏幕上。

using System;
using System.Collections;  

namespace GenericApp
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] iArray = new int[] {1,2,3,4,5,6,7,8,9,10 };  

            //定义一个堆栈
            Stack sObj = new Stack(iArray);  

            Console.WriteLine("Total items="+sObj.Count);  

            //显示集合数据
            for (int i = 0; i < sObj.Count;++i )
            {
                Console.WriteLine(sObj.Pop());
            }  

            Console.ReadKey();
        }
    }
}  

在使用泛型实现的另一个示例中,使用Push()方法将5个项添加到堆栈中。然后使用循环迭代输出堆栈中的数据。堆栈的枚举器不会删除数据; 它只是以LIFO方式返回每个项目,如下所示:

using System;
using System.Collections;  

namespace GenericApp
{
    class Program
    {
        static void Main(string[] args)
        {  

             //定义一个堆栈
            Stack sObj = new Stack();

            //向集合添加数据
            for (int i = 0; i < 5; ++i)
            {
                sObj.Push(i+1);
            }
            Console.WriteLine("Total items=" + sObj.Count);
            //打印数据
            foreach (int i in sObj)
            {
                Console.WriteLine(i);
            }
            Console.ReadKey();
        }
    }
}  

总结

今天忙里偷闲,在浏览外文的时候看到一篇讲泛型的文章,因此就加上自己的理解进行了相关翻译,也加深了自己对泛型的理解!如果英文比较好的话可以直接访问https://www.c-sharpcorner.com/UploadFile/84c85b/using-generics-with-C-Sharp/ 自行查看!当然,我在翻译的过程中也发现了文中的一些错误,所以进行了更正!同时最近建了一个.NET Core实战项目交流群637326624,有兴趣的朋友可以来相互交流。目前.NET Core实战项目之CMS的教程也已经更新了6篇了,目前两到三天更新一篇。最后感谢大家的阅读。

原文地址:https://www.cnblogs.com/yilezhu/p/10029782.html

时间: 2024-11-01 15:57:25

[译]聊聊C#中的泛型的使用(新手勿入)的相关文章

构造方法中使用泛型

------------siwuxie095 构造方法中使用泛型: 构造方法可以为类中的属性初始化,如果类中的属性通过泛型指定,而又需要 通过构造方法设置属性内容的时候,构造方法的定义与之前并无不同,不需要 像声明类那样指定泛型 package com.siwuxie095.generic; class Context<T>{ private T value; public Context(T value) { this.value=value; } public T getValue() {

泛型及java中的泛型

当作笔记整理的~~~ 首先,引出堆对象这个概念. 什么是堆对象,就是程序在运行过程中可以随时建立或者删除的对象,可以用new运算符(或malloc函数)或者delete运算符(或free函数).泛型可以看作是一类堆对象. 泛型是程序设计语言的一种特性.允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明. 各种程序设计语言和其编译器.运行环境对泛型的支持均不一样.将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型. 泛型的定义主要有两种:1.在程序编

Java 中的泛型详解-Java编程思想

Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限. 2.简单泛型 促成泛型出现最引人注目的一个原因就是为了创造容器类. 首先看一个只能持有单个对象的类,这个类可以明确指定其持有的对象的类型 class Holder1 { private Circle a; public Holder1(Circle a) { this.a = a; } Circle get() { return a; } } 上面的类的可重用性不怎么样,无法持有其他类型的任何对象,下面通过持有Object

转载:C#中的泛型

泛型类和泛型方法兼复用性.类型安全和高效率于一身,是与之对应的非泛型的类和方法所不及.泛型广泛用于容器(collections)和对容器操作的方 法中..NET框架2.0的类库提供一个新的命名空间System.Collections.Generic,其中包含了一些新的基于泛型的容器类.要查 找新的泛型容器类(collection classes)的示例代码,请参见基础类库中的泛型.当然,你也可以创建自己的泛型类和方法,以提供你自己的泛化的方案和设计模式,这是类型安全且高效 的.下面的示例代码以一

java中的泛型(转)

什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 可以在集合框架(Collection framework)中看到泛型的动机.例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象. 因为 Map.get(

.net中的泛型

泛型把类或方法的类型的确定推迟到实例化该类或方法的时候 ,也就是说刚开始声明是不指定类型,等到要使用(实例化)时再指定类型 泛型可以用于  类.方法.委托.事件等 下面先写一个简单的泛型 public class GenericClass<T> { void SomeMethod( T t ) { //do something } } 其使用方法如下: 实例化一个类 ? 1 GenericClass<int> gci=new GenericClass<int>(); 方

Java中的泛型 (上) - 基本概念和原理

本节我们主要来介绍泛型的基本概念和原理 后续章节我们会介绍各种容器类,容器类可以说是日常程序开发中天天用到的,没有容器类,难以想象能开发什么真正有用的程序.而容器类是基于泛型的,不理解泛型,我们就难以深刻理解容器类.那,泛型到底是什么呢? 什么是泛型? 一个简单泛型类 我们通过一个简单的例子来说明泛型类的基本概念.实现原理和好处. 基本概念 我们直接来看代码: public class Pair<T> { T first; T second; public Pair(T first, T se

编写高质量代码改善C#程序的157个建议——建议43:让接口中的泛型参数支持协变

建议43:让接口中的泛型参数支持协变 除了上一建议中提到的使用泛型参数兼容接口不可变性外,还有一种办法是为接口中的泛型声明加上out关键字来支持协变,如下所示: interface ISalary<out T> //使用out关键字 { void Pay(); } static void Main(string[] args) { ISalary<Programmer> s = new BaseSalaryCounter<Programmer>(); ISalary&l

ActionScript3.0(AS3)中的泛型数组Vector

Adobe官方并没有"泛型数组"的叫法,这是我自己对Vector的叫法(有点标题党),不过Vector在使用上确实跟c#中的泛型数组有些相似之处. 原作者:菩提树下的杨过出处:http://yjmyzz.cnblogs.com 我们知道:ActionScript3.0中的Array数组可以存放多种类型,甚至在同一个Array数组中,可以同时存入String,Object,Number...,但其实我们在实际开发中,通常一个数组中所保存的元素类型都是一致的,为了改进这种情况下的效率,AS