Introduction
One of the most anticipated (and perhaps the most daunting) features of Visual C# 2.0 is support for generics. This article will tell you what kind of problems generics are used to solve, how to use them to improve the quality of your code, and why you don't have to be afraid of generics.
What are generics?
Many people find generics difficult to understand. I believe this is because they are often fed a lot of theory and examples before they understand what problem generics are used to solve. The result is that you have a solution, but no problem that requires the solution.
This article will try to change this learning process. We will start with a simple question: What are generics used for? The answer is: without generics, it would be difficult to create type-safe collections.
C# is a type-safe language. Type safety allows the compiler to (reliably) catch potential errors instead of discovering them when the program is running (unreliably, which often happens when you sell the product) after!). Therefore, in C#, all variables have a defined type; when you assign an object to that variable, the compiler checks whether the assignment is correct and will give an error message if there is a problem.
In .Net version 1.1 (2003), this type safety is broken when you use collections. All collection classes provided by the .Net class library are used to store base types (Object), and everything in .Net is inherited from the Object base class, so all types can be put into a collection. middle. Therefore, it is equivalent to no type detection at all.
What's worse, every time you take an Object out of the collection, you have to cast it to the correct type. This conversion will have a performance impact and produce verbose code (if you If you forget to do the conversion, an exception will be thrown). Furthermore, if you add a value type to the collection (for example, an integer variable), the integer variable is implicitly boxed (again reducing performance), and when you remove it from the collection When it is used, explicit unboxing will be performed again (another performance reduction and type conversion).
For more information about boxing and unboxing, please visit Trap 4. Be wary of implicit boxing and unboxing.
Create a simple linear linked list
In order to feel these problems vividly, we will create a linear linked list as simple as possible. For those of you reading this who have never created a linear linked list. You can think of a linear linked list as a chain of boxes (called a node). Each box contains some data and a reference to the next box in the chain (except for the last box, of course). , this box's reference to the next box is set to NULL).
In order to create our simple linear linked list, we need the following three classes:
1. Node class, which contains data and a reference to the next Node.
2. The LinkedList class contains the first Node in the linked list and any additional information about the linked list.
3. Test program, used to test the LinkedList class.
To see how the linked list works, we add two types of Objects to the linked list: integer and Employee types. You can think of the Employee type as a class that contains all the information about an employee in the company. For demonstration purposes, the Employee class is very simple.
public class Employee{ private string name; public Employee (string name){ this.name = name; } public override string ToString(){ return this.name; } }
This class only contains a string type that represents the employee's name, a constructor that sets the employee's name, and a ToString() method that returns the employee's name.
The linked list itself is composed of many Nodes. These Notes, as mentioned above, must contain data (integer and Employee) and a reference to the next Node in the linked list.
public class Node{ Object data; Node next; public Node(Object data){ this.data = data; this.next = null; } public Object Data{ get { return this.data; } set { data = value; } } public Node Next{ get { return this.next; } set { this.next = value; } } }
Note that the constructor sets the private data members to the passed in object and sets the next field to null.
This class also includes a method, Append, which accepts a Node type parameter. We will add the passed Node to the last position in the list. The process is like this: first check the next field of the current Node to see if it is null. If it is, then the current Node is the last Node, and we point the next attribute of the current Node to the new node passed in. In this way, we insert the new Node into the end of the linked list.
If the next field of the current Node is not null, it means that the current node is not the last node in the linked list. Because the type of the next field is also node, we call the Append method of the next field (note: recursive call) and pass the Node parameter again, and this continues until the last Node is found.
public void Append(Node newNode){ if ( this.next == null ){ this.next = newNode; }else{ next.Append(newNode); } }
The ToString() method in the Node class is also overridden, used to output the value in data, and call the ToString() method of the next Node (Annotation: another recursive call).
public override string ToString(){ string output = data.ToString(); if ( next != null ){ output += ", " + next.ToString(); } return output; }
In this way, when you call the ToString() method of the first Node, the values of all Nodes on the linked list will be printed out.
The LinkedList class itself only contains a reference to one Node. This Node is called HeadNode, which is the first Node in the linked list and is initialized to null.
public class LinkedList{ Node headNode = null; }
LinkedList 类不需要构造函数(使用编译器创建的默认构造函数),但是我们需要创建一个公共方法,Add(),这个方法把 data存储到线性链表中。这个方法首先检查headNode是不是null,如果是,它将使用data创建结点,并将这个结点作为headNode,如果不是null,它将创建一个新的包含data的结点,并调用headNode的Append方法,如下面的代码所示:
public void Add(Object data){ if ( headNode == null ){ headNode = new Node(data); }else{ headNode.Append(new Node(data)); } }
为了提供一点集合的感觉,我们为线性链表创建一个索引器。
public object this[ int index ]{ get{ int ctr = 0; Node node = headNode; while ( node != null &&ctr <= index ){ if ( ctr == index ){ return node.Data; }else{ node = node.Next; } ctr++; } return null; } }
最后,ToString()方法再一次被覆盖,用以调用headNode的ToString()方法。
public override string ToString(){ if ( this.headNode != null ){ return this.headNode.ToString(); }else{ return string.Empty; } }
测试线性链表
我们可以添加一些整型值到链表中进行测试:
public void Run(){ LinkedList ll = new LinkedList(); for ( int i = 0; i < 10; i ++ ){ ll.Add(i); } Console.WriteLine(ll); Console.WriteLine(" Done.Adding employees..."); }
如果你对这段代码进行测试,它会如预计的那样工作:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9 Done. Adding employees...
然而,因为这是一个Object类型的集合,所以你同样可以将Employee类型添加到集合中。
ll.Add(new Employee("John")); ll.Add(new Employee("Paul")); ll.Add(new Employee("George")); ll.Add(new Employee("Ringo")); Console.WriteLine(ll); Console.WriteLine(" Done.");
输出的结果证实了,整型值和Employee类型都被存储在了同一个集合中。
0, 1, 2, 3, 4, 5, 6, 7, 8, 9 Done. Adding employees... 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, John, Paul, George, Ringo Done.
虽然看上去这样很方便,但是负面影响是,你失去了所有类型安全的特性。因为线性链表需要的是一个Object类型,每一个添加到集合中的整型值都被隐式装箱了,如同 IL 代码所示:
IL_000c: box [mscorlib]System.Int32 IL_0011: callvirt instance void ObjectLinkedList.LinkedList::Add(object)
同样,如果上面所说,当你从你的列表中取出项目的时候,这些整型必须被显式地拆箱(强制转换成整型),Employee类型必须被强制转换成Employee类型。
Console.WriteLine("The fourth integer is " +Convert.ToInt32(ll[3])); Employee d = (Employee) ll[11]; Console.WriteLine("The second Employee is " + d);
这些问题的解决方案是创建一个类型安全的集合。一个 Employee 线性链表将不能接受 Object 类型;它只接受 Employee类的实例(或者继承自Employee类的实例)。这样将会是类型安全的,并且不再需要类型转换。一个整型的 线性链表,这个链表将不再需要装箱和拆箱的操作(因为它只能接受整型值)。
作为示例,你将创建一个 EmployeeNode,该结点知道它的data的类型是Employee。
public class EmployeeNode { Employee employeedata; EmployeeNode employeeNext; }
Append 方法现在接受一个 EmployeeNode 类型的参数。你同样需要创建一个新的EmployeeLinkedList ,这个链表接受一个新的 EmployeeNode:
public class EmployeeLinkedList{ EmployeeNode headNode = null; }
EmployeeLinkedList.Add()方法不再接受一个 Object,而是接受一个Employee:
public void Add(Employee data){ if ( headNode == null ){ headNode = new EmployeeNode(data);} else{ headNode.Append(new EmployeeNode(data)); } }
类似的,索引器必须被修改成接受 EmployeeNode 类型,等等。这样确实解决了装箱、拆箱的问题,并且加入了类型安全的特性。你现在可以添加Employee(但不是整型)到你新的线性链表中了,并且当你从中取出Employee的时候,不再需要类型转换了。
EmployeeLinkedList employees = new EmployeeLinkedList(); employees.Add(new Employee("Stephen King")); employees.Add(new Employee("James Joyce")); employees.Add(new Employee("William Faulkner")); /* employees.Add(5); // try toadd an integer - won't compile */ Console.WriteLine(employees); Employee e = employees[1]; Console.WriteLine("The second Employee is " + e);
这样多好啊,当有一个整型试图隐式地转换到Employee类型时,代码甚至连编译器都不能通过!
但它不好的地方是:每次你需要创建一个类型安全的列表时,你都需要做很多的复制/粘贴 。一点也不够好,一点也没有代码重用。同时,如果你是这个类的作者,你甚至不能提前欲知这个链接列表所应该接受的类型是什么,所以,你不得不将添加类型安全这一机制的工作交给类的使用者---你的用户。
使用泛型来达到代码重用
解决方案,如同你所猜想的那样,就是使用泛型。通过泛型,你重新获得了链接列表的 代码通用(对于所有类型只用实现一次),而当你初始化链表的时候你告诉链表所能接受的类型。这个实现是非常简单的,让我们重新回到Node类:
public class Node{ Object data; ...
注意到 data 的类型是Object,(在EmployeeNode中,它是Employee)。我们将把它变成一个泛型(通常,由一个大写的T代表)。我们同样定义Node类,表示它可以被泛型化,以接受一个T类型。
public class Node <T>{ T data; ...
读作:T类型的Node。T代表了当Node被初始化时,Node所接受的类型。T可以是Object,也可能是整型或者是Employee。这个在Node被初始化的时候才能确定。
注意:使用T作为标识只是一种约定俗成,你可以使用其他的字母组合来代替,比如这样:
public class Node <UnknownType>{ UnknownType data; ...
通过使用T作为未知类型,next字段(下一个结点的引用)必须被声明为T类型的Node(意思是说接受一个T类型的泛型化Node)。
Node<T> next;
构造函数接受一个T类型的简单参数:
public Node(T data) { this.data = data; this.next = null; }
Node 类的其余部分是很简单的,所有你需要使用Object的地方,你现在都需要使用T。LinkedList类现在接受一个 T类型的Node,而不是一个简单的Node作为头结点。
public class LinkedList<T>{ Node<T> headNode = null;
再来一遍,转换是很直白的。任何地方你需要使用Object的,现在改做T,任何需要使用Node的地方,现在改做Node
LinkedList<int> ll = new LinkedList<int>();
另一个是Employee类型的:
LinkedList<Employee> employees = new LinkedList<Employee>();
剩下的代码与第一个版本没有区别,除了没有装箱、拆箱,而且也不可能将错误的类型保存到集合中。
LinkedList<int> ll = new LinkedList<int>(); for ( int i = 0; i < 10; i ++ ) { ll.Add(i); } Console.WriteLine(ll); Console.WriteLine(" Done."); LinkedList<Employee> employees = new LinkedList<Employee>(); employees.Add(new Employee("John")); employees.Add(new Employee("Paul")); employees.Add(new Employee("George")); employees.Add(new Employee("Ringo")); Console.WriteLine(employees); Console.WriteLine(" Done."); Console.WriteLine("The fourth integer is " + ll[3]); Employee d = employees[1]; Console.WriteLine("The second Employee is " + d);
泛型允许你不用复制/粘贴冗长的代码就实现类型安全的集合。而且,因为泛型是在运行时才被扩展成特殊类型。Just In Time编译器可以在不同的实例之间共享代码,最后,它显著地减少了你需要编写的代码。
以上就是C#理解泛型的内容,更多相关内容请关注PHP中文网(www.php.cn)!

如何使用C#编写时间序列预测算法时间序列预测是一种通过分析过去的数据来预测未来数据趋势的方法。它在很多领域,如金融、销售和天气预报中有广泛的应用。在本文中,我们将介绍如何使用C#编写时间序列预测算法,并附上具体的代码示例。数据准备在进行时间序列预测之前,首先需要准备好数据。一般来说,时间序列数据应该具有足够的长度,并且是按照时间顺序排列的。你可以从数据库或者

如何使用Redis和C#开发分布式事务功能引言分布式系统的开发中,事务处理是一项非常重要的功能。事务处理能够保证在分布式系统中的一系列操作要么全部成功,要么全部回滚。Redis是一种高性能的键值存储数据库,而C#是一种广泛应用于开发分布式系统的编程语言。本文将介绍如何使用Redis和C#来实现分布式事务功能,并提供具体代码示例。I.Redis事务Redis

如何实现C#中的人脸识别算法人脸识别算法是计算机视觉领域中的一个重要研究方向,它可以用于识别和验证人脸,广泛应用于安全监控、人脸支付、人脸解锁等领域。在本文中,我们将介绍如何使用C#来实现人脸识别算法,并提供具体的代码示例。实现人脸识别算法的第一步是获取图像数据。在C#中,我们可以使用EmguCV库(OpenCV的C#封装)来处理图像。首先,我们需要在项目

C#开发中如何处理跨域请求和安全性问题在现代的网络应用开发中,跨域请求和安全性问题是开发人员经常面临的挑战。为了提供更好的用户体验和功能,应用程序经常需要与其他域或服务器进行交互。然而,浏览器的同源策略导致了这些跨域请求被阻止,因此需要采取一些措施来处理跨域请求。同时,为了保证数据的安全性,开发人员还需要考虑一些安全性问题。本文将探讨C#开发中如何处理跨域请

Redis在C#开发中的应用:如何实现高效的缓存更新引言:在Web开发中,缓存是提高系统性能的常用手段之一。而Redis作为一款高性能的Key-Value存储系统,能够提供快速的缓存操作,为我们的应用带来了不少便利。本文将介绍如何在C#开发中使用Redis,实现高效的缓存更新。Redis的安装与配置在开始之前,我们需要先安装Redis并进行相应的配置。你可以

如何使用C#编写动态规划算法摘要:动态规划是求解最优化问题的一种常用算法,适用于多种场景。本文将介绍如何使用C#编写动态规划算法,并提供具体的代码示例。一、什么是动态规划算法动态规划(DynamicProgramming,简称DP)是一种用来求解具有重叠子问题和最优子结构性质的问题的算法思想。动态规划将问题分解成若干个子问题来求解,通过记录每个子问题的解,

如何在C#中实现遗传算法引言:遗传算法是一种模拟自然选择和基因遗传机制的优化算法,其主要思想是通过模拟生物进化的过程来搜索最优解。在计算机科学领域,遗传算法被广泛应用于优化问题的解决,例如机器学习、参数优化、组合优化等。本文将介绍如何在C#中实现遗传算法,并提供具体的代码示例。一、遗传算法的基本原理遗传算法通过使用编码表示解空间中的候选解,并利用选择、交叉和

如何实现C#中的图像压缩算法摘要:图像压缩是图像处理领域中的一个重要研究方向,本文将介绍在C#中实现图像压缩的算法,并给出相应的代码示例。引言:随着数字图像的广泛应用,图像压缩成为了图像处理中的重要环节。压缩能够减小存储空间和传输带宽,并能提高图像处理的效率。在C#语言中,我们可以通过使用各种图像压缩算法来实现对图像的压缩。本文将介绍两种常见的图像压缩算法:


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

SecLists
SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.

Zend Studio 13.0.1
Powerful PHP integrated development environment

Safe Exam Browser
Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

PhpStorm Mac version
The latest (2018.2.1) professional PHP integrated development tool