简介:《C#实用教程》是一本深入讲解C#编程语言及其应用的教程,适合各水平层次的学习者。教程内容涵盖了从设置开发环境到实现数据库交互的全周期,详细介绍了C#的基础知识和高级特性。本教程着重于实践应用,结合理论与实例练习,帮助读者理解并应用C#的核心概念,包括面向对象编程、命名空间、异常处理、泛型、事件驱动编程、异步编程、以及ADO.NET和Entity Framework等数据库交互技术。
1. C#开发环境搭建与项目创建
1.1 安装.NET开发环境
首先,下载并安装最新版本的.NET SDK(软件开发工具包),它包括了.NET运行时和编译器。安装完成后,通过命令行运行 dotnet --version 验证安装是否成功。
1.2 创建第一个C#项目
打开命令行工具,进入你希望创建项目的目录,输入以下命令创建一个控制台应用程序:
dotnet new console -n HelloWorld
其中, console 指定了项目模板类型, -n HelloWorld 是项目名称。
1.3 项目结构与运行
新建项目会在当前目录下生成一个项目文件夹,打开该项目文件夹,你会看到如下结构: - HelloWorld.csproj:项目文件,定义了项目引用的库等配置信息。 - Program.cs:包含主入口点Main方法的文件。
通过命令行导航到项目目录,输入以下命令来运行程序:
dotnet run
若一切设置正确,你应该能看到控制台输出 "Hello, World!"。
至此,你的C#开发环境已成功搭建,并创建了第一个简单的控制台应用程序。接下来,你可以开始深入学习C#编程语言的其他概念了。
2. C#面向对象编程基础
2.1 类与对象
2.1.1 类的定义和对象的创建
在C#中,类(Class)是面向对象编程的基础,它是一种用户定义的数据类型,可以包含状态(字段)和操作(方法)。类的定义使用 class 关键字,其后跟着类名和一对大括号 {} ,在大括号内定义类的成员。
对象(Object)则是类的实例化,每个对象都包含类定义的所有字段和方法。在C#中,对象通过 new 关键字进行创建,如 var obj = new ClassName(); 。
下面是一个简单的类定义和对象创建的示例代码:
public class Person
{
// 字段
private string name;
private int age;
// 构造方法
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
// 方法
public void Greet()
{
Console.WriteLine($"Hello, my name is {name} and I am {age} years old.");
}
}
// 创建对象
Person person = new Person("Alice", 30);
person.Greet();
在上述代码中,定义了一个 Person 类,它有两个私有字段 name 和 age ,一个构造方法以及一个公共方法 Greet 。然后我们通过 new Person("Alice", 30); 创建了一个 Person 对象,并调用了该对象的 Greet 方法来输出问候语。
2.1.2 封装、继承和多态性
封装、继承和多态是面向对象编程的三大基本特性。
- 封装 :隐藏对象的内部细节,并保护对象的内部状态。它通过定义公共接口来暴露对象功能,同时隐藏内部数据。
- 继承 :允许创建一个新类(子类),继承现有类(父类)的属性和方法。
- 多态 :指子类具有父类的方法,但可以有自己的实现。
C#中的类支持这些特性,实现方式通常如下:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal speaks.");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Dog barks.");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Cat meows.");
}
}
在这个例子中, Animal 类定义了一个 Speak 方法。 Dog 和 Cat 类继承自 Animal 类,并通过 override 关键字重写了 Speak 方法。在运行时,根据对象的实际类型调用相应的方法,体现了多态性。
2.2 面向对象的核心概念
2.2.1 抽象和封装的原理
抽象 是面向对象编程中减少复杂性的核心技术之一。它涉及创建一组行为和属性的简化的表示,以便我们可以专注于需要的特定方面。C#通过类和接口支持抽象。
封装 是指将数据(或状态)和操作数据的代码捆绑在一起,并对外隐藏对象的实现细节。这是通过控制对类成员的访问级别来实现的。在C#中,我们使用访问修饰符如 public 、 protected 、 internal 、 private 等来控制成员的可见性。
例如:
public class BankAccount
{
// 私有字段,外部不可直接访问
private decimal balance;
// 公共属性,封装了对私有字段的访问
public decimal Balance
{
get { return balance; }
private set { balance = value; }
}
// 公共方法,提供对余额的操作
public void Deposit(decimal amount)
{
if (amount > 0)
{
balance += amount;
}
}
// 为了演示封装和抽象,提供一个抽象方法
public abstract void Report();
}
在上述代码中, balance 字段被封装在 BankAccount 类的内部,外部代码无法直接访问。相反,我们提供了一个 Balance 属性,它通过 get 和 set 访问器提供对 balance 字段的受控访问。 Deposit 方法是一个公共方法,提供存款功能,而 Report 方法则是抽象的,需要在子类中具体实现,从而体现了抽象的概念。
2.2.2 继承的优势与限制
继承允许我们创建新类基于现有类,提供代码重用,以及创建一个类层次结构。继承的主要优势在于它能够定义子类,子类除了拥有基类的功能外,还可以增加新功能或重写基类的某些方法。
然而,继承也有其限制:
- 破坏封装 :子类可能依赖于基类的内部实现,这可能会导致在基类更改时破坏子类。
- 增加复杂性 :随着继承层次的增加,代码的维护难度也会增加。
- 紧密耦合 :子类与基类之间紧密耦合,限制了代码的灵活性。
继承的限制可以通过组合(Composition)来弥补,在组合中,类之间通过使用彼此的实例来建立关系,而不是通过继承。
2.2.3 多态的实现方式
多态性允许使用一个单一的接口来表示不同的底层形态。在C#中,多态性主要通过接口和继承来实现:
- 方法重载(Overloading) :在同一个类中定义具有不同参数列表的同名方法,编译器根据不同的参数列表选择调用相应的方法。
- 方法重写(Overriding) :子类提供父类中某方法的具体实现。
下面展示了方法重写:
public class Vehicle
{
public virtual void Start()
{
Console.WriteLine("Vehicle starts.");
}
}
public class Car : Vehicle
{
public override void Start()
{
Console.WriteLine("Car starts with engine sound.");
}
}
在这个例子中, Car 类重写了 Vehicle 类的 Start 方法。当创建 Car 类的实例并调用 Start 方法时,调用的是 Car 类中的版本,体现了运行时多态性。
3. 变量、数据类型、运算符和流程控制
3.1 基本数据类型和变量
3.1.1 C#内置的数据类型
在 C# 中,数据类型是定义变量可存储的数据种类的属性。C# 是一种静态类型语言,意味着变量在声明时必须指定其类型,并且在编译时类型会被检查。C# 内置了多种数据类型,包括数值类型、字符类型、布尔类型和引用类型。
数值类型可以进一步被分为整型和浮点型。整型包括 sbyte 、 byte 、 short 、 ushort 、 int 、 uint 、 long 和 ulong 。其中, int 类型是使用最频繁的整数类型,适用于大多数整数操作。
浮点类型包括 float 和 double ,分别对应单精度和双精度浮点数。 double 类型由于其较高的精度和性能,通常是浮点数运算的首选。 decimal 类型用于需要高精度的小数运算,如财务和货币计算。
字符类型使用 char 表示,用于存储单个 Unicode 字符。布尔类型使用 bool 表示,它有两个可能的值: true 和 false 。
引用类型包括 string 、 object 和其他用户定义的类类型。 string 类型用于存储文本,其对象是不可变的。
int number = 42; // 整型
double pi = 3.14159; // 浮点型
bool isCompleted = true; // 布尔型
char grade = 'A'; // 字符型
string message = "Hello, World!"; // 字符串
3.1.2 变量的作用域和生命周期
变量的作用域指的是程序中可以访问该变量的区域。在 C# 中,变量的作用域由其声明的位置决定。例如,局部变量在声明它们的方法或语句块内具有作用域。
局部变量的生命周期从声明时开始,一直持续到程序离开该变量的作用域时结束。这意味着,一旦退出变量的作用域,该变量占用的内存将被释放。静态变量的生命周期与应用程序的生命周期一致。
变量的声明必须遵循“类型 变量名”的格式,例如:
int count;
string text;
变量的作用域和生命周期在程序设计中扮演着重要角色,错误的管理可能导致资源泄露或其他运行时错误。
3.2 运算符和表达式
3.2.1 算术运算符、关系运算符和逻辑运算符
C# 提供了丰富的运算符来执行各种操作。算术运算符用于执行数学计算,包括加(+)、减(-)、乘(*)、除(/)和取模(%)。关系运算符用于比较两个值,其结果是布尔值,包括等于(==)、不等于(!=)、大于(>)、小于(<)、大于等于(>=)和小于等于(<=)。
逻辑运算符用于执行布尔逻辑操作,包括逻辑与(&&)、逻辑或(||)和逻辑非(!)。运算符具有优先级和结合性规则,它们决定了在表达式中运算符的执行顺序。
int a = 10, b = 20, c;
c = a + b; // c 现在是 30
bool result = (a == b); // 结果为 false
result = (a > 0) && (b > 0); // 结果为 true
3.2.2 字符串和数组的操作
字符串是字符的集合,是 System.String 类型的对象。C# 提供了丰富的字符串操作方法,例如 Length 属性返回字符串长度, Substring 方法用于截取字符串的一部分。
数组是相同类型的多个数据集合,它们有固定大小,并且在内存中连续存储。数组的声明和实例化可以如下完成:
string myString = "Hello";
int stringLength = myString.Length; // 获取字符串长度
int[] myArray = new int[3]; // 声明并初始化一个大小为 3 的数组
myArray[0] = 1;
myArray[1] = 2;
myArray[2] = 3;
// 通过字符串操作
string concatenatedString = myString + " World"; // "Hello World"
// 数组操作
myArray[0] = 5; // 修改数组元素
int sum = myArray[0] + myArray[1] + myArray[2]; // 计算数组元素和
3.3 控制结构
3.3.1 条件语句(if、switch)
在 C# 中,根据条件的真假来决定程序执行哪个分支,通常使用 if 和 switch 语句实现。
if 语句是最基本的条件语句,根据布尔表达式的结果为真或假来执行不同的代码块:
int number = 5;
if (number > 0)
{
Console.WriteLine("The number is positive.");
}
else if (number < 0)
{
Console.WriteLine("The number is negative.");
}
else
{
Console.WriteLine("The number is zero.");
}
switch 语句用于基于不同的情况执行不同的操作。每个 case 关键字后面跟着一个值和一个冒号,表示一种情况:
string status = "Warning";
switch (status)
{
case "Normal":
Console.WriteLine("Everything is fine.");
break;
case "Warning":
Console.WriteLine("There is a problem.");
break;
case "Error":
Console.WriteLine("There is an error.");
break;
default:
Console.WriteLine("Unknown status.");
break;
}
3.3.2 循环语句(for、foreach、while、do-while)
循环语句用于重复执行一段代码直到满足特定条件。C# 提供了四种循环结构: for 、 foreach 、 while 和 do-while 。
for 循环通常用于已知循环次数时的迭代:
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
foreach 循环用于遍历数组或集合中的每个元素:
string[] names = { "Alice", "Bob", "Charlie" };
foreach (string name in names)
{
Console.WriteLine(name);
}
while 循环在每次迭代开始前检查条件,条件为真时继续执行:
int i = 0;
while (i < 10)
{
Console.WriteLine(i);
i++;
}
do-while 循环至少执行一次代码块,之后检查条件:
int j = 0;
do
{
Console.WriteLine(j);
j++;
} while (j < 10);
在使用循环时,应确保循环条件最终会变为假,以避免无限循环的发生。
4. 类和对象的声明、实例化及继承
在 C# 中,类是面向对象编程的核心。类提供了一种定义对象蓝图的方式,这些对象可以具有属性、方法、事件和其他类成员。对象是类的实例,每个对象都包含类定义的所有成员数据(称为字段)和代码(称为方法)的实现。继承是面向对象编程的另一个关键特性,它允许一个类继承另一个类的成员,从而实现代码的重用和扩展。在本章中,我们将深入探讨类和对象的高级特性,对象的创建与生命周期,以及如何利用继承来扩展类的功能。
4.1 类的高级特性
4.1.1 静态成员、常量和只读字段
在类定义中,我们不仅能够定义实例成员(需要创建类的实例才能访问的成员),还可以定义静态成员(无需创建类的实例即可访问的成员)。静态成员在所有实例之间共享。这包括静态字段、静态属性和静态方法。
静态成员
静态成员与类关联而不是与类的特定实例关联。这允许我们访问成员而不创建类的实例。例如,可以使用类名直接访问静态成员:
public class Calculator
{
public static int Add(int x, int y)
{
return x + y;
}
}
// 使用静态方法
int result = Calculator.Add(5, 3);
在这个例子中, Add 方法是静态的,意味着我们不需要创建 Calculator 类的实例来调用它。
常量
常量是声明时需要初始化的静态字段,它们的值在编译时就已确定,并且在程序运行时不可更改。
public class Constants
{
public const double Pi = 3.14159;
}
// 使用常量
double circumference = Constants.Pi * diameter;
只读字段
只读字段可以在声明时或在所有构造函数中初始化,一旦对象被创建,它们的值就不能改变。
public class Point
{
public readonly double X;
public readonly double Y;
public Point(double x, double y)
{
X = x;
Y = y;
}
}
4.1.2 属性、索引器和方法重载
属性、索引器和方法重载是类的高级特性,使我们能够以更加灵活和面向对象的方式封装数据。
属性
属性是类的成员,提供了字段的封装,可以实现数据的获取和设置。属性具有 getter 和 setter 方法,可以控制对数据的访问。
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
索引器
索引器类似于属性,但是它们通过索引访问类的实例。索引器使得类的实例可以像数组一样进行索引。
private List<string> items = new List<string>();
public string this[int index]
{
get { return items[index]; }
set { items[index] = value; }
}
方法重载
方法重载允许在同一个类中定义多个同名方法,只要这些方法的参数类型或参数数量不同即可。
public int Add(int x, int y) { /* ... */ }
public double Add(double x, double y) { /* ... */ }
4.2 对象的创建和生命周期
4.2.1 构造函数和析构函数
构造函数是类的一个特殊方法,当创建类的新实例时会被调用。它们通常用于初始化新创建的对象。析构函数是当对象被销毁时由垃圾回收器调用的特殊方法,用于清理对象占用的资源。
构造函数
构造函数可以有参数,并且可以重载,以提供不同的初始化方式。
public class Example
{
public Example() { /* Default constructor */ }
public Example(string arg) { /* Parameterized constructor */ }
}
析构函数
析构函数是 ~Example() 形式的一个方法,C# 中析构函数的使用应谨慎,因为它们引入不确定的清理时间。
~Example()
{
// Cleanup code
}
4.2.2 对象的初始化和对象销毁
对象的初始化在构造函数中完成。对象销毁是在对象的引用超出作用域后,由垃圾回收器管理的。C# 程序员通常不需要担心对象销毁的具体时刻,除非使用了非托管资源。
初始化
对象创建后,构造函数会初始化对象的状态。可以使用对象初始化器来初始化对象:
var obj = new Example { Property1 = "Value1", Property2 = 123 };
对象销毁
在 .NET 中,对象被垃圾回收器回收时,析构函数(如果存在)会被调用。在非托管资源需要释放的情况下,应实现 IDisposable 接口,并在 Dispose 方法中释放资源。
public class ResourceHolder : IDisposable
{
public void Dispose()
{
// Free unmanaged resources.
}
}
4.3 高级继承技巧
继承允许我们创建一个新类基于一个已存在的类。继承的类被称为派生类或子类,被继承的类被称为基类或父类。
4.3.1 抽象类和接口的使用
抽象类和接口是实现继承的关键工具。
抽象类
抽象类是不能实例化的类,它们通常包含抽象成员,这些成员需要在派生类中被实现。
public abstract class Vehicle
{
public abstract void Start();
}
接口
接口定义了必须由实现它的类实现的合约。接口可以包含方法、属性、事件和索引器的成员定义。
public interface IDriveable
{
void Drive();
}
4.3.2 密封类和类成员的重写
密封类可以防止其他类继承,而类成员的重写允许我们在派生类中修改继承自基类的行为。
密封类
使用 sealed 关键字可以创建一个不允许其他类继承的类。
public sealed class ElectricCar : Vehicle
{
// ElectricCar specific implementation
}
类成员的重写
使用 override 关键字来重写基类的方法,属性或索引器。
public class Animal
{
public virtual void Speak() { /* ... */ }
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}
在本章中,我们学习了类和对象的声明、实例化以及继承的高级用法。我们探索了静态成员、常量和只读字段以及属性、索引器和方法重载的定义和使用。我们还讨论了对象的生命周期,包括构造函数和析构函数的使用,以及如何正确初始化对象并处理对象销毁。此外,我们深入探讨了继承技巧,包括抽象类和接口的实现,以及如何通过密封类和类成员的重写来控制继承行为。所有这些概念和技巧是面向对象编程中不可或缺的一部分,对于构建健壮和可扩展的软件系统至关重要。
5. 命名空间的使用和组织代码
命名空间是C#语言中非常重要的概念,用于组织代码、避免命名冲突以及提供逻辑分组。本章节将详细介绍命名空间的定义、作用以及如何通过命名空间组织代码。
5.1 命名空间的定义和作用
5.1.1 命名空间的作用域
在C#中,命名空间是一种用于对代码进行逻辑分组的方式。它创建了一个层次化的命名环境,使得我们可以将类型(类、接口、结构等)组织在不同的逻辑单元中。命名空间可以嵌套,形成一个树状的命名空间层次结构。在一个命名空间中定义的类型,可以通过完全限定名访问,即包含命名空间名称在内的类型名称。
// 使用完全限定名访问命名空间中的类
System.Console.WriteLine("Hello, World!");
5.1.2 using指令和别名声明
为了避免在每次使用类型时都必须指定完整的命名空间,C#提供了 using 指令。 using 指令可以有两种形式:引入命名空间和别名声明。引入命名空间允许在没有完全限定名的情况下使用该命名空间下的类型;而别名声明则允许为类型或命名空间指定一个简短的名称。
using System; // 引入命名空间
using Math = System.Math; // 别名声明
Console.WriteLine(Math.Sqrt(4)); // 使用别名调用方法
5.2 代码组织技巧
5.2.1 分文件组织类和命名空间
在较大的项目中,将代码分散到不同的文件中是一种常见的做法,以提高可维护性和可读性。每个源文件通常包含一个或多个类或结构,并且属于一个或多个命名空间。一个命名空间可以跨越多个文件,这有助于将代码分拆成更小的模块。
5.2.2 使用区域和代码隐藏
在C#项目中,开发者可以使用“区域”来组织代码。区域是一个虚拟的代码分组,可以用来将相关的代码片段集中在一起。尽管区域不是语言的关键特性,但它可以增强代码的可读性。代码隐藏通常是指将私有成员、方法或类隐藏起来,不对外公开,从而简化了类的公共接口。
// 区域示例
#region MyNamespace
public class MyClass
{
// 类成员
}
#endregion
代码块示例与分析
// 代码块示例 - 区域和命名空间的使用
#region MyNamespace
namespace MyNamespace
{
public class MyClass
{
private int privateVar; // 私有成员变量
}
}
#endregion
在上述代码块中,首先使用 #region 和 #endregion 定义了一个区域。区域内的所有代码都被包含在 MyNamespace 命名空间中,这样做的好处是可以在视觉上将相关的代码片段组织在一起,而不影响实际的代码结构。 MyClass 类属于 MyNamespace 命名空间,这样就可以通过命名空间的限定来访问 MyClass 。
表格示例
| 项目 | 描述 | |--------|----------------------------------| | using | 引入命名空间,省略命名空间前缀 | | #region | 代码折叠区,用于组织代码块 | | namespace | 命名空间的声明,用于逻辑分组代码 | | class | 类的声明,包含在命名空间内 | | private | 访问修饰符,指定成员只在本类内可见 |
通过表格,我们快速地展示了几个与命名空间和代码组织相关的关键词项及其描述,使得相关概念和用法一目了然。
通过本章的介绍,读者应能理解命名空间在C#中的作用和如何有效地组织项目代码。这将为编写结构良好、易于维护的C#应用程序打下坚实的基础。下一章节我们将探讨异常处理和try-catch块的使用,这是在开发过程中保持程序健壮性的关键话题。
6. 异常处理和try-catch块
异常处理是软件开发中的一个重要组成部分,它确保了程序在遇到错误或异常情况时能够优雅地处理这些问题,而不是直接崩溃。在C#中,异常处理是通过try-catch块来实现的,这使得开发者能够捕获和响应运行时出现的错误。本章节将深入探讨异常处理机制,以及如何有效地使用try-catch块来处理异常。
6.1 异常处理机制
在C#中,异常处理机制允许程序在遇到错误时跳转到错误处理代码块,而不是停止运行。异常是程序运行时发生的一种错误情况,当无法正常完成操作时,异常就会被抛出。
6.1.1 异常类和异常层次结构
C#中的异常是通过异常类来表示的,所有异常类都继承自System.Exception基类。异常层次结构非常清晰,从基类System.Exception衍生出各种特定异常类。这使得开发人员可以根据异常类型编写特定的处理代码,例如 ArgumentException、NullReferenceException 等。
6.1.2 throw语句和异常抛出
在C#中,throw语句用于抛出一个异常。开发者可以在代码中使用throw关键字来显式地抛出异常对象,这通常用于验证输入参数的正确性或处理不满足预期条件的情况。
public static void ThrowExample(int value)
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "Value must be non-negative.");
}
// 其他代码...
}
在上面的例子中,如果传入的值小于零,则会抛出一个 ArgumentOutOfRangeException。
6.2 异常捕获和处理
异常捕获是通过try-catch块来实现的。在一个try块中编写可能抛出异常的代码,然后使用一个或多个catch块来捕获和处理异常。
6.2.1 try-catch-finally块的使用
try块后面可以跟随一个或多个catch块,以捕获不同类型的异常。当一个异常被抛出时,C#运行时会检查对应的catch块,直到找到一个匹配的类型。finally块无论是否抛出异常都会执行,常用于执行清理资源的操作。
try
{
// 可能抛出异常的代码
}
catch(NullReferenceException ex)
{
// 处理特定类型的异常
Console.WriteLine("Caught a NullReferenceException: " + ex.Message);
}
catch(Exception ex)
{
// 处理其他所有异常
Console.WriteLine("Caught an exception: " + ex.Message);
}
finally
{
// 无论是否发生异常,都会执行的代码
}
6.2.2 自定义异常和异常过滤器
C#允许开发者创建自定义异常类,通过继承自System.Exception类来实现。自定义异常通常包含更详细的信息,有助于在调试时定位问题。另外,C# 6.0 引入了异常过滤器,允许在catch块之前添加条件语句来决定是否捕获异常。
catch (MyCustomException ex) when (ex.ErrorCode == 42)
{
// 只有当异常的ErrorCode为42时,才会执行这个catch块
}
异常处理在日常编程中扮演着关键角色。通过使用try-catch块,开发者可以确保应用程序在遇到错误时能够继续运行,提高系统的健壮性和用户的体验。同时,合理地使用自定义异常和异常过滤器可以提升代码的可读性和可维护性。
异常处理不仅仅是一个编程技巧,它更是一种编程思维。随着本章内容的深入,你将学会如何在C#程序中运用这种思维,确保你的应用能够更加稳定和可靠。
7. 泛型和类型安全的数据结构
7.1 泛型基础
7.1.1 泛型类和方法的概念
泛型是C#编程语言提供的一项强大的特性,它允许在定义类、结构、接口和方法时不具体指定它们要操作的数据类型。泛型类和方法在编译时提供类型安全,但延迟绑定具体的类型直到实例化或调用时。这意味着代码可以在不知道操作的具体类型的情况下复用。
例如,泛型集合类 List<T> 可以存储任何类型的元素,而无需将元素转换为特定类型(如 object )。这不仅提高了代码的可读性,还提供了编译时的类型检查,减少了运行时的类型转换错误。
7.1.2 泛型的约束和优势
泛型的约束可以指定泛型类型必须满足的条件。常见的约束包括:
-
where T : class- T 必须是一个引用类型。 -
where T : struct- T 必须是一个值类型。 -
where T : new()- T 必须有一个无参的构造函数。 -
where T : IComparable- T 必须实现IComparable接口。
泛型的优势包括:
- 类型安全 :泛型集合在编译时进行类型检查,减少运行时类型错误。
- 性能提升 :避免使用
object类型带来的装箱和拆箱操作。 - 代码复用 :同一段泛型代码可以适用于多种数据类型。
// 一个简单的泛型类示例
public class Stack<T> where T : class
{
private List<T> elements = new List<T>();
public void Push(T element) { elements.Add(element); }
public T Pop() { return elements.Count == 0 ? null : elements[elements.Count - 1]; }
}
7.2 集合和数据结构
7.2.1 泛型集合类的使用
.NET框架中的泛型集合类,如 List<T> , Dictionary<TKey, TValue> , 和 Queue<T> 等,提供了丰富的操作接口,且相较于非泛型的 ArrayList 或 Hashtable 等具有更高的类型安全性和效率。
例如,使用泛型 Dictionary 来存储键值对:
var map = new Dictionary<string, int>();
map.Add("one", 1);
map.Add("two", 2);
map.TryGetValue("two", out int value); // value = 2
7.2.2 自定义泛型数据结构
开发者可以创建自己的泛型类、接口、委托和方法,以满足特定的应用需求。例如,创建一个泛型链表类:
public class LinkedListNode<T>
{
public T Value { get; set; }
public LinkedListNode<T> Next { get; set; }
}
public class LinkedList<T> : IEnumerable<T>
{
private LinkedListNode<T> head;
public LinkedListNode<T> Head => head;
// 添加和遍历方法的实现...
}
泛型提供了更灵活的代码复用机制,使得开发者能够编写出更加健壮、可维护的代码。通过使用泛型,C#程序员能够在保持代码清晰和类型安全的同时,提高代码的复用性和性能。
简介:《C#实用教程》是一本深入讲解C#编程语言及其应用的教程,适合各水平层次的学习者。教程内容涵盖了从设置开发环境到实现数据库交互的全周期,详细介绍了C#的基础知识和高级特性。本教程着重于实践应用,结合理论与实例练习,帮助读者理解并应用C#的核心概念,包括面向对象编程、命名空间、异常处理、泛型、事件驱动编程、异步编程、以及ADO.NET和Entity Framework等数据库交互技术。
956

被折叠的 条评论
为什么被折叠?



