- C# 基础教程
- C# - 首页
- C# - 概述
- C# - 环境
- C# - 程序结构
- C# - 基本语法
- C# - 数据类型
- C# - 类型转换
- C# - 变量
- C# - 常量
- C# - 运算符
- C# - 决策
- C# - 循环
- C# - 封装
- C# - 方法
- C# - 可空类型
- C# - 数组
- C# - 字符串
- C# - 结构体
- C# - 枚举
- C# - 类
- C# - 继承
- C# - 多态
- C# - 运算符重载
- C# - 接口
- C# - 命名空间
- C# - 预处理器指令
- C# - 正则表达式
- C# - 异常处理
- C# - 文件 I/O
C# - 快速指南
C# - 概述
C# 是一种现代的、通用的、面向对象的编程语言,由微软开发,并经欧洲计算机制造商协会 (ECMA) 和国际标准化组织 (ISO) 批准。
C# 由 Anders Hejlsberg 及其团队在开发 .Net Framework 期间开发。
C# 是为公共语言基础结构 (CLI) 设计的,它由可执行代码和运行时环境组成,允许在不同的计算机平台和架构上使用各种高级语言。
以下原因使得 C# 成为一种广泛使用的专业语言:
- 它是一种现代的通用编程语言
- 它是面向对象的。
- 它是面向组件的。
- 它易于学习。
- 它是一种结构化语言。
- 它生成高效的程序。
- 它可以在各种计算机平台上编译。
- 它是 .Net Framework 的一部分。
C# 的强大编程特性
尽管 C# 的构造紧密遵循传统的 C 和 C++ 等高级语言,并且作为一种面向对象的编程语言,它与 Java 具有很强的相似性,但它拥有许多强大的编程特性,使其受到全球众多程序员的喜爱。
以下是 C# 的一些重要特性的列表:
- 布尔条件
- 自动垃圾回收
- 标准库
- 程序集版本控制
- 属性和事件
- 委托和事件管理
- 易于使用的泛型
- 索引器
- 条件编译
- 简单的多线程
- LINQ 和 Lambda 表达式
- 与 Windows 集成
C# - 环境
在线尝试选项
我们已在线设置了 C# 编程环境,以便您可以在线编译和执行所有可用的示例。它可以让您对您正在阅读的内容充满信心,并使您能够使用不同的选项验证程序。请随意修改任何示例并在网上执行它。
使用我们位于 CodingGround 的在线编译器尝试以下示例。
using System; namespace HelloWorldApplication { class HelloWorld { static void Main(string[] args) { /* my first program in C# */ Console.WriteLine("Hello World"); Console.ReadKey(); } } }在本教程中提供的多数示例中,您会在我们网站代码部分的右上角找到一个“尝试一下”选项,它会将您带到在线编译器。因此,请充分利用它并享受您的学习过程。
在本章中,我们将讨论创建 C# 程序所需的工具。我们已经提到 C# 是 .Net framework 的一部分,用于编写 .Net 应用程序。因此,在讨论运行 C# 程序的可用工具之前,让我们了解一下 C# 如何与 .Net framework 相关联。
.Net Framework
.Net framework 是一个革命性的平台,可帮助您编写以下类型的应用程序:
- Windows 应用程序
- Web 应用程序
- Web 服务
.Net framework 应用程序是多平台应用程序。该框架的设计方式使其可以从以下任何语言中使用:C#、C++、Visual Basic、Jscript、COBOL 等。所有这些语言都可以访问框架并相互通信。
.Net framework 包含一个庞大的代码库,供 C# 等客户端语言使用。以下是 .Net framework 的一些组件:
- 公共语言运行时 (CLR)
- .Net Framework 类库
- 公共语言规范
- 公共类型系统
- 元数据和程序集
- Windows 窗体
- ASP.Net 和 ASP.Net AJAX
- ADO.Net
- Windows 工作流基础 (WF)
- Windows Presentation Foundation
- Windows Communication Foundation (WCF)
- LINQ
有关这些组件各自执行的工作,请参阅 ASP.Net - 简介,有关每个组件的详细信息,请参阅 Microsoft 的文档。
C# 的集成开发环境 (IDE)
Microsoft 为 C# 编程提供了以下开发工具:
- Visual Studio 2010 (VS)
- Visual C# 2010 Express (VCE)
- Visual Web Developer
后两个可以从 Microsoft 官方网站免费获得。使用这些工具,您可以编写各种 C# 程序,从简单的命令行应用程序到更复杂的应用程序。您还可以使用基本的文本编辑器(如记事本)编写 C# 源代码文件,并使用命令行编译器(也是 .NET Framework 的一部分)将代码编译成程序集。
Visual C# Express 和 Visual Web Developer Express 版本是 Visual Studio 的精简版本,具有相同的界面。它们保留了 Visual Studio 的大部分功能。在本教程中,我们使用了 Visual C# 2010 Express。
您可以从 Microsoft Visual Studio 下载它。它会自动安装到您的机器上。
注意:安装 Express 版本需要活动的互联网连接。
在 Linux 或 Mac OS 上编写 C# 程序
尽管 .NET Framework 运行在 Windows 操作系统上,但有一些替代版本可以在其他操作系统上运行。Mono 是 .NET Framework 的一个开源版本,它包括一个 C# 编译器,并在多个操作系统上运行,包括各种 Linux 和 Mac OS 版本。请查看 Go Mono。
Mono 的既定目标不仅能够跨平台运行 Microsoft .NET 应用程序,而且还能为 Linux 开发人员提供更好的开发工具。Mono 可以在许多操作系统上运行,包括 Android、BSD、iOS、Linux、OS X、Windows、Solaris 和 UNIX。
C# - 程序结构
在我们学习 C# 编程语言的基本构建块之前,让我们先看一下 C# 程序的基本结构,以便在接下来的章节中将其作为参考。
创建 Hello World 程序
C# 程序包含以下部分:
- 命名空间声明
- 一个类
- 类方法
- 类属性
- 一个 Main 方法
- 语句和表达式
- 注释
让我们看一个打印“Hello World”字样的简单代码:
using System; namespace HelloWorldApplication { class HelloWorld { static void Main(string[] args) { /* my first program in C# */ Console.WriteLine("Hello World"); Console.ReadKey(); } } }
当这段代码被编译和执行时,它会产生以下结果:
Hello World
让我们看一下给定程序的各个部分:
程序的第一行 using System; - using 关键字用于在程序中包含 System 命名空间。一个程序通常有多个 using 语句。
下一行是 namespace 声明。namespace 是类的集合。HelloWorldApplication 命名空间包含类 HelloWorld。
下一行是 class 声明,类 HelloWorld 包含程序使用的的数据和方法定义。类通常包含多个方法。方法定义了类的行为。但是,HelloWorld 类只有一个方法 Main。
下一行定义了 Main 方法,它是所有 C# 程序的 入口点。Main 方法说明了类在执行时所做的事情。
下一行 /*...*/ 被编译器忽略,它用于在程序中添加 注释。
Main 方法使用语句 Console.WriteLine("Hello World"); 指定其行为。
WriteLine 是在 System 命名空间中定义的 Console 类的 方法。此语句导致消息“Hello, World!”显示在屏幕上。
最后一行 Console.ReadKey(); 是为 VS.NET 用户准备的。这使得程序等待按键,并且当程序从 Visual Studio .NET 启动时,它可以防止屏幕快速运行和关闭。
值得注意以下几点:
- C# 区分大小写。
- 所有语句和表达式都必须以分号 (;) 结尾。
- 程序执行从 Main 方法开始。
- 与 Java 不同,程序文件名可以与类名不同。
编译和执行程序
如果您使用 Visual Studio.Net 编译和执行 C# 程序,请执行以下步骤:
启动 Visual Studio。
在菜单栏上,选择文件 -> 新建 -> 项目。
从模板中选择 Visual C#,然后选择 Windows。
选择控制台应用程序。
为您的项目指定一个名称,然后单击“确定”按钮。
这将在解决方案资源管理器中创建一个新项目。
在代码编辑器中编写代码。
单击“运行”按钮或按 F5 键执行项目。将出现一个命令提示符窗口,其中包含“Hello World”行。
您可以使用命令行而不是 Visual Studio IDE 编译 C# 程序:
打开一个文本编辑器并添加上述代码。
将文件保存为 helloworld.cs
打开命令提示符工具并转到保存文件的目录。
键入 csc helloworld.cs 并按 Enter 键编译您的代码。
如果代码中没有错误,命令提示符会将您带到下一行并生成helloworld.exe可执行文件。
键入helloworld执行您的程序。
您可以在屏幕上看到输出“Hello World”。
C# - 基本语法
C# 是一种面向对象的编程语言。在面向对象编程方法中,程序由各种对象组成,这些对象通过动作相互交互。对象可能采取的动作称为方法。相同类型的对象被称为具有相同的类型,或者被称为属于同一个类。
例如,让我们考虑一个矩形对象。它具有诸如长度和宽度之类的属性。根据设计,它可能需要接受这些属性值、计算面积和显示详细信息的方法。
让我们看看矩形类的实现并讨论C#的基本语法:
using System; namespace RectangleApplication { class Rectangle { // member variables double length; double width; public void Acceptdetails() { length = 4.5; width = 3.5; } public double GetArea() { return length * width; } public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } } class ExecuteRectangle { static void Main(string[] args) { Rectangle r = new Rectangle(); r.Acceptdetails(); r.Display(); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Length: 4.5 Width: 3.5 Area: 15.75
using关键字
任何C#程序中的第一条语句是
using System;
using关键字用于在程序中包含命名空间。一个程序可以包含多个using语句。
class关键字
class关键字用于声明一个类。
C#中的注释
注释用于解释代码。编译器会忽略注释条目。C#程序中的多行注释以/*开头,以*/结尾,如下所示:
/* This program demonstrates The basic syntax of C# programming Language */
单行注释以'//'符号表示。例如,
}//end class Rectangle
成员变量
变量是类的属性或数据成员,用于存储数据。在前面的程序中,Rectangle类有两个名为length和width的成员变量。
成员函数
函数是一组执行特定任务的语句。类的成员函数在类中声明。我们的示例类Rectangle包含三个成员函数:AcceptDetails、GetArea和Display。
实例化类
在前面的程序中,类ExecuteRectangle包含Main()方法并实例化Rectangle类。
标识符
标识符是用于识别类、变量、函数或任何其他用户定义项的名称。C#中命名类的基本规则如下:
名称必须以字母开头,后面可以跟一系列字母、数字(0-9)或下划线。标识符的第一个字符不能是数字。
它不能包含任何嵌入的空格或符号,例如? - + ! @ # % ^ & * ( ) [ ] { } . ; : " ' / 和 \。但是,可以使用下划线(_)。
它不应是C#关键字。
C#关键字
关键字是预定义给C#编译器的保留字。这些关键字不能用作标识符。但是,如果您想将这些关键字用作标识符,则可以在关键字前加上@字符。
在C#中,某些标识符在代码上下文中具有特殊含义,例如get和set称为上下文关键字。
下表列出了C#中的保留关键字和上下文关键字:
保留关键字 | ||||||
---|---|---|---|---|---|---|
abstract | as | base | bool | break | byte | case |
catch | char | checked | class | const | continue | decimal |
default | delegate | do | double | else | enum | event |
explicit | extern | false | finally | fixed | float | for |
foreach | goto | if | implicit | in | in (泛型修饰符) | int |
interface | internal | is | lock | long | namespace | new |
null | object | operator | out | out (泛型修饰符) | override | params |
private | protected | public | readonly | ref | return | sbyte |
sealed | short | sizeof | stackalloc | static | string | struct |
switch | this | throw | true | try | typeof | uint |
ulong | unchecked | unsafe | ushort | using | virtual | void |
volatile | while | |||||
上下文关键字 | ||||||
add | alias | ascending | descending | dynamic | from | get |
global | group | into | join | let | orderby | partial (类型) |
partial (方法) |
remove | select | set |
C# - 数据类型
C#中的变量分为以下类型:
- 值类型
- 引用类型
- 指针类型
值类型
值类型变量可以直接赋值。它们派生自类System.ValueType。
值类型直接包含数据。一些示例包括int、char和float,它们分别存储数字、字母和浮点数。当您声明int类型时,系统会分配内存来存储该值。
下表列出了C# 2010中可用的值类型:
类型 | 表示 | 范围 | 默认值 |
---|---|---|---|
bool | 布尔值 | 真或假 | False |
byte | 8位无符号整数 | 0到255 | 0 |
char | 16位Unicode字符 | U +0000到U +ffff | '\0' |
decimal | 128位精确十进制值,具有28-29位有效数字 | (-7.9 x 1028到7.9 x 1028) / 100到28 | 0.0M |
double | 64位双精度浮点数类型 | (+/-)5.0 x 10-324到(+/-)1.7 x 10308 | 0.0D |
float | 32位单精度浮点数类型 | -3.4 x 1038到+ 3.4 x 1038 | 0.0F |
int | 32位有符号整数类型 | -2,147,483,648到2,147,483,647 | 0 |
long | 64位有符号整数类型 | -9,223,372,036,854,775,808到9,223,372,036,854,775,807 | 0L |
sbyte | 8位有符号整数类型 | -128到127 | 0 |
short | 16位有符号整数类型 | -32,768到32,767 | 0 |
uint | 32位无符号整数类型 | 0到4,294,967,295 | 0 |
ulong | 64位无符号整数类型 | 0到18,446,744,073,709,551,615 | 0 |
ushort | 16位无符号整数类型 | 0到65,535 | 0 |
要获取特定平台上类型或变量的确切大小,可以使用sizeof方法。表达式sizeof(type)以字节为单位生成对象或类型的存储大小。以下是如何在任何机器上获取int类型大小的示例:
using System; namespace DataTypeApplication { class Program { static void Main(string[] args) { Console.WriteLine("Size of int: {0}", sizeof(int)); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Size of int: 4
引用类型
引用类型不包含存储在变量中的实际数据,而是包含对变量的引用。
换句话说,它们引用一个内存位置。使用多个变量,引用类型可以引用一个内存位置。如果一个变量更改了内存位置中的数据,则另一个变量会自动反映此值变化。内置引用类型的示例包括:object、dynamic和string。
对象类型
对象类型是C#公共类型系统(CTS)中所有数据类型的最终基类。Object是System.Object类的别名。对象类型可以分配任何其他类型的值,包括值类型、引用类型、预定义或用户定义类型。但是,在分配值之前,需要进行类型转换。
当值类型转换为对象类型时,称为装箱,反之,当对象类型转换为值类型时,称为拆箱。
object obj; obj = 100; // this is boxing
动态类型
您可以在动态数据类型变量中存储任何类型的值。这些类型的变量的类型检查在运行时进行。
声明动态类型的语法如下:
dynamic <variable_name> = value;
例如,
dynamic d = 20;
动态类型类似于对象类型,不同之处在于对象类型变量的类型检查在编译时进行,而动态类型变量的类型检查在运行时进行。
字符串类型
字符串类型允许您将任何字符串值分配给变量。字符串类型是System.String类的别名。它派生自对象类型。字符串类型的值可以使用两种形式的字符串文字分配:带引号和@带引号。
例如,
String str = "Tutorials Point";
@带引号的字符串文字如下所示:
@"Tutorials Point";
用户定义的引用类型包括:类、接口或委托。我们将在后面的章节中讨论这些类型。
指针类型
指针类型变量存储另一个类型的内存地址。C#中的指针与C或C++中的指针具有相同的功能。
声明指针类型的语法如下:
type* identifier;
例如,
char* cptr; int* iptr;
我们将在“不安全代码”章节中讨论指针类型。
C# - 类型转换
类型转换是指将一种类型的数据转换为另一种类型。它也称为类型强制转换。在C#中,类型强制转换有两种形式:
隐式类型转换 - 这些转换由C#以类型安全的方式执行。例如,从较小的整数类型到较大的整数类型的转换,以及从派生类到基类的转换。
显式类型转换 - 这些转换由用户使用预定义函数显式执行。显式转换需要强制转换运算符。
以下示例显示了显式类型转换:
using System; namespace TypeConversionApplication { class ExplicitConversion { static void Main(string[] args) { double d = 5673.74; int i; // cast double to int. i = (int)d; Console.WriteLine(i); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
5673
C#类型转换方法
C#提供以下内置类型转换方法:
序号 | 方法和描述 |
---|---|
1 | ToBoolean 尽可能地将类型转换为布尔值。 |
2 | ToByte 将类型转换为字节。 |
3 | ToChar 尽可能地将类型转换为单个Unicode字符。 |
4 | ToDateTime 将类型(整数或字符串类型)转换为日期时间结构。 |
5 | ToDecimal 将浮点数或整数类型转换为十进制类型。 |
6 | ToDouble 将类型转换为双精度类型。 |
7 | ToInt16 将类型转换为16位整数。 |
8 | ToInt32 将类型转换为32位整数。 |
9 | ToInt64 将类型转换为64位整数。 |
10 | ToSbyte 将类型转换为有符号字节类型。 |
11 | ToSingle 将类型转换为小浮点数。 |
12 | ToString 将类型转换为字符串。 |
13 | ToType 将类型转换为指定的类型。 |
14 | ToUInt16 将类型转换为无符号int类型。 |
15 | ToUInt32 将类型转换为无符号长整型。 |
16 | ToUInt64 将类型转换为无符号大整数。 |
以下示例将各种值类型转换为字符串类型:
using System; namespace TypeConversionApplication { class StringConversion { static void Main(string[] args) { int i = 75; float f = 53.005f; double d = 2345.7652; bool b = true; Console.WriteLine(i.ToString()); Console.WriteLine(f.ToString()); Console.WriteLine(d.ToString()); Console.WriteLine(b.ToString()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
75 53.005 2345.7652 True
C# - 变量
变量只不过是赋予存储区域的名称,我们的程序可以操作这些存储区域。C#中的每个变量都具有特定的类型,该类型决定了变量内存的大小和布局、可以存储在该内存中的值的范围以及可以应用于变量的操作集。
C#中提供的基本值类型可以分为:
类型 | 示例 |
---|---|
整数类型 | sbyte、byte、short、ushort、int、uint、long、ulong和char |
浮点数类型 | float和double |
十进制类型 | decimal |
布尔类型 | 真或假值,根据分配情况而定 |
可空类型 | 可空数据类型 |
C#还允许定义其他变量值类型,例如enum,以及变量引用类型,例如class,我们将在后续章节中介绍。
定义变量
C#中变量定义的语法如下:
<data_type> <variable_list>;
这里,data_type必须是有效的C#数据类型,包括char、int、float、double或任何用户定义的数据类型,而variable_list可以包含一个或多个以逗号分隔的标识符名称。
这里显示了一些有效的变量定义:
int i, j, k; char c, ch; float f, salary; double d;
您可以在定义时初始化变量,如下所示:
int i = 100;
初始化变量
变量使用等号后跟常量表达式进行初始化(赋值)。初始化的一般形式如下:
variable_name = value;
变量可以在声明时进行初始化。初始化器由一个等号后跟一个常量表达式组成,例如:
<data_type> <variable_name> = value;
一些例子如下:
int d = 3, f = 5; /* initializing d and f. */ byte z = 22; /* initializes z. */ double pi = 3.14159; /* declares an approximation of pi. */ char x = 'x'; /* the variable x has the value 'x'. */
良好的编程习惯是正确初始化变量,否则有时程序可能会产生意外的结果。
以下示例使用了各种类型的变量:
using System; namespace VariableDefinition { class Program { static void Main(string[] args) { short a; int b ; double c; /* actual initialization */ a = 10; b = 20; c = a + b; Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
a = 10, b = 20, c = 30
从用户接受值
System 命名空间中的 Console 类提供了一个函数 ReadLine(),用于接受来自用户的输入并将其存储到变量中。
例如,
int num; num = Convert.ToInt32(Console.ReadLine());
函数 Convert.ToInt32() 将用户输入的数据转换为 int 数据类型,因为 Console.ReadLine() 接受字符串格式的数据。
C# 中的左值和右值表达式
C# 中有两种表达式:
左值 (lvalue) - 可以出现在赋值运算符左侧或右侧的表达式。
右值 (rvalue) - 可以出现在赋值运算符右侧但不能出现在左侧的表达式。
变量是左值,因此可以出现在赋值运算符的左侧。数字字面量是右值,因此不能被赋值,也不能出现在左侧。以下是一个有效的 C# 语句:
int g = 20;
但以下语句无效,会生成编译时错误:
10 = 20;
C# - 常量和字面量
常量是指程序在其执行过程中不能更改的固定值。这些固定值也称为字面量。常量可以是任何基本数据类型,例如整数常量、浮点常量、字符常量或字符串字面量。还有枚举常量。
常量与普通变量的处理方式相同,只是它们的数值在定义后不能修改。
整数字面量
整数字面量可以是十进制或十六进制常量。前缀指定基数或进制:十六进制为 0x 或 0X,十进制没有前缀。
整数字面量还可以带后缀,该后缀是 U 和 L 的组合,分别表示无符号和长整数。后缀可以是大写或小写,并且可以按任意顺序排列。
以下是一些整数字面量的示例:
212 /* Legal */ 215u /* Legal */ 0xFeeL /* Legal */
以下是各种类型整数字面量的其他示例:
85 /* decimal */ 0x4b /* hexadecimal */ 30 /* int */ 30u /* unsigned int */ 30l /* long */ 30ul /* unsigned long */
浮点字面量
浮点字面量包含整数部分、小数点、小数部分和指数部分。您可以以十进制形式或指数形式表示浮点字面量。
以下是一些浮点字面量的示例:
3.14159 /* Legal */ 314159E-5F /* Legal */ 510E /* Illegal: incomplete exponent */ 210f /* Illegal: no decimal or exponent */ .e55 /* Illegal: missing integer or fraction */
以十进制形式表示时,必须包含小数点、指数或两者;而使用指数形式表示时,必须包含整数部分、小数部分或两者。带符号的指数由 e 或 E 引入。
字符常量
字符字面量用单引号括起来。例如,'x' 可以存储在 char 类型的简单变量中。字符字面量可以是普通字符(如 'x')、转义序列(如 '\t')或通用字符(如 '\u02C0')。
在 C# 中,某些字符在前面加上反斜杠时具有特殊含义,用于表示换行符 (\n) 或制表符 (\t) 等。以下是一些此类转义序列代码的列表:
转义序列 | 含义 |
---|---|
\\ | \ 字符 |
\' | ' 字符 |
\" | " 字符 |
\? | ? 字符 |
\a | 警报或铃声 |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\xhh . . . | 一个或多个数字的十六进制数 |
以下示例显示了一些转义序列字符:
using System; namespace EscapeChar { class Program { static void Main(string[] args) { Console.WriteLine("Hello\tWorld\n\n"); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Hello World
字符串字面量
字符串字面量或常量用双引号 "" 或 @"" 括起来。字符串包含与字符字面量类似的字符:普通字符、转义序列和通用字符。
您可以使用字符串字面量将长行分解成多行,并使用空格分隔各个部分。
以下是一些字符串字面量的示例。这三种形式都是相同的字符串。
"hello, dear" "hello, \ dear" "hello, " "d" "ear" @"hello dear"
定义常量
常量使用 const 关键字定义。定义常量的语法如下:
const <data_type> <constant_name> = value;
以下程序演示了如何在程序中定义和使用常量:
using System; namespace DeclaringConstants { class Program { static void Main(string[] args) { const double pi = 3.14159; // constant declaration double r; Console.WriteLine("Enter Radius: "); r = Convert.ToDouble(Console.ReadLine()); double areaCircle = pi * r * r; Console.WriteLine("Radius: {0}, Area: {1}", r, areaCircle); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Enter Radius: 3 Radius: 3, Area: 28.27431
C# - 运算符
运算符是告诉编译器执行特定数学或逻辑操作的符号。C# 具有丰富的内置运算符集,并提供以下类型的运算符:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 其他运算符
本教程将逐一解释算术、关系、逻辑、位、赋值和其他运算符。
算术运算符
下表显示了 C# 支持的所有算术运算符。假设变量 A 为 10,变量 B 为 20,则:
运算符 | 描述 | 示例 |
---|---|---|
+ | 将两个操作数相加 | A + B = 30 |
- | 从第一个操作数中减去第二个操作数 | A - B = -10 |
* | 将两个操作数相乘 | A * B = 200 |
/ | 将分子除以分母 | B / A = 2 |
% | 模运算符,整数除法后的余数 | B % A = 0 |
++ | 递增运算符将整数的值增加 1 | A++ = 11 |
-- | 递减运算符将整数的值减少 1 | A-- = 9 |
关系运算符
下表显示了 C# 支持的所有关系运算符。假设变量 A 为 10,变量 B 为 20,则:
运算符 | 描述 | 示例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 |
!= | 检查两个操作数的值是否不相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左侧操作数的值是否大于右侧操作数的值,如果大于则条件为真。 | (A > B) 为假。 |
< | 检查左侧操作数的值是否小于右侧操作数的值,如果小于则条件为真。 | (A < B) 为真。 |
>= | 检查左侧操作数的值是否大于或等于右侧操作数的值,如果大于或等于则条件为真。 | (A >= B) 为假。 |
<= | 检查左侧操作数的值是否小于或等于右侧操作数的值,如果小于或等于则条件为真。 | (A <= B) 为真。 |
逻辑运算符
下表显示了 C# 支持的所有逻辑运算符。假设变量 A 的布尔值为 true,变量 B 的布尔值为 false,则:
运算符 | 描述 | 示例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数均非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中的任何一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑非运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
位运算符
位运算符在位上操作,并执行逐位操作。&、| 和 ^ 的真值表如下:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
假设 A = 60;B = 13;则它们的二进制格式如下:
A = 0011 1100
B = 0000 1101
-------------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011
C# 支持的位运算符列在下表中。假设变量 A 为 60,变量 B 为 13,则:
运算符 | 描述 | 示例 |
---|---|---|
& | 二进制与运算符将位复制到结果中,如果该位存在于两个操作数中。 | (A & B) = 12,即 0000 1100 |
| | 二进制或运算符将位复制到结果中,如果该位存在于任一操作数中。 | (A | B) = 61,即 0011 1101 |
^ | 二进制异或运算符将位复制到结果中,如果该位在一个操作数中设置,但在另一个操作数中未设置。 | (A ^ B) = 49,即 0011 0001 |
~ | 二进制取反运算符是一元运算符,具有“翻转”位的功效。 | (~A ) = -61,由于带符号二进制数,在二进制补码中为 1100 0011。 |
<< | 二进制左移运算符。左侧操作数的值向左移动右侧操作数指定的位数。 | A << 2 = 240,即 1111 0000 |
>> | 二进制右移运算符。左侧操作数的值向右移动右侧操作数指定的位数。 | A >> 2 = 15,即 0000 1111 |
赋值运算符
C# 支持以下赋值运算符:
运算符 | 描述 | 示例 |
---|---|---|
= | 简单赋值运算符,将右侧操作数的值赋给左侧操作数 | C = A + B 将 A + B 的值赋给 C |
+= | 加法赋值运算符,将右侧操作数加到左侧操作数上,并将结果赋给左侧操作数 | C += A 等价于 C = C + A |
-= | 减法赋值运算符,从左侧操作数中减去右侧操作数,并将结果赋给左侧操作数 | C -= A 等价于 C = C - A |
*= | 乘法赋值运算符,将右侧操作数乘以左侧操作数,并将结果赋给左侧操作数 | C *= A 等价于 C = C * A |
/= | 除法赋值运算符,将左侧操作数除以右侧操作数,并将结果赋给左侧操作数 | C /= A 等价于 C = C / A |
%= | 模赋值运算符,使用两个操作数进行模运算,并将结果赋给左侧操作数 | C %= A 等价于 C = C % A |
<<= | 左移赋值运算符 | C <<= 2 等价于 C = C << 2 |
>>= | 右移赋值运算符 | C >>= 2 等价于 C = C >> 2 |
&= | 按位与赋值运算符 | C &= 2 等价于 C = C & 2 |
^= | 按位异或赋值运算符 | C ^= 2 等价于 C = C ^ 2 |
|= | 按位或赋值运算符 | C |= 2 等价于 C = C | 2 |
其他运算符
C# 支持一些其他重要的运算符,包括sizeof、typeof 和 ? :。
运算符 | 描述 | 示例 |
---|---|---|
sizeof() | 返回数据类型的尺寸。 | sizeof(int) 返回 4。 |
typeof() | 返回类的类型。 | typeof(StreamReader); |
& | 返回变量的地址。 | &a; 返回变量的实际地址。 |
* | 指向变量的指针。 | *a; 创建名为 'a' 的指向变量的指针。 |
? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y |
is | 确定对象是否属于某种类型。 | If( Ford is Car) // 检查 Ford 是否是 Car 类的对象。 |
as | 如果转换失败,则不引发异常的转换。 | Object obj = new StringReader("Hello"); StringReader r = obj as StringReader; |
C# 中的运算符优先级
运算符优先级决定了表达式中项的组合方式。这会影响表达式的计算。某些运算符比其他运算符具有更高的优先级;例如,乘法运算符的优先级高于加法运算符。
例如 x = 7 + 3 * 2; 这里,x 被赋值为 13,而不是 20,因为运算符 * 的优先级高于 +,所以首先计算 3*2,然后将 7 加到结果中。
这里,优先级最高的运算符出现在表的最上面,优先级最低的出现在最下面。在表达式中,优先级高的运算符首先计算。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ -- (type)* & sizeof | 从右到左 |
乘法 | * / % | 从左到右 |
加法 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等性 | == != | 从左到右 |
按位与 | & | 从左到右 |
按位异或 | ^ | 从左到右 |
按位或 | | | 从左到右 |
逻辑与 | && | 从左到右 |
逻辑或 | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
C# - 决策
决策结构要求程序员指定一个或多个条件,由程序进行评估或测试,以及在确定条件为真时要执行的语句或语句组,以及可选地,在确定条件为假时要执行的其他语句。
以下是大多数编程语言中常见的决策结构的一般形式:
C# 提供以下类型的决策语句。点击以下链接查看其详细信息。
序号 | 语句 & 描述 |
---|---|
1 | if 语句
if 语句由一个布尔表达式后跟一个或多个语句组成。 |
2 | if...else 语句
if 语句后面可以跟一个可选的else 语句,当布尔表达式为假时执行。 |
3 | 嵌套 if 语句
可以在另一个if 或else if 语句中使用一个if 或else if 语句。 |
4 | switch 语句
switch 语句允许测试一个变量与一系列值是否相等。 |
5 | 嵌套 switch 语句
可以在另一个switch 语句中使用一个switch 语句。 |
?: 运算符
我们在上一章中介绍了条件运算符 ? :,它可以用来代替if...else 语句。它具有以下一般形式:
Exp1 ? Exp2 : Exp3;
其中 Exp1、Exp2 和 Exp3 是表达式。注意冒号的使用和位置。
?: 表达式的值如下确定:首先计算 Exp1。如果为真,则计算 Exp2,并将其作为整个 ? 表达式的值。如果 Exp1 为假,则计算 Exp3,并将其值作为表达式的值。
C# - 循环
可能存在需要多次执行代码块的情况。通常,语句是按顺序执行的:函数中的第一个语句首先执行,然后是第二个语句,依此类推。
编程语言提供了各种控制结构,允许更复杂的执行路径。
循环语句允许我们多次执行一个语句或一组语句,以下是大多数编程语言中循环语句的一般形式:
C# 提供以下类型的循环来处理循环需求。点击以下链接查看其详细信息。
序号 | 循环类型 & 描述 |
---|---|
1 | while 循环
当给定条件为真时,它重复执行一个语句或一组语句。它在执行循环体之前测试条件。 |
2 | for 循环
它多次执行一系列语句,并简化管理循环变量的代码。 |
3 | do...while 循环
它类似于 while 语句,只是它在循环体末尾测试条件。 |
4 | 嵌套循环
可以在任何其他 while、for 或 do..while 循环中使用一个或多个循环。 |
循环控制语句
循环控制语句改变执行的正常顺序。当执行离开作用域时,在该作用域中创建的所有自动对象都会被销毁。
C# 提供以下控制语句。点击以下链接查看其详细信息。
序号 | 控制语句 & 描述 |
---|---|
1 | break 语句
终止循环或switch 语句,并将执行转移到循环或 switch 后面的语句。 |
2 | continue 语句
导致循环跳过其主体剩余部分,并在重新迭代之前立即重新测试其条件。 |
无限循环
如果条件永远不会变为假,则循环将变成无限循环。for 循环通常用于此目的。由于构成 for 循环的三个表达式都不需要,因此可以通过将条件表达式留空来创建无限循环。
示例
using System; namespace Loops { class Program { static void Main(string[] args) { for (; ; ) { Console.WriteLine("Hey! I am Trapped"); } } } }
当条件表达式不存在时,它被假定为真。你可能有初始化和增量表达式,但程序员更常使用 for(;;) 构造来表示无限循环。
C# - 封装
封装被定义为“将一个或多个项目封装在物理或逻辑包中的过程”。在面向对象编程方法中,封装防止访问实现细节。
抽象和封装是面向对象编程中的相关特性。抽象允许显示相关信息,而封装使程序员能够实现所需的抽象级别。
封装是通过使用访问修饰符实现的。访问修饰符定义了类成员的作用域和可见性。C# 支持以下访问修饰符:
- Public
- Private
- Protected
- Internal
- Protected internal
Public 访问修饰符
Public 访问修饰符允许类将其成员变量和成员函数公开给其他函数和对象。任何公共成员都可以从类外部访问。
以下示例说明了这一点:
using System; namespace RectangleApplication { class Rectangle { //member variables public double length; public double width; public double GetArea() { return length * width; } public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }//end class Rectangle class ExecuteRectangle { static void Main(string[] args) { Rectangle r = new Rectangle(); r.length = 4.5; r.width = 3.5; r.Display(); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Length: 4.5 Width: 3.5 Area: 15.75
在前面的示例中,成员变量 length 和 width 被声明为public,因此可以使用 Rectangle 类的实例(名为r)从函数 Main() 中访问它们。
成员函数Display() 和GetArea() 也可以直接访问这些变量,而无需使用类的任何实例。
成员函数Display() 也被声明为public,因此也可以使用 Rectangle 类的实例(名为r)从Main() 中访问它。
Private 访问修饰符
Private 访问修饰符允许类将其成员变量和成员函数隐藏在其他函数和对象之外。只有同一类的函数才能访问其私有成员。即使是类的实例也无法访问其私有成员。
以下示例说明了这一点:
using System; namespace RectangleApplication { class Rectangle { //member variables private double length; private double width; public void Acceptdetails() { Console.WriteLine("Enter Length: "); length = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("Enter Width: "); width = Convert.ToDouble(Console.ReadLine()); } public double GetArea() { return length * width; } public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }//end class Rectangle class ExecuteRectangle { static void Main(string[] args) { Rectangle r = new Rectangle(); r.Acceptdetails(); r.Display(); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Enter Length: 4.4 Enter Width: 3.3 Length: 4.4 Width: 3.3 Area: 14.52
在前面的示例中,成员变量 length 和 width 被声明为private,因此无法从函数 Main() 中访问它们。成员函数AcceptDetails() 和Display() 可以访问这些变量。由于成员函数AcceptDetails() 和Display() 被声明为public,因此可以使用 Rectangle 类的实例(名为r)从Main() 中访问它们。
Protected 访问修饰符
Protected 访问修饰符允许子类访问其基类的成员变量和成员函数。这样,它有助于实现继承。我们将在继承章节中更详细地讨论这一点。
Internal 访问修饰符
Internal 访问修饰符允许类将其成员变量和成员函数公开给当前程序集中的其他函数和对象。换句话说,任何具有 internal 访问修饰符的成员都可以从定义该成员的应用程序中定义的任何类或方法中访问。
以下程序说明了这一点:
using System; namespace RectangleApplication { class Rectangle { //member variables internal double length; internal double width; double GetArea() { return length * width; } public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }//end class Rectangle class ExecuteRectangle { static void Main(string[] args) { Rectangle r = new Rectangle(); r.length = 4.5; r.width = 3.5; r.Display(); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Length: 4.5 Width: 3.5 Area: 15.75
在前面的示例中,请注意成员函数GetArea() 没有声明任何访问修饰符。那么,如果我们不提及任何访问修饰符,类成员的默认访问修饰符是什么?它是private。
Protected Internal 访问修饰符
protected internal 访问修饰符允许类将其成员变量和成员函数隐藏在其他类对象和函数之外,除了同一应用程序中的子类。这在实现继承时也会用到。
C# - 方法
方法是一组一起执行任务的语句。每个 C# 程序至少有一个包含名为 Main 的方法的类。
要使用方法,你需要:
- 定义方法
- 调用方法
在 C# 中定义方法
定义方法时,基本上是在声明其结构的元素。在 C# 中定义方法的语法如下:
<Access Specifier> <Return Type> <Method Name>(Parameter List) { Method Body }
以下是方法的各个元素:
访问修饰符 - 确定从另一个类访问变量或方法的可见性。
返回类型 - 方法可以返回值。返回类型是方法返回的值的数据类型。如果方法不返回值,则返回类型为void。
方法名称 - 方法名称是唯一的标识符,并且区分大小写。它不能与类中声明的任何其他标识符相同。
参数列表 - 参数括在括号内,用于向方法传递和接收数据。参数列表指的是方法的参数的类型、顺序和数量。参数是可选的;也就是说,方法可能不包含任何参数。
方法体 - 包含完成所需活动所需的指令集。
示例
以下代码片段显示了一个函数FindMax,它接受两个整数值并返回两者中较大的一个。它具有 public 访问修饰符,因此可以使用类的实例从类外部访问它。
class NumberManipulator { public int FindMax(int num1, int num2) { /* local variable declaration */ int result; if (num1 > num2) result = num1; else result = num2; return result; } ... }
在 C# 中调用方法
可以使用方法的名称来调用方法。以下示例说明了这一点:
using System; namespace CalculatorApplication { class NumberManipulator { public int FindMax(int num1, int num2) { /* local variable declaration */ int result; if (num1 > num2) result = num1; else result = num2; return result; } static void Main(string[] args) { /* local variable definition */ int a = 100; int b = 200; int ret; NumberManipulator n = new NumberManipulator(); //calling the FindMax method ret = n.FindMax(a, b); Console.WriteLine("Max value is : {0}", ret ); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Max value is : 200
你也可以使用类的实例从其他类调用公共方法。例如,方法FindMax 属于NumberManipulator 类,你可以从另一个类Test 中调用它。
using System; namespace CalculatorApplication { class NumberManipulator { public int FindMax(int num1, int num2) { /* local variable declaration */ int result; if(num1 > num2) result = num1; else result = num2; return result; } } class Test { static void Main(string[] args) { /* local variable definition */ int a = 100; int b = 200; int ret; NumberManipulator n = new NumberManipulator(); //calling the FindMax method ret = n.FindMax(a, b); Console.WriteLine("Max value is : {0}", ret ); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Max value is : 200
递归方法调用
方法可以调用自身。这称为递归。以下是一个使用递归函数计算给定数字的阶乘的示例:
using System; namespace CalculatorApplication { class NumberManipulator { public int factorial(int num) { /* local variable declaration */ int result; if (num == 1) { return 1; } else { result = factorial(num - 1) * num; return result; } } static void Main(string[] args) { NumberManipulator n = new NumberManipulator(); //calling the factorial method {0}", n.factorial(6)); Console.WriteLine("Factorial of 7 is : {0}", n.factorial(7)); Console.WriteLine("Factorial of 8 is : {0}", n.factorial(8)); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Factorial of 6 is: 720 Factorial of 7 is: 5040 Factorial of 8 is: 40320
向方法传递参数
调用带参数的方法时,需要将参数传递给方法。参数传递给方法有三种方式:
序号 | 机制 & 描述 |
---|---|
1 | 值参数
此方法将参数的实际值复制到函数的形式参数中。在这种情况下,在函数内部对参数所做的更改不会影响参数。 |
2 | 引用参数
此方法将参数的内存地址的引用复制到形式参数中。这意味着对参数所做的更改会影响参数。 |
3 | 输出参数
此方法有助于返回多个值。 |
C# - 可空类型
C# 提供了一种特殊的数据类型,即可空类型,您可以为其分配正常范围的值以及空值。
例如,您可以将从 -2,147,483,648 到 2,147,483,647 的任何值或 null 存储在 Nullable<Int32> 变量中。类似地,您可以在 Nullable<bool> 变量中分配 true、false 或 null。声明可空类型的语法如下所示:
< data_type> ? <variable_name> = null;
以下示例演示了可空数据类型的用法:
using System; namespace CalculatorApplication { class NullablesAtShow { static void Main(string[] args) { int? num1 = null; int? num2 = 45; double? num3 = new double?(); double? num4 = 3.14157; bool? boolval = new bool?(); // display the values Console.WriteLine("Nullables at Show: {0}, {1}, {2}, {3}", num1, num2, num3, num4); Console.WriteLine("A Nullable boolean value: {0}", boolval); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Nullables at Show: , 45, , 3.14157 A Nullable boolean value:
空合并运算符 (??)
空合并运算符与可空值类型和引用类型一起使用。它用于将操作数转换为另一个可空(或不可空)值类型操作数的类型,其中允许隐式转换。
如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。以下示例说明了这一点:
using System; namespace CalculatorApplication { class NullablesAtShow { static void Main(string[] args) { double? num1 = null; double? num2 = 3.14157; double num3; num3 = num1 ?? 5.34; Console.WriteLine(" Value of num3: {0}", num3); num3 = num2 ?? 5.34; Console.WriteLine(" Value of num3: {0}", num3); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Value of num3: 5.34 Value of num3: 3.14157
C# - 数组
数组存储相同类型元素的固定大小的顺序集合。数组用于存储数据集合,但通常更有用的是将数组视为存储在连续内存位置的相同类型变量的集合。
与其声明单独的变量,如 number0、number1、... 和 number99,不如声明一个数组变量,如 numbers,并使用 numbers[0]、numbers[1] 和 ...、numbers[99] 来表示单独的变量。数组中的特定元素通过索引访问。
所有数组都由连续的内存位置组成。最低地址对应于第一个元素,最高地址对应于最后一个元素。
声明数组
要在 C# 中声明数组,可以使用以下语法:
datatype[] arrayName;
其中,
数据类型用于指定数组中元素的类型。
[ ] 指定数组的秩。秩指定数组的大小。
数组名称指定数组的名称。
例如,
double[] balance;
初始化数组
声明数组不会在内存中初始化数组。当数组变量被初始化时,您可以为数组分配值。
数组是一种引用类型,因此您需要使用new关键字来创建数组的实例。例如,
double[] balance = new double[10];
为数组赋值
您可以使用索引号为各个数组元素赋值,例如:
double[] balance = new double[10]; balance[0] = 4500.0;
您可以在声明时为数组赋值,如下所示:
double[] balance = { 2340.0, 4523.69, 3421.0};
您还可以创建和初始化数组,如下所示:
int [] marks = new int[5] { 99, 98, 92, 97, 95};
您也可以省略数组的大小,如下所示:
int [] marks = new int[] { 99, 98, 92, 97, 95};
您可以将一个数组变量复制到另一个目标数组变量中。在这种情况下,目标和源都指向相同的内存位置:
int [] marks = new int[] { 99, 98, 92, 97, 95}; int[] score = marks;
当您创建数组时,C# 编译器会隐式地将每个数组元素初始化为一个默认值,具体取决于数组类型。例如,对于 int 数组,所有元素都初始化为 0。
访问数组元素
通过索引数组名称来访问元素。这可以通过在数组名称后面方括号内放置元素的索引来完成。例如,
double salary = balance[9];
以下示例演示了上述概念,即声明、赋值和访问数组:
using System; namespace ArrayApplication { class MyArray { static void Main(string[] args) { int [] n = new int[10]; /* n is an array of 10 integers */ int i,j; /* initialize elements of array n */ for ( i = 0; i < 10; i++ ) { n[ i ] = i + 100; } /* output each array element's value */ for (j = 0; j < 10; j++ ) { Console.WriteLine("Element[{0}] = {1}", j, n[j]); } Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Element[0] = 100 Element[1] = 101 Element[2] = 102 Element[3] = 103 Element[4] = 104 Element[5] = 105 Element[6] = 106 Element[7] = 107 Element[8] = 108 Element[9] = 109
使用foreach循环
在前面的示例中,我们使用了 for 循环来访问每个数组元素。您还可以使用foreach语句来迭代数组。
using System; namespace ArrayApplication { class MyArray { static void Main(string[] args) { int [] n = new int[10]; /* n is an array of 10 integers */ /* initialize elements of array n */ for ( int i = 0; i < 10; i++ ) { n[i] = i + 100; } /* output each array element's value */ foreach (int j in n ) { int i = j-100; Console.WriteLine("Element[{0}] = {1}", i, j); } Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Element[0] = 100 Element[1] = 101 Element[2] = 102 Element[3] = 103 Element[4] = 104 Element[5] = 105 Element[6] = 106 Element[7] = 107 Element[8] = 108 Element[9] = 109
C# 数组
以下是一些与数组相关的重要的概念,C# 程序员应该清楚:
序号 | 概念和描述 |
---|---|
1 | 多维数组
C# 支持多维数组。多维数组最简单的形式是二维数组。 |
2 | 交错数组
C# 支持多维数组,即数组的数组。 |
3 | 将数组传递给函数
您可以通过指定数组的名称(不带索引)将指向数组的指针传递给函数。 |
4 | 参数数组
这用于将未知数量的参数传递给函数。 |
5 | Array 类
定义在 System 命名空间中,它是所有数组的基类,并提供用于处理数组的各种属性和方法。 |
C# - 字符串
在 C# 中,您可以使用字符串作为字符数组,但是,更常见的做法是使用string关键字来声明字符串变量。string 关键字是System.String类的别名。
创建字符串对象
您可以使用以下方法之一创建字符串对象:
通过将字符串文字分配给 String 变量
通过使用 String 类构造函数
通过使用字符串连接运算符 (+)
通过检索返回字符串的属性或调用方法
通过调用格式化方法将值或对象转换为其字符串表示形式
以下示例演示了这一点:
using System; namespace StringApplication { class Program { static void Main(string[] args) { //from string literal and string concatenation string fname, lname; fname = "Rowan"; lname = "Atkinson"; char []letters= { 'H', 'e', 'l', 'l','o' }; string [] sarray={ "Hello", "From", "Tutorials", "Point" }; string fullname = fname + lname; Console.WriteLine("Full Name: {0}", fullname); //by using string constructor { 'H', 'e', 'l', 'l','o' }; string greetings = new string(letters); Console.WriteLine("Greetings: {0}", greetings); //methods returning string { "Hello", "From", "Tutorials", "Point" }; string message = String.Join(" ", sarray); Console.WriteLine("Message: {0}", message); //formatting method to convert a value DateTime waiting = new DateTime(2012, 10, 10, 17, 58, 1); string chat = String.Format("Message sent at {0:t} on {0:D}", waiting); Console.WriteLine("Message: {0}", chat); } } }
当以上代码编译并执行时,会产生以下结果:
Full Name: RowanAtkinson Greetings: Hello Message: Hello From Tutorials Point Message: Message sent at 5:58 PM on Wednesday, October 10, 2012
String 类的属性
String 类具有以下两个属性:
序号 | 属性和描述 |
---|---|
1 | Chars 获取当前String对象中指定位置处的Char对象。 |
2 | Length 获取当前 String 对象中的字符数。 |
String 类的方法
String 类有许多方法可以帮助您处理字符串对象。下表提供了一些最常用的方法:
序号 | 方法和描述 |
---|---|
1 | public static int Compare(string strA, string strB) 比较两个指定的字符串对象,并返回一个整数,指示它们在排序顺序中的相对位置。 |
2 | public static int Compare(string strA, string strB, bool ignoreCase ) 比较两个指定的字符串对象,并返回一个整数,指示它们在排序顺序中的相对位置。但是,如果布尔参数为 true,则忽略大小写。 |
3 | public static string Concat(string str0, string str1) 连接两个字符串对象。 |
4 | public static string Concat(string str0, string str1, string str2) 连接三个字符串对象。 |
5 | public static string Concat(string str0, string str1, string str2, string str3) 连接四个字符串对象。 |
6 | public bool Contains(string value) 返回一个值,指示指定的 String 对象是否出现在此字符串中。 |
7 | public static string Copy(string str) 创建一个新的 String 对象,其值与指定的字符串相同。 |
8 | public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) 将 String 对象指定位置处的指定数量的字符复制到 Unicode 字符数组的指定位置。 |
9 | public bool EndsWith(string value) 确定字符串对象的末尾是否与指定的字符串匹配。 |
10 | public bool Equals(string value) 确定当前 String 对象和指定的 String 对象是否具有相同的值。 |
11 | public static bool Equals(string a, string b) 确定两个指定的 String 对象是否具有相同的值。 |
12 | public static string Format(string format, Object arg0) 将指定字符串中一个或多个格式项替换为指定对象的字符串表示形式。 |
13 | public int IndexOf(char value) 返回当前字符串中指定 Unicode 字符第一次出现的从零开始的索引。 |
14 | public int IndexOf(string value) 返回此实例中指定字符串第一次出现的从零开始的索引。 |
15 | public int IndexOf(char value, int startIndex) 返回此字符串中指定 Unicode 字符第一次出现的从零开始的索引,从指定字符位置开始搜索。 |
16 | public int IndexOf(string value, int startIndex) 返回此实例中指定字符串第一次出现的从零开始的索引,从指定字符位置开始搜索。 |
17 | public int IndexOfAny(char[] anyOf) 返回此实例中指定 Unicode 字符数组中任何字符第一次出现的从零开始的索引。 |
18 | public int IndexOfAny(char[] anyOf, int startIndex) 返回此实例中指定 Unicode 字符数组中任何字符第一次出现的从零开始的索引,从指定字符位置开始搜索。 |
19 | public string Insert(int startIndex, string value) 返回一个新字符串,其中指定的字符串插入到当前字符串对象的指定索引位置。 |
20 | public static bool IsNullOrEmpty(string value) 指示指定的字符串是否为 null 或空字符串。 |
21 | public static string Join(string separator, params string[] value) 连接字符串数组的所有元素,在每个元素之间使用指定的连接符。 |
22 | public static string Join(string separator, string[] value, int startIndex, int count) 连接字符串数组的指定元素,在每个元素之间使用指定的连接符。 |
23 | public int LastIndexOf(char value) 返回当前字符串对象中指定 Unicode 字符最后一次出现的从零开始的索引位置。 |
24 | public int LastIndexOf(string value) 返回当前字符串对象中指定字符串最后一次出现的从零开始的索引位置。 |
25 | public string Remove(int startIndex) 删除当前实例中从指定位置开始到最后一个位置的所有字符,并返回字符串。 |
26 | public string Remove(int startIndex, int count) 删除当前字符串中从指定位置开始的指定数量的字符,并返回字符串。 |
27 | public string Replace(char oldChar, char newChar) 将当前字符串对象中指定 Unicode 字符的所有出现替换为指定的 Unicode 字符,并返回新字符串。 |
28 | public string Replace(string oldValue, string newValue) 将当前字符串对象中指定字符串的所有出现替换为指定的字符串,并返回新字符串。 |
29 | public string[] Split(params char[] separator) 返回一个字符串数组,其中包含当前字符串对象中的子字符串,这些子字符串由指定 Unicode 字符数组的元素分隔。 |
30 | public string[] Split(char[] separator, int count) 返回一个字符串数组,其中包含当前字符串对象中的子字符串,这些子字符串由指定 Unicode 字符数组的元素分隔。int 参数指定要返回的最大子字符串数。 |
31 | public bool StartsWith(string value) 确定此字符串实例的开头是否与指定的字符串匹配。 |
32 | public char[] ToCharArray() 返回一个 Unicode 字符数组,其中包含当前字符串对象中的所有字符。 |
33 | public char[] ToCharArray(int startIndex, int length) 返回当前字符串对象中从指定索引开始到指定长度的所有字符的 Unicode 字符数组。 |
34 | public string ToLower() 返回此字符串转换为小写的副本。 |
35 | public string ToUpper() 返回此字符串转换为大写的副本。 |
36 | public string Trim() 移除当前字符串对象中所有前导和尾随的空白字符。 |
您可以访问 MSDN 库以获取方法和 String 类构造函数的完整列表。
示例
以下示例演示了上面提到的一些方法 -
比较字符串
using System; namespace StringApplication { class StringProg { static void Main(string[] args) { string str1 = "This is test"; string str2 = "This is text"; if (String.Compare(str1, str2) == 0) { Console.WriteLine(str1 + " and " + str2 + " are equal."); } else { Console.WriteLine(str1 + " and " + str2 + " are not equal."); } Console.ReadKey() ; } } }
当以上代码编译并执行时,会产生以下结果:
This is test and This is text are not equal.
字符串包含字符串
using System; namespace StringApplication { class StringProg { static void Main(string[] args) { string str = "This is test"; if (str.Contains("test")) { Console.WriteLine("The sequence 'test' was found."); } Console.ReadKey() ; } } }
当以上代码编译并执行时,会产生以下结果:
The sequence 'test' was found.
获取子字符串
using System; namespace StringApplication { class StringProg { static void Main(string[] args) { string str = "Last night I dreamt of San Pedro"; Console.WriteLine(str); string substr = str.Substring(23); Console.WriteLine(substr); } } }
当以上代码编译并执行时,会产生以下结果:
San Pedro
连接字符串
using System; namespace StringApplication { class StringProg { static void Main(string[] args) { string[] starray = new string[]{"Down the way nights are dark", "And the sun shines daily on the mountain top", "I took a trip on a sailing ship", "And when I reached Jamaica", "I made a stop"}; string str = String.Join("\n", starray); Console.WriteLine(str); } } }
当以上代码编译并执行时,会产生以下结果:
Down the way nights are dark And the sun shines daily on the mountain top I took a trip on a sailing ship And when I reached Jamaica I made a stop
C# - 结构体
在 C# 中,结构体是一种值类型数据类型。它可以帮助您使单个变量保存各种数据类型的相关数据。struct 关键字用于创建结构体。
结构体用于表示记录。假设您想跟踪图书馆中书籍的信息。您可能希望跟踪每本书的以下属性 -
- 标题
- 作者
- 主题
- 图书 ID
定义结构体
要定义结构体,必须使用 struct 语句。struct 语句定义了一种新的数据类型,其中包含程序的多个成员。
例如,以下是如何声明 Book 结构体 -
struct Books { public string title; public string author; public string subject; public int book_id; };
以下程序展示了结构体的用法 -
using System; struct Books { public string title; public string author; public string subject; public int book_id; }; public class testStructure { public static void Main(string[] args) { Books Book1; /* Declare Book1 of type Book */ Books Book2; /* Declare Book2 of type Book */ /* book 1 specification */ Book1.title = "C Programming"; Book1.author = "Nuha Ali"; Book1.subject = "C Programming Tutorial"; Book1.book_id = 6495407; /* book 2 specification */ Book2.title = "Telecom Billing"; Book2.author = "Zara Ali"; Book2.subject = "Telecom Billing Tutorial"; Book2.book_id = 6495700; /* print Book1 info */ Console.WriteLine( "Book 1 title : {0}", Book1.title); Console.WriteLine("Book 1 author : {0}", Book1.author); Console.WriteLine("Book 1 subject : {0}", Book1.subject); Console.WriteLine("Book 1 book_id :{0}", Book1.book_id); /* print Book2 info */ Console.WriteLine("Book 2 title : {0}", Book2.title); Console.WriteLine("Book 2 author : {0}", Book2.author); Console.WriteLine("Book 2 subject : {0}", Book2.subject); Console.WriteLine("Book 2 book_id : {0}", Book2.book_id); Console.ReadKey(); } }
当以上代码编译并执行时,会产生以下结果:
Book 1 title : C Programming Book 1 author : Nuha Ali Book 1 subject : C Programming Tutorial Book 1 book_id : 6495407 Book 2 title : Telecom Billing Book 2 author : Zara Ali Book 2 subject : Telecom Billing Tutorial Book 2 book_id : 6495700
C# 结构体的特性
您已经使用了一个名为 Books 的简单结构体。C# 中的结构体与传统 C 或 C++ 中的结构体有很大不同。C# 结构体具有以下特性 -
结构体可以包含方法、字段、索引器、属性、运算符方法和事件。
结构体可以有定义的构造函数,但不能有析构函数。但是,您不能为结构体定义默认构造函数。默认构造函数会自动定义,并且不能更改。
与类不同,结构体不能继承其他结构体或类。
结构体不能用作其他结构体或类的基类。
结构体可以实现一个或多个接口。
结构体成员不能指定为抽象的、虚拟的或受保护的。
当您使用 New 运算符创建结构体对象时,它将被创建,并调用相应的构造函数。与类不同,结构体可以在不使用 New 运算符的情况下实例化。
如果不使用 New 运算符,则字段将保持未赋值,并且在所有字段初始化之前,该对象都无法使用。
类与结构体
类和结构体具有以下基本区别 -
- 类是引用类型,结构体是值类型
- 结构体不支持继承
- 结构体不能有默认构造函数
根据以上讨论,让我们重写前面的示例 -
using System; struct Books { private string title; private string author; private string subject; private int book_id; public void getValues(string t, string a, string s, int id) { title = t; author = a; subject = s; book_id = id; } public void display() { Console.WriteLine("Title : {0}", title); Console.WriteLine("Author : {0}", author); Console.WriteLine("Subject : {0}", subject); Console.WriteLine("Book_id :{0}", book_id); } }; public class testStructure { public static void Main(string[] args) { Books Book1 = new Books(); /* Declare Book1 of type Book */ Books Book2 = new Books(); /* Declare Book2 of type Book */ /* book 1 specification */ Book1.getValues("C Programming", "Nuha Ali", "C Programming Tutorial",6495407); /* book 2 specification */ Book2.getValues("Telecom Billing", "Zara Ali", "Telecom Billing Tutorial", 6495700); /* print Book1 info */ Book1.display(); /* print Book2 info */ Book2.display(); Console.ReadKey(); } }
当以上代码编译并执行时,会产生以下结果:
Title : C Programming Author : Nuha Ali Subject : C Programming Tutorial Book_id : 6495407 Title : Telecom Billing Author : Zara Ali Subject : Telecom Billing Tutorial Book_id : 6495700
C# - 枚举
枚举是一组命名的整型常量。使用 enum 关键字声明枚举类型。
C# 枚举是值数据类型。换句话说,枚举包含它自己的值,不能继承或传递继承。
声明 enum 变量
声明枚举的一般语法如下 -
enum <enum_name> { enumeration list };
其中,
enum_name 指定枚举类型名称。
enumeration list 是一个用逗号分隔的标识符列表。
枚举列表中的每个符号都代表一个整数值,比它前面的符号大 1。默认情况下,第一个枚举符号的值为 0。例如 -
enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
示例
以下示例演示了 enum 变量的用法 -
using System; namespace EnumApplication { class EnumProgram { enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat }; static void Main(string[] args) { int WeekdayStart = (int)Days.Mon; int WeekdayEnd = (int)Days.Fri; Console.WriteLine("Monday: {0}", WeekdayStart); Console.WriteLine("Friday: {0}", WeekdayEnd); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Monday: 1 Friday: 5
C# - 类
当您定义一个类时,您定义了一个数据类型的蓝图。这实际上并没有定义任何数据,但它确实定义了类名所代表的含义。也就是说,类的对象由什么组成以及可以对该对象执行什么操作。对象是类的实例。构成类的这些方法和变量称为类的成员。
定义类
类定义以关键字 class 后跟类名开头;以及用一对花括号括起来的类体。以下是类定义的一般形式 -
<access specifier> class class_name { // member variables <access specifier> <data type> variable1; <access specifier> <data type> variable2; ... <access specifier> <data type> variableN; // member methods <access specifier> <return type> method1(parameter_list) { // method body } <access specifier> <return type> method2(parameter_list) { // method body } ... <access specifier> <return type> methodN(parameter_list) { // method body } }
注意 -
访问修饰符指定成员以及类本身的访问规则。如果未提及,则类类型的默认访问修饰符为 internal。成员的默认访问权限为 private。
数据类型指定变量的类型,返回值类型指定方法返回的数据的类型(如果有)。
要访问类成员,可以使用点 (.) 运算符。
点运算符将对象的名称与成员的名称链接起来。
以下示例说明了迄今为止讨论的概念 -
using System; namespace BoxApplication { class Box { public double length; // Length of a box public double breadth; // Breadth of a box public double height; // Height of a box } class Boxtester { static void Main(string[] args) { Box Box1 = new Box(); // Declare Box1 of type Box Box Box2 = new Box(); // Declare Box2 of type Box double volume = 0.0; // Store the volume of a box here // box 1 specification Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0; // box 2 specification Box2.height = 10.0; Box2.length = 12.0; Box2.breadth = 13.0; // volume of box 1 volume = Box1.height * Box1.length * Box1.breadth; Console.WriteLine("Volume of Box1 : {0}", volume); // volume of box 2 volume = Box2.height * Box2.length * Box2.breadth; Console.WriteLine("Volume of Box2 : {0}", volume); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Volume of Box1 : 210 Volume of Box2 : 1560
成员函数和封装
类的成员函数是其定义或原型在类定义中类似于任何其他变量的函数。它对它是成员的类的任何对象进行操作,并可以访问该对象类的所有成员。
成员变量是对象(从设计角度)的属性,并且它们被设为私有以实现封装。这些变量只能使用公共成员函数访问。
让我们将上述概念用于设置和获取类中不同类成员的值 -
using System; namespace BoxApplication { class Box { private double length; // Length of a box private double breadth; // Breadth of a box private double height; // Height of a box public void setLength( double len ) { length = len; } public void setBreadth( double bre ) { breadth = bre; } public void setHeight( double hei ) { height = hei; } public double getVolume() { return length * breadth * height; } } class Boxtester { static void Main(string[] args) { Box Box1 = new Box(); // Declare Box1 of type Box Box Box2 = new Box(); double volume; // Declare Box2 of type Box // box 1 specification Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); // box 2 specification Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0); // volume of box 1 volume = Box1.getVolume(); Console.WriteLine("Volume of Box1 : {0}" ,volume); // volume of box 2 volume = Box2.getVolume(); Console.WriteLine("Volume of Box2 : {0}", volume); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Volume of Box1 : 210 Volume of Box2 : 1560
C# 构造函数
类的构造函数是类的特殊成员函数,每当我们创建该类的新的对象时都会执行它。
构造函数的名称与类的名称完全相同,并且它没有任何返回值类型。以下示例解释了构造函数的概念 -
using System; namespace LineApplication { class Line { private double length; // Length of a line public Line() { Console.WriteLine("Object is being created"); } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(); // set line length line.setLength(6.0); Console.WriteLine("Length of line : {0}", line.getLength()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Object is being created Length of line : 6
默认构造函数没有任何参数,但如果需要,构造函数可以有参数。此类构造函数称为参数化构造函数。此技术可以帮助您在对象创建时为其分配初始值,如以下示例所示 -
using System; namespace LineApplication { class Line { private double length; // Length of a line public Line(double len) { //Parameterized constructor Console.WriteLine("Object is being created, length = {0}", len); length = len; } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(10.0); Console.WriteLine("Length of line : {0}", line.getLength()); // set line length line.setLength(6.0); Console.WriteLine("Length of line : {0}", line.getLength()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Object is being created, length = 10 Length of line : 10 Length of line : 6
C# 析构函数
析构函数是类的特殊成员函数,每当其类的对象超出范围时都会执行。析构函数的名称与类的名称完全相同,前面带有波浪号 (~),并且它既不能返回值,也不能接受任何参数。
析构函数对于在退出程序之前释放内存资源非常有用。析构函数不能被继承或重载。
以下示例解释了析构函数的概念 -
using System; namespace LineApplication { class Line { private double length; // Length of a line public Line() { // constructor Console.WriteLine("Object is being created"); } ~Line() { //destructor Console.WriteLine("Object is being deleted"); } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(); // set line length line.setLength(6.0); Console.WriteLine("Length of line : {0}", line.getLength()); } } }
当以上代码编译并执行时,会产生以下结果:
Object is being created Length of line : 6 Object is being deleted
C# 类中的静态成员
我们可以使用 static 关键字将类成员定义为静态的。当我们将类的成员声明为静态时,这意味着无论创建了多少个类的对象,静态成员都只有一个副本。
关键字 static 表示类只有一个成员实例。静态变量用于定义常量,因为可以通过调用类来检索它们的值,而无需创建它的实例。静态变量可以在成员函数或类定义之外初始化。您也可以在类定义内部初始化静态变量。
以下示例演示了 静态变量的用法 -
using System; namespace StaticVarApplication { class StaticVar { public static int num; public void count() { num++; } public int getNum() { return num; } } class StaticTester { static void Main(string[] args) { StaticVar s1 = new StaticVar(); StaticVar s2 = new StaticVar(); s1.count(); s1.count(); s1.count(); s2.count(); s2.count(); s2.count(); Console.WriteLine("Variable num for s1: {0}", s1.getNum()); Console.WriteLine("Variable num for s2: {0}", s2.getNum()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Variable num for s1: 6 Variable num for s2: 6
您还可以将成员函数声明为静态的。此类函数只能访问静态变量。静态函数甚至在创建对象之前就存在。以下示例演示了静态函数的用法 -
using System; namespace StaticVarApplication { class StaticVar { public static int num; public void count() { num++; } public static int getNum() { return num; } } class StaticTester { static void Main(string[] args) { StaticVar s = new StaticVar(); s.count(); s.count(); s.count(); Console.WriteLine("Variable num: {0}", StaticVar.getNum()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Variable num: 3
C# - 继承
面向对象编程中最重要概念之一是继承。继承允许我们根据另一个类定义类,这使得创建和维护应用程序变得更容易。这也提供了重用代码功能的机会,并加快了实施时间。
在创建类时,程序员可以指定新类应该继承现有类的成员,而不是编写全新的数据成员和成员函数。此现有类称为基类,新类称为派生类。
继承的概念实现了IS-A关系。例如,哺乳动物IS A动物,狗IS-A哺乳动物,因此狗IS-A动物,依此类推。
基类和派生类
一个类可以从多个类或接口派生,这意味着它可以继承多个基类或接口的数据和函数。
C# 中用于创建派生类的语法如下 -
<acess-specifier> class <base_class> { ... } class <derived_class> : <base_class> { ... }
考虑一个基类 Shape 及其派生类 Rectangle -
using System; namespace InheritanceApplication { class Shape { public void setWidth(int w) { width = w; } public void setHeight(int h) { height = h; } protected int width; protected int height; } // Derived class class Rectangle: Shape { public int getArea() { return (width * height); } } class RectangleTester { static void Main(string[] args) { Rectangle Rect = new Rectangle(); Rect.setWidth(5); Rect.setHeight(7); // Print the area of the object. Console.WriteLine("Total area: {0}", Rect.getArea()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Total area: 35
初始化基类
派生类继承基类的成员变量和成员方法。因此,在创建子类之前,应先创建超类对象。您可以在成员初始化列表中提供超类初始化的指令。
以下程序演示了这一点 -
using System; namespace RectangleApplication { class Rectangle { //member variables protected double length; protected double width; public Rectangle(double l, double w) { length = l; width = w; } public double GetArea() { return length * width; } public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }//end class Rectangle class Tabletop : Rectangle { private double cost; public Tabletop(double l, double w) : base(l, w) { } public double GetCost() { double cost; cost = GetArea() * 70; return cost; } public void Display() { base.Display(); Console.WriteLine("Cost: {0}", GetCost()); } } class ExecuteRectangle { static void Main(string[] args) { Tabletop t = new Tabletop(4.5, 7.5); t.Display(); Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Length: 4.5 Width: 7.5 Area: 33.75 Cost: 2362.5
C# 中的多重继承
C# 不支持多重继承。但是,您可以使用接口来实现多重继承。以下程序演示了这一点 -
using System; namespace InheritanceApplication { class Shape { public void setWidth(int w) { width = w; } public void setHeight(int h) { height = h; } protected int width; protected int height; } // Base class PaintCost public interface PaintCost { int getCost(int area); } // Derived class class Rectangle : Shape, PaintCost { public int getArea() { return (width * height); } public int getCost(int area) { return area * 70; } } class RectangleTester { static void Main(string[] args) { Rectangle Rect = new Rectangle(); int area; Rect.setWidth(5); Rect.setHeight(7); area = Rect.getArea(); // Print the area of the object. Console.WriteLine("Total area: {0}", Rect.getArea()); Console.WriteLine("Total paint cost: ${0}" , Rect.getCost(area)); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Total area: 35 Total paint cost: $2450
C# - 多态
术语多态性表示具有多种形式。在面向对象编程范式中,多态性通常表示为“一个接口,多个函数”。
多态性可以是静态的或动态的。在静态多态性中,对函数的响应是在编译时确定的。在动态多态性中,它是在运行时确定的。
静态多态性
在编译期间将函数与对象链接的机制称为早期绑定。它也称为静态绑定。C# 提供了两种实现静态多态性的技术。它们是 -
- 函数重载
- 运算符重载
我们将在下一章中讨论运算符重载。
函数重载
您可以在同一作用域中拥有多个相同函数名称的定义。函数的定义必须通过参数列表中参数的类型和/或数量彼此不同。您不能重载仅返回值类型不同的函数声明。
以下示例展示了使用函数 print() 打印不同数据类型 -
using System; namespace PolymorphismApplication { class Printdata { void print(int i) { Console.WriteLine("Printing int: {0}", i ); } void print(double f) { Console.WriteLine("Printing float: {0}" , f); } void print(string s) { Console.WriteLine("Printing string: {0}", s); } static void Main(string[] args) { Printdata p = new Printdata(); // Call print to print integer p.print(5); // Call print to print float p.print(500.263); // Call print to print string p.print("Hello C++"); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Printing int: 5 Printing float: 500.263 Printing string: Hello C++
动态多态性
C# 允许您创建抽象类,这些类用于提供接口的部分类实现。当派生类从它继承时,实现就完成了。抽象类包含抽象方法,这些方法由派生类实现。派生类具有更专业的函数。
以下是关于抽象类的规则 -
您不能创建抽象类的实例
您不能在抽象类之外声明抽象方法
当类被声明为 sealed 时,它不能被继承,抽象类不能被声明为 sealed。
以下程序演示了一个抽象类 -
using System; namespace PolymorphismApplication { abstract class Shape { public abstract int area(); } class Rectangle: Shape { private int length; private int width; public Rectangle( int a = 0, int b = 0) { length = a; width = b; } public override int area () { Console.WriteLine("Rectangle class area :"); return (width * length); } } class RectangleTester { static void Main(string[] args) { Rectangle r = new Rectangle(10, 7); double a = r.area(); Console.WriteLine("Area: {0}",a); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Rectangle class area : Area: 70
当您在类中定义了一个要在继承类中实现的函数时,您使用虚函数。虚函数可以在不同的继承类中以不同的方式实现,对这些函数的调用将在运行时决定。
动态多态性由抽象类和虚函数实现。
以下程序演示了这一点 -
using System; namespace PolymorphismApplication { class Shape { protected int width, height; public Shape( int a = 0, int b = 0) { width = a; height = b; } public virtual int area() { Console.WriteLine("Parent class area :"); return 0; } } class Rectangle: Shape { public Rectangle( int a = 0, int b = 0): base(a, b) { } public override int area () { Console.WriteLine("Rectangle class area :"); return (width * height); } } class Triangle: Shape { public Triangle(int a = 0, int b = 0): base(a, b) { } public override int area() { Console.WriteLine("Triangle class area :"); return (width * height / 2); } } class Caller { public void CallArea(Shape sh) { int a; a = sh.area(); Console.WriteLine("Area: {0}", a); } } class Tester { static void Main(string[] args) { Caller c = new Caller(); Rectangle r = new Rectangle(10, 7); Triangle t = new Triangle(10, 5); c.CallArea(r); c.CallArea(t); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Rectangle class area: Area: 70 Triangle class area: Area: 25
C# - 运算符重载
您可以重新定义或重载 C# 中提供的大多数内置运算符。因此,程序员也可以将运算符与用户定义类型一起使用。重载的运算符是具有特殊名称的函数,关键字 operator 后跟要定义的运算符的符号。与任何其他函数类似,重载的运算符具有返回类型和参数列表。
例如,请查看以下函数 -
public static Box operator+ (Box b, Box c) { Box box = new Box(); box.length = b.length + c.length; box.breadth = b.breadth + c.breadth; box.height = b.height + c.height; return box; }
上述函数为用户定义的类 Box 实现加法运算符 (+) 。它添加两个 Box 对象的属性并返回结果 Box 对象。
实现运算符重载
以下程序展示了完整的实现 -
using System; namespace OperatorOvlApplication { class Box { private double length; // Length of a box private double breadth; // Breadth of a box private double height; // Height of a box public double getVolume() { return length * breadth * height; } public void setLength( double len ) { length = len; } public void setBreadth( double bre ) { breadth = bre; } public void setHeight( double hei ) { height = hei; } // Overload + operator to add two Box objects. public static Box operator+ (Box b, Box c) { Box box = new Box(); box.length = b.length + c.length; box.breadth = b.breadth + c.breadth; box.height = b.height + c.height; return box; } } class Tester { static void Main(string[] args) { Box Box1 = new Box(); // Declare Box1 of type Box Box Box2 = new Box(); // Declare Box2 of type Box Box Box3 = new Box(); // Declare Box3 of type Box double volume = 0.0; // Store the volume of a box here // box 1 specification Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); // box 2 specification Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0); // volume of box 1 volume = Box1.getVolume(); Console.WriteLine("Volume of Box1 : {0}", volume); // volume of box 2 volume = Box2.getVolume(); Console.WriteLine("Volume of Box2 : {0}", volume); // Add two object as follows: Box3 = Box1 + Box2; // volume of box 3 volume = Box3.getVolume(); Console.WriteLine("Volume of Box3 : {0}", volume); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Volume of Box1 : 210 Volume of Box2 : 1560 Volume of Box3 : 5400
可重载和不可重载运算符
下表描述了 C# 中运算符的可重载性 -
序号 | 运算符 & 描述 |
---|---|
1 | +, -, !, ~, ++, -- 这些一元运算符接受一个操作数,并且可以重载。 |
2 | +, -, *, /, % 这些二元运算符接受一个操作数,并且可以重载。 |
3 | ==, !=, <, >, <=, >= 比较运算符可以重载。 |
4 | &&, || 条件逻辑运算符不能直接重载。 |
5 | +=, -=, *=, /=, %= 赋值运算符不能重载。 |
6 | =, ., ?:, ->, new, is, sizeof, typeof 这些运算符不能重载。 |
示例
根据以上讨论,让我们扩展前面的示例,并重载更多运算符 -
using System; namespace OperatorOvlApplication { class Box { private double length; // Length of a box private double breadth; // Breadth of a box private double height; // Height of a box public double getVolume() { return length * breadth * height; } public void setLength( double len ) { length = len; } public void setBreadth( double bre ) { breadth = bre; } public void setHeight( double hei ) { height = hei; } // Overload + operator to add two Box objects. public static Box operator+ (Box b, Box c) { Box box = new Box(); box.length = b.length + c.length; box.breadth = b.breadth + c.breadth; box.height = b.height + c.height; return box; } public static bool operator == (Box lhs, Box rhs) { bool status = false; if (lhs.length == rhs.length && lhs.height == rhs.height && lhs.breadth == rhs.breadth) { status = true; } return status; } public static bool operator !=(Box lhs, Box rhs) { bool status = false; if (lhs.length != rhs.length || lhs.height != rhs.height || lhs.breadth != rhs.breadth) { status = true; } return status; } public static bool operator <(Box lhs, Box rhs) { bool status = false; if (lhs.length < rhs.length && lhs.height < rhs.height && lhs.breadth < rhs.breadth) { status = true; } return status; } public static bool operator >(Box lhs, Box rhs) { bool status = false; if (lhs.length > rhs.length && lhs.height > rhs.height && lhs.breadth > rhs.breadth) { status = true; } return status; } public static bool operator <=(Box lhs, Box rhs) { bool status = false; if (lhs.length <= rhs.length && lhs.height <= rhs.height && lhs.breadth <= rhs.breadth) { status = true; } return status; } public static bool operator >=(Box lhs, Box rhs) { bool status = false; if (lhs.length >= rhs.length && lhs.height >= rhs.height && lhs.breadth >= rhs.breadth) { status = true; } return status; } public override string ToString() { return String.Format("({0}, {1}, {2})", length, breadth, height); } } class Tester { static void Main(string[] args) { Box Box1 = new Box(); // Declare Box1 of type Box Box Box2 = new Box(); // Declare Box2 of type Box Box Box3 = new Box(); // Declare Box3 of type Box Box Box4 = new Box(); double volume = 0.0; // Store the volume of a box here // box 1 specification Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); // box 2 specification Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0); //displaying the Boxes using the overloaded ToString(): Console.WriteLine("Box 1: {0}", Box1.ToString()); Console.WriteLine("Box 2: {0}", Box2.ToString()); // volume of box 1 volume = Box1.getVolume(); Console.WriteLine("Volume of Box1 : {0}", volume); // volume of box 2 volume = Box2.getVolume(); Console.WriteLine("Volume of Box2 : {0}", volume); // Add two object as follows: Box3 = Box1 + Box2; Console.WriteLine("Box 3: {0}", Box3.ToString()); // volume of box 3 volume = Box3.getVolume(); Console.WriteLine("Volume of Box3 : {0}", volume); //comparing the boxes if (Box1 > Box2) Console.WriteLine("Box1 is greater than Box2"); else Console.WriteLine("Box1 is greater than Box2"); if (Box1 < Box2) Console.WriteLine("Box1 is less than Box2"); else Console.WriteLine("Box1 is not less than Box2"); if (Box1 >= Box2) Console.WriteLine("Box1 is greater or equal to Box2"); else Console.WriteLine("Box1 is not greater or equal to Box2"); if (Box1 <= Box2) Console.WriteLine("Box1 is less or equal to Box2"); else Console.WriteLine("Box1 is not less or equal to Box2"); if (Box1 != Box2) Console.WriteLine("Box1 is not equal to Box2"); else Console.WriteLine("Box1 is not greater or equal to Box2"); Box4 = Box3; if (Box3 == Box4) Console.WriteLine("Box3 is equal to Box4"); else Console.WriteLine("Box3 is not equal to Box4"); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Box 1: (6, 7, 5) Box 2: (12, 13, 10) Volume of Box1 : 210 Volume of Box2 : 1560 Box 3: (18, 20, 15) Volume of Box3 : 5400 Box1 is not greater than Box2 Box1 is less than Box2 Box1 is not greater or equal to Box2 Box1 is less or equal to Box2 Box1 is not equal to Box2 Box3 is equal to Box4
C# - 接口
接口被定义为所有继承该接口的类都应该遵循的语法契约。接口定义了语法契约的“是什么”部分,而派生类定义了语法契约的“如何”部分。
接口定义属性、方法和事件,这些是接口的成员。接口仅包含成员的声明。派生类负责定义这些成员。它通常有助于提供派生类将遵循的标准结构。
抽象类在某种程度上具有相同的目的,但是,当基类只需要声明少量方法,而派生类实现功能时,它们主要用于这种情况。
声明接口
接口使用 interface 关键字声明。它类似于类声明。接口语句默认情况下是公开的。以下是一个接口声明的示例 -
public interface ITransactions { // interface members void showTransaction(); double getAmount(); }
示例
以下示例演示了上述接口的实现 -
using System.Collections.Generic; using System.Linq; using System.Text; using System; namespace InterfaceApplication { public interface ITransactions { // interface members void showTransaction(); double getAmount(); } public class Transaction : ITransactions { private string tCode; private string date; private double amount; public Transaction() { tCode = " "; date = " "; amount = 0.0; } public Transaction(string c, string d, double a) { tCode = c; date = d; amount = a; } public double getAmount() { return amount; } public void showTransaction() { Console.WriteLine("Transaction: {0}", tCode); Console.WriteLine("Date: {0}", date); Console.WriteLine("Amount: {0}", getAmount()); } } class Tester { static void Main(string[] args) { Transaction t1 = new Transaction("001", "8/10/2012", 78900.00); Transaction t2 = new Transaction("002", "9/10/2012", 451900.00); t1.showTransaction(); t2.showTransaction(); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Transaction: 001 Date: 8/10/2012 Amount: 78900 Transaction: 002 Date: 9/10/2012 Amount: 451900
C# - 命名空间
命名空间旨在提供一种方法来使一组名称与另一组名称分开。在一个命名空间中声明的类名不会与另一个命名空间中声明的相同类名冲突。
定义命名空间
命名空间定义以关键字namespace开头,后跟命名空间名称,如下所示 -
namespace namespace_name { // code declarations }
要调用函数或变量的启用命名空间的版本,请在前面加上命名空间名称,如下所示 -
namespace_name.item_name;
以下程序演示了命名空间的使用 -
using System; namespace first_space { class namespace_cl { public void func() { Console.WriteLine("Inside first_space"); } } } namespace second_space { class namespace_cl { public void func() { Console.WriteLine("Inside second_space"); } } } class TestClass { static void Main(string[] args) { first_space.namespace_cl fc = new first_space.namespace_cl(); second_space.namespace_cl sc = new second_space.namespace_cl(); fc.func(); sc.func(); Console.ReadKey(); } }
当以上代码编译并执行时,会产生以下结果:
Inside first_space Inside second_space
using关键字
using关键字表示程序正在使用给定命名空间中的名称。例如,我们在程序中使用了System命名空间。类 Console 在那里定义。我们只需编写 -
Console.WriteLine ("Hello there");
我们也可以写出完全限定的名称,如下所示 -
System.Console.WriteLine("Hello there");
您还可以使用using命名空间指令避免在命名空间前加上前缀。此指令告诉编译器后续代码正在使用指定命名空间中的名称。因此,命名空间对以下代码隐含有效 -
让我们使用 using 指令重写前面的示例 -
using System; using first_space; using second_space; namespace first_space { class abc { public void func() { Console.WriteLine("Inside first_space"); } } } namespace second_space { class efg { public void func() { Console.WriteLine("Inside second_space"); } } } class TestClass { static void Main(string[] args) { abc fc = new abc(); efg sc = new efg(); fc.func(); sc.func(); Console.ReadKey(); } }
当以上代码编译并执行时,会产生以下结果:
Inside first_space Inside second_space
嵌套命名空间
您可以按如下方式在一个命名空间内定义另一个命名空间 -
namespace namespace_name1 { // code declarations namespace namespace_name2 { // code declarations } }
您可以使用点 (.) 运算符访问嵌套命名空间的成员,如下所示 -
using System; using first_space; using first_space.second_space; namespace first_space { class abc { public void func() { Console.WriteLine("Inside first_space"); } } namespace second_space { class efg { public void func() { Console.WriteLine("Inside second_space"); } } } } class TestClass { static void Main(string[] args) { abc fc = new abc(); efg sc = new efg(); fc.func(); sc.func(); Console.ReadKey(); } }
当以上代码编译并执行时,会产生以下结果:
Inside first_space Inside second_space
C# - 预处理器指令
预处理器指令指示编译器在实际编译开始之前预处理信息。
所有预处理器指令都以 # 开头,并且在一行上预处理器指令之前只能出现空白字符。预处理器指令不是语句,因此它们不会以分号 (;) 结尾。
C# 编译器没有单独的预处理器;但是,这些指令被处理得好像有一个一样。在 C# 中,预处理器指令用于帮助进行条件编译。与 C 和 C++ 指令不同,它们不用于创建宏。预处理器指令必须是行上唯一的指令。
C# 中的预处理器指令
下表列出了 C# 中可用的预处理器指令 -
序号 | 预处理器指令 & 描述 |
---|---|
1 | #define 它定义一个字符序列,称为符号。 |
2 | #undef 它允许您取消定义符号。 |
3 | #if 它允许测试符号或符号以查看它们是否评估为真。 |
4 | #else 它允许与 #if 一起创建复合条件指令。 |
5 | #elif 它允许创建复合条件指令。 |
6 | #endif 指定条件指令的结束。 |
7 | #line 它允许您修改编译器的行号和(可选)错误和警告的输出文件名。 |
8 | #error 它允许从代码中的特定位置生成错误。 |
9 | #warning 它允许从代码中的特定位置生成一级警告。 |
10 | #region 它允许您指定代码块,当使用 Visual Studio 代码编辑器的概述功能时,您可以展开或折叠该代码块。 |
11 | #endregion 它标记 #region 块的结束。 |
#define 预处理器
#define 预处理器指令创建符号常量。
#define 允许您定义一个符号,以便通过使用该符号作为传递给 #if 指令的表达式,该表达式评估为真。其语法如下 -
#define symbol
以下程序说明了这一点:
#define PI using System; namespace PreprocessorDAppl { class Program { static void Main(string[] args) { #if (PI) Console.WriteLine("PI is defined"); #else Console.WriteLine("PI is not defined"); #endif Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
PI is defined
条件指令
您可以使用 #if 指令创建条件指令。条件指令对于测试符号或符号以检查它们是否评估为真很有用。如果它们确实评估为真,则编译器会评估 #if 和下一个指令之间的所有代码。
条件指令的语法为 -
#if symbol [operator symbol]...
其中,symbol 是要测试的符号的名称。您还可以使用 true 和 false 或在符号前加上否定运算符。
operator symbol 是用于评估符号的运算符。运算符可以是以下任何一个 -
- == (相等)
- != (不相等)
- && (与)
- || (或)
您还可以使用括号对符号和运算符进行分组。条件指令用于为调试版本编译代码或在为特定配置编译时使用。以#if指令开头的条件指令必须显式地以#endif指令结束。
以下程序演示了条件指令的使用 -
#define DEBUG #define VC_V10 using System; public class TestClass { public static void Main() { #if (DEBUG && !VC_V10) Console.WriteLine("DEBUG is defined"); #elif (!DEBUG && VC_V10) Console.WriteLine("VC_V10 is defined"); #elif (DEBUG && VC_V10) Console.WriteLine("DEBUG and VC_V10 are defined"); #else Console.WriteLine("DEBUG and VC_V10 are not defined"); #endif Console.ReadKey(); } }
当以上代码编译并执行时,会产生以下结果:
DEBUG and VC_V10 are defined
C# - 正则表达式
正则表达式是一种可以与输入文本匹配的模式。.Net 框架提供了一个正则表达式引擎,允许进行这种匹配。模式由一个或多个字符文字、运算符或结构组成。
定义正则表达式的结构
有各种类别的字符、运算符和结构可以让您定义正则表达式。点击以下链接查找这些结构。
Regex 类
Regex 类用于表示正则表达式。它具有以下常用方法 -
序号 | 方法和描述 |
---|---|
1 | public bool IsMatch(string input) 指示 Regex 构造函数中指定的正则表达式是否在指定的输入字符串中找到匹配项。 |
2 | public bool IsMatch(string input, int startat) 指示 Regex 构造函数中指定的正则表达式是否在指定的输入字符串中找到匹配项,从字符串中的指定起始位置开始。 |
3 | public static bool IsMatch(string input, string pattern) 指示指定的正则表达式是否在指定的输入字符串中找到匹配项。 |
4 | public MatchCollection Matches(string input) 在指定的输入字符串中搜索正则表达式的所有出现。 |
5 | public string Replace(string input, string replacement) 在指定的输入字符串中,将与正则表达式模式匹配的所有字符串替换为指定的替换字符串。 |
6 | public string[] Split(string input) 根据 Regex 构造函数中指定的正则表达式模式定义的位置,将输入字符串拆分为子字符串数组。 |
有关方法和属性的完整列表,请阅读有关 C# 的 Microsoft 文档。
示例 1
以下示例匹配以“S”开头的单词 -
using System; using System.Text.RegularExpressions; namespace RegExApplication { class Program { private static void showMatch(string text, string expr) { Console.WriteLine("The Expression: " + expr); MatchCollection mc = Regex.Matches(text, expr); foreach (Match m in mc) { Console.WriteLine(m); } } static void Main(string[] args) { string str = "A Thousand Splendid Suns"; Console.WriteLine("Matching words that start with 'S': "); showMatch(str, @"\bS\S*"); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Matching words that start with 'S': The Expression: \bS\S* Splendid Suns
示例 2
以下示例匹配以“m”开头并以“e”结尾的单词 -
using System; using System.Text.RegularExpressions; namespace RegExApplication { class Program { private static void showMatch(string text, string expr) { Console.WriteLine("The Expression: " + expr); MatchCollection mc = Regex.Matches(text, expr); foreach (Match m in mc) { Console.WriteLine(m); } } static void Main(string[] args) { string str = "make maze and manage to measure it"; Console.WriteLine("Matching words start with 'm' and ends with 'e':"); showMatch(str, @"\bm\S*e\b"); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Matching words start with 'm' and ends with 'e': The Expression: \bm\S*e\b make maze manage measure
示例 3
此示例替换额外的空格 -
using System; using System.Text.RegularExpressions; namespace RegExApplication { class Program { static void Main(string[] args) { string input = "Hello World "; string pattern = "\\s+"; string replacement = " "; Regex rgx = new Regex(pattern); string result = rgx.Replace(input, replacement); Console.WriteLine("Original String: {0}", input); Console.WriteLine("Replacement String: {0}", result); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Original String: Hello World Replacement String: Hello World
C# - 异常处理
异常是在程序执行期间出现的错误。C# 异常是对程序运行期间出现的异常情况的响应,例如尝试除以零。
异常提供了一种将控制权从程序的一部分转移到另一部分的方法。C# 异常处理建立在四个关键字之上:try、catch、finally和throw。
try − try 块标识一个代码块,为此激活特定异常。它后面跟着一个或多个 catch 块。
catch − 程序在程序中要处理问题的某个位置使用异常处理程序捕获异常。catch 关键字表示捕获异常。
finally − finally 块用于执行给定的语句集,无论是否抛出异常。例如,如果您打开一个文件,无论是否引发异常,都必须关闭它。
throw − 当出现问题时,程序会抛出异常。这是使用 throw 关键字完成的。
语法
假设一个块引发了异常,则方法使用 try 和 catch 关键字的组合捕获异常。try/catch 块放置在可能生成异常的代码周围。try/catch 块内的代码称为受保护代码,使用 try/catch 的语法如下 -
try { // statements causing exception } catch( ExceptionName e1 ) { // error handling code } catch( ExceptionName e2 ) { // error handling code } catch( ExceptionName eN ) { // error handling code } finally { // statements to be executed }
您可以列出多个 catch 语句以捕获不同类型的异常,以防您的 try 块在不同情况下引发多个异常。
C# 中的异常类
C# 异常由类表示。C# 中的异常类主要直接或间接地派生自System.Exception类。一些从 System.Exception 类派生的异常类是System.ApplicationException和 System.SystemException类。
System.ApplicationException类支持应用程序程序生成的异常。因此,程序员定义的异常应该从此类派生。
System.SystemException类是所有预定义系统异常的基类。
下表提供了一些从 Sytem.SystemException 类派生的预定义异常类 -
序号 | 异常类 & 描述 |
---|---|
1 | System.IO.IOException 处理 I/O 错误。 |
2 | System.IndexOutOfRangeException 处理方法引用超出范围的数组索引时生成的错误。 |
3 | System.ArrayTypeMismatchException 处理类型与数组类型不匹配时生成的错误。 |
4 | System.NullReferenceException 处理引用空对象时生成的错误。 |
5 | System.DivideByZeroException 处理除数为零导致的错误。 |
6 | System.InvalidCastException 处理类型转换期间产生的错误。 |
7 | System.OutOfMemoryException 处理由于可用内存不足而产生的错误。 |
8 | System.StackOverflowException 处理栈溢出产生的错误。 |
异常处理
C# 提供了一种结构化的异常处理解决方案,即使用 try 和 catch 代码块。使用这些代码块,可以将核心程序语句与错误处理语句分开。
这些错误处理块使用 **try**、**catch** 和 **finally** 关键字实现。以下是在除以零条件发生时抛出异常的示例:
using System; namespace ErrorHandlingApplication { class DivNumbers { int result; DivNumbers() { result = 0; } public void division(int num1, int num2) { try { result = num1 / num2; } catch (DivideByZeroException e) { Console.WriteLine("Exception caught: {0}", e); } finally { Console.WriteLine("Result: {0}", result); } } static void Main(string[] args) { DivNumbers d = new DivNumbers(); d.division(25, 0); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Exception caught: System.DivideByZeroException: Attempted to divide by zero. at ... Result: 0
创建用户定义的异常
您也可以定义自己的异常。用户定义的异常类是从 **Exception** 类派生的。以下示例演示了这一点:
using System; namespace UserDefinedException { class TestTemperature { static void Main(string[] args) { Temperature temp = new Temperature(); try { temp.showTemp(); } catch(TempIsZeroException e) { Console.WriteLine("TempIsZeroException: {0}", e.Message); } Console.ReadKey(); } } } public class TempIsZeroException: Exception { public TempIsZeroException(string message): base(message) { } } public class Temperature { int temperature = 0; public void showTemp() { if(temperature == 0) { throw (new TempIsZeroException("Zero Temperature found")); } else { Console.WriteLine("Temperature: {0}", temperature); } } }
当以上代码编译并执行时,会产生以下结果:
TempIsZeroException: Zero Temperature found
抛出对象
如果对象直接或间接从 **System.Exception** 类派生,则可以抛出该对象。您可以在 catch 代码块中使用 throw 语句抛出当前对象,如下所示:
Catch(Exception e) { ... Throw e }
C# - 文件 I/O
**文件**是存储在磁盘上的一组数据,具有特定的名称和目录路径。当文件被打开以进行读取或写入时,它就变成了一个**流**。
流基本上是通过通信路径传递的字节序列。主要有两种流:**输入流**和**输出流**。**输入流**用于从文件读取数据(读取操作),**输出流**用于向文件写入数据(写入操作)。
C# I/O 类
System.IO 命名空间包含各种用于执行文件操作的类,例如创建和删除文件、读取或写入文件、关闭文件等。
下表显示了 System.IO 命名空间中一些常用的非抽象类:
序号 | I/O 类 & 描述 |
---|---|
1 | BinaryReader 从二进制流中读取原始数据。 |
2 | BinaryWriter 以二进制格式写入原始数据。 |
3 | BufferedStream 字节流的临时存储。 |
4 | Directory 帮助操作目录结构。 |
5 | DirectoryInfo 用于执行目录操作。 |
6 | DriveInfo 提供驱动器信息。 |
7 | File 帮助操作文件。 |
8 | FileInfo 用于执行文件操作。 |
9 | FileStream 用于读取和写入文件中的任何位置。 |
10 | MemoryStream 用于对存储在内存中的流式数据进行随机访问。 |
11 | Path 执行路径信息操作。 |
12 | StreamReader 用于从字节流中读取字符。 |
13 | StreamWriter 用于向流中写入字符。 |
14 | StringReader 用于从字符串缓冲区读取。 |
15 | StringWriter 用于向字符串缓冲区写入。 |
FileStream 类
System.IO 命名空间中的 **FileStream** 类有助于读取、写入和关闭文件。此类派生自抽象类 Stream。
您需要创建一个 **FileStream** 对象来创建新文件或打开现有文件。创建 **FileStream** 对象的语法如下所示:
FileStream <object_name> = new FileStream( <file_name>, <FileMode Enumerator>, <FileAccess Enumerator>, <FileShare Enumerator>);
例如,我们创建了一个 FileStream 对象 **F** 用于读取名为 **sample.txt 的文件,如下所示:**
FileStream F = new FileStream("sample.txt", FileMode.Open, FileAccess.Read, FileShare.Read);
序号 | 参数 & 描述 |
---|---|
1 | FileMode **FileMode** 枚举定义了打开文件的各种方法。FileMode 枚举的成员有:
|
2 | FileAccess **FileAccess** 枚举具有成员:**Read**、**ReadWrite** 和 **Write**。 |
3 | FileShare **FileShare** 枚举具有以下成员:
|
示例
以下程序演示了 **FileStream** 类的用法:
using System; using System.IO; namespace FileIOApplication { class Program { static void Main(string[] args) { FileStream F = new FileStream("test.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite); for (int i = 1; i <= 20; i++) { F.WriteByte((byte)i); } F.Position = 0; for (int i = 0; i <= 20; i++) { Console.Write(F.ReadByte() + " "); } F.Close(); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -1
C# 中的高级文件操作
前面的示例提供了 C# 中简单的文件操作。但是,要利用 C# System.IO 类的强大功能,您需要了解这些类的常用属性和方法。
序号 | 主题 & 描述 |
---|---|
1 | 读取和写入文本文件
它涉及读取和写入文本文件。**StreamReader** 和 **StreamWriter** 类有助于完成此操作。 |
2 | 读取和写入二进制文件
它涉及读取和写入二进制文件。**BinaryReader** 和 **BinaryWriter** 类有助于完成此操作。 |
3 | 操作 Windows 文件系统
它使 C# 程序员能够浏览和定位 Windows 文件和目录。 |
C# - 属性
**特性**是一个声明性标签,用于向运行时传达有关程序中各种元素(如类、方法、结构、枚举、程序集等)行为的信息。您可以通过使用特性向程序添加声明性信息。声明性标签由放置在它所用元素上方的方括号 ([ ]) 表示。
特性用于向程序添加元数据,例如编译器指令和其他信息,如注释、描述、方法和类。.Net Framework 提供两种类型的特性:*预定义*特性和*自定义构建*特性。
指定特性
指定特性的语法如下所示:
[attribute(positional_parameters, name_parameter = value, ...)] element
特性名称及其值在方括号内指定,位于应用特性的元素之前。位置参数指定基本信息,名称参数指定可选信息。
预定义特性
.Net Framework 提供三个预定义特性:
- AttributeUsage
- 条件
- Obsolete
AttributeUsage
预定义特性 **AttributeUsage** 描述了如何使用自定义特性类。它指定了可以应用特性的项目类型。
指定此特性的语法如下所示:
[AttributeUsage ( validon, AllowMultiple = allowmultiple, Inherited = inherited )]
其中,
validon 参数指定可以在其上放置特性的语言元素。它是枚举 *AttributeTargets* 值的组合。默认值为 *AttributeTargets.All*。
allowmultiple 参数(可选)为该特性的 *AllowMultiple* 属性提供值,一个布尔值。如果为 true,则该特性是多用途的。默认为 false(单用途)。
inherited 参数(可选)为该特性的 *Inherited* 属性提供值,一个布尔值。如果为 true,则该特性会被派生类继承。默认值为 false(未继承)。
例如,
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
条件
此预定义特性标记一个条件方法,其执行取决于指定的预处理标识符。
它根据指定的值(如 **Debug** 或 **Trace**)导致条件编译方法调用。例如,它在调试代码时显示变量的值。
指定此特性的语法如下所示:
[Conditional( conditionalSymbol )]
例如,
[Conditional("DEBUG")]
以下示例演示了该特性:
#define DEBUG using System; using System.Diagnostics; public class Myclass { [Conditional("DEBUG")] public static void Message(string msg) { Console.WriteLine(msg); } } class Test { static void function1() { Myclass.Message("In Function 1."); function2(); } static void function2() { Myclass.Message("In Function 2."); } public static void Main() { Myclass.Message("In Main function."); function1(); Console.ReadKey(); } }
当以上代码编译并执行时,会产生以下结果:
In Main function In Function 1 In Function 2
Obsolete
此预定义特性标记不应使用的程序实体。它使您能够通知编译器丢弃特定的目标元素。例如,当在类中使用新方法时,如果仍然希望在类中保留旧方法,则可以通过显示一条消息来将其标记为已过时,该消息应使用新方法而不是旧方法。
指定此特性的语法如下所示:
[Obsolete ( message )] [Obsolete ( message, iserror )]
其中,
message 参数是一个字符串,描述该项目已过时的原因以及应使用什么代替。
iserror 参数是一个布尔值。如果其值为 true,则编译器应将项目的用法视为错误。默认值为 false(编译器生成警告)。
以下程序演示了这一点 -
using System; public class MyClass { [Obsolete("Don't use OldMethod, use NewMethod instead", true)] static void OldMethod() { Console.WriteLine("It is the old method"); } static void NewMethod() { Console.WriteLine("It is the new method"); } public static void Main() { OldMethod(); } }
当您尝试编译程序时,编译器会给出错误消息,指出:
Don't use OldMethod, use NewMethod instead
创建自定义特性
.Net Framework 允许创建自定义特性,这些特性可用于存储声明性信息,并在运行时检索。此信息可以与任何目标元素相关,具体取决于设计标准和应用程序需求。
创建和使用自定义特性涉及四个步骤:
- 声明自定义特性
- 构造自定义特性
- 在目标程序元素上应用自定义特性
- 通过反射访问特性
最后一步涉及编写一个简单的程序来读取元数据以查找各种注释。元数据是关于数据的数据或用于描述其他数据的信息。此程序应使用反射在运行时访问特性。我们将在下一章中讨论这一点。
声明自定义特性
新的自定义特性应从 **System.Attribute** 类派生。例如,
//a custom attribute BugFix to be assigned to a class and its members [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class DeBugInfo : System.Attribute
在前面的代码中,我们声明了一个名为 *DeBugInfo* 的自定义特性。
构造自定义特性
让我们构造一个名为 *DeBugInfo* 的自定义特性,它存储调试任何程序获得的信息。让它存储以下信息:
- 错误的代码编号
- 识别错误的开发人员的姓名
- 代码上次审查的日期
- 用于存储开发人员备注的字符串消息
DeBugInfo 类有三个私有属性用于存储前三个信息,以及一个公有属性用于存储消息。因此,错误编号、开发人员姓名和审查日期是 DeBugInfo 类的位置参数,而消息是可选参数或命名参数。
每个属性都必须至少有一个构造函数。位置参数应该通过构造函数传递。以下代码显示了 DeBugInfo 类 -
//a custom attribute BugFix to be assigned to a class and its members [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class DeBugInfo : System.Attribute { private int bugNo; private string developer; private string lastReview; public string message; public DeBugInfo(int bg, string dev, string d) { this.bugNo = bg; this.developer = dev; this.lastReview = d; } public int BugNo { get { return bugNo; } } public string Developer { get { return developer; } } public string LastReview { get { return lastReview; } } public string Message { get { return message; } set { message = value; } } }
应用自定义属性
通过将属性放置在其目标的正前方来应用它 -
[DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")] [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")] class Rectangle { //member variables protected double length; protected double width; public Rectangle(double l, double w) { length = l; width = w; } [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")] public double GetArea() { return length * width; } [DeBugInfo(56, "Zara Ali", "19/10/2012")] public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }
在下一章中,我们将使用反射类对象检索属性信息。
C# - 反射
反射对象用于在运行时获取类型信息。提供对正在运行的程序的元数据访问的类位于 System.Reflection 命名空间中。
System.Reflection 命名空间包含允许您获取有关应用程序的信息以及动态向应用程序添加类型、值和对象的类。
反射的应用
反射具有以下应用 -
它允许在运行时查看属性信息。
它允许检查程序集中各种类型并实例化这些类型。
它允许延迟绑定到方法和属性
它允许在运行时创建新类型,然后使用这些类型执行某些任务。
查看元数据
我们在上一章中提到,使用反射可以查看属性信息。
System.Reflection 类的 MemberInfo 对象需要初始化才能发现与类关联的属性。为此,您定义目标类的对象,如下所示 -
System.Reflection.MemberInfo info = typeof(MyClass);
以下程序演示了这一点 -
using System; [AttributeUsage(AttributeTargets.All)] public class HelpAttribute : System.Attribute { public readonly string Url; public string Topic // Topic is a named parameter { get { return topic; } set { topic = value; } } public HelpAttribute(string url) // url is a positional parameter { this.Url = url; } private string topic; } [HelpAttribute("Information on the class MyClass")] class MyClass { } namespace AttributeAppl { class Program { static void Main(string[] args) { System.Reflection.MemberInfo info = typeof(MyClass); object[] attributes = info.GetCustomAttributes(true); for (int i = 0; i < attributes.Length; i++) { System.Console.WriteLine(attributes[i]); } Console.ReadKey(); } } }
当它被编译并运行时,它将显示附加到 MyClass 类上的自定义属性的名称 -
HelpAttribute
示例
在这个例子中,我们使用了上一章创建的 DeBugInfo 属性,并使用反射来读取 Rectangle 类中的元数据。
using System; using System.Reflection; namespace BugFixApplication { //a custom attribute BugFix to be //assigned to a class and its members [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class DeBugInfo : System.Attribute { private int bugNo; private string developer; private string lastReview; public string message; public DeBugInfo(int bg, string dev, string d) { this.bugNo = bg; this.developer = dev; this.lastReview = d; } public int BugNo { get { return bugNo; } } public string Developer { get { return developer; } } public string LastReview { get { return lastReview; } } public string Message { get { return message; } set { message = value; } } } [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")] [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")] class Rectangle { //member variables protected double length; protected double width; public Rectangle(double l, double w) { length = l; width = w; } [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")] public double GetArea() { return length * width; } [DeBugInfo(56, "Zara Ali", "19/10/2012")] public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }//end class Rectangle class ExecuteRectangle { static void Main(string[] args) { Rectangle r = new Rectangle(4.5, 7.5); r.Display(); Type type = typeof(Rectangle); //iterating through the attribtues of the Rectangle class foreach (Object attributes in type.GetCustomAttributes(false)) { DeBugInfo dbi = (DeBugInfo)attributes; if (null != dbi) { Console.WriteLine("Bug no: {0}", dbi.BugNo); Console.WriteLine("Developer: {0}", dbi.Developer); Console.WriteLine("Last Reviewed: {0}", dbi.LastReview); Console.WriteLine("Remarks: {0}", dbi.Message); } } //iterating through the method attribtues foreach (MethodInfo m in type.GetMethods()) { foreach (Attribute a in m.GetCustomAttributes(true)) { DeBugInfo dbi = (DeBugInfo)a; if (null != dbi) { Console.WriteLine("Bug no: {0}, for Method: {1}", dbi.BugNo, m.Name); Console.WriteLine("Developer: {0}", dbi.Developer); Console.WriteLine("Last Reviewed: {0}", dbi.LastReview); Console.WriteLine("Remarks: {0}", dbi.Message); } } } Console.ReadLine(); } } }
当以上代码编译并执行时,会产生以下结果:
Length: 4.5 Width: 7.5 Area: 33.75 Bug No: 49 Developer: Nuha Ali Last Reviewed: 10/10/2012 Remarks: Unused variable Bug No: 45 Developer: Zara Ali Last Reviewed: 12/8/2012 Remarks: Return type mismatch Bug No: 55, for Method: GetArea Developer: Zara Ali Last Reviewed: 19/10/2012 Remarks: Return type mismatch Bug No: 56, for Method: Display Developer: Zara Ali Last Reviewed: 19/10/2012 Remarks:
C# - 属性
属性是类、结构和接口的命名成员。类或结构中的成员变量或方法称为字段。属性是字段的扩展,并使用相同的语法访问。它们使用访问器,通过这些访问器可以读取、写入或操作私有字段的值。
属性不命名存储位置。相反,它们具有访问器,用于读取、写入或计算其值。
例如,让我们有一个名为 Student 的类,其中包含年龄、姓名和代码的私有字段。我们不能直接从类范围之外访问这些字段,但我们可以使用属性来访问这些私有字段。
访问器
属性的访问器包含有助于获取(读取或计算)或设置(写入)属性的可执行语句。访问器声明可以包含 get 访问器、set 访问器或两者兼而有之。例如 -
// Declare a Code property of type string: public string Code { get { return code; } set { code = value; } } // Declare a Name property of type string: public string Name { get { return name; } set { name = value; } } // Declare a Age property of type int: public int Age { get { return age; } set { age = value; } }
示例
以下示例演示了属性的使用 -
using System; namespace tutorialspoint { class Student { private string code = "N.A"; private string name = "not known"; private int age = 0; // Declare a Code property of type string: public string Code { get { return code; } set { code = value; } } // Declare a Name property of type string: public string Name { get { return name; } set { name = value; } } // Declare a Age property of type int: public int Age { get { return age; } set { age = value; } } public override string ToString() { return "Code = " + Code +", Name = " + Name + ", Age = " + Age; } } class ExampleDemo { public static void Main() { // Create a new Student object: Student s = new Student(); // Setting code, name and the age of the student s.Code = "001"; s.Name = "Zara"; s.Age = 9; Console.WriteLine("Student Info: {0}", s); //let us increase age s.Age += 1; Console.WriteLine("Student Info: {0}", s); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Student Info: Code = 001, Name = Zara, Age = 9 Student Info: Code = 001, Name = Zara, Age = 10
抽象属性
抽象类可以具有抽象属性,该属性应在派生类中实现。以下程序说明了这一点 -
using System; namespace tutorialspoint { public abstract class Person { public abstract string Name { get; set; } public abstract int Age { get; set; } } class Student : Person { private string code = "N.A"; private string name = "N.A"; private int age = 0; // Declare a Code property of type string: public string Code { get { return code; } set { code = value; } } // Declare a Name property of type string: public override string Name { get { return name; } set { name = value; } } // Declare a Age property of type int: public override int Age { get { return age; } set { age = value; } } public override string ToString() { return "Code = " + Code +", Name = " + Name + ", Age = " + Age; } } class ExampleDemo { public static void Main() { // Create a new Student object: Student s = new Student(); // Setting code, name and the age of the student s.Code = "001"; s.Name = "Zara"; s.Age = 9; Console.WriteLine("Student Info:- {0}", s); //let us increase age s.Age += 1; Console.WriteLine("Student Info:- {0}", s); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Student Info: Code = 001, Name = Zara, Age = 9 Student Info: Code = 001, Name = Zara, Age = 10
C# - 索引器
索引器允许像数组一样对对象进行索引。当您为类定义索引器时,此类表现得类似于虚拟数组。然后,您可以使用数组访问运算符([ ])访问此类的实例。
语法
一维索引器具有以下语法 -
element-type this[int index] { // The get accessor. get { // return the value specified by index } // The set accessor. set { // set the value specified by index } }
索引器的使用
索引器行为的声明在某种程度上类似于属性。与属性类似,您使用get和set访问器来定义索引器。但是,属性返回或设置特定的数据成员,而索引器返回或设置对象实例中的特定值。换句话说,它将实例数据分解成更小的部分并为每个部分编制索引,获取或设置每个部分。
定义属性涉及提供属性名称。索引器不用名称定义,而是使用this关键字,该关键字引用对象实例。以下示例演示了此概念 -
using System; namespace IndexerApplication { class IndexedNames { private string[] namelist = new string[size]; static public int size = 10; public IndexedNames() { for (int i = 0; i < size; i++) namelist[i] = "N. A."; } public string this[int index] { get { string tmp; if( index >= 0 && index <= size-1 ) { tmp = namelist[index]; } else { tmp = ""; } return ( tmp ); } set { if( index >= 0 && index <= size-1 ) { namelist[index] = value; } } } static void Main(string[] args) { IndexedNames names = new IndexedNames(); names[0] = "Zara"; names[1] = "Riz"; names[2] = "Nuha"; names[3] = "Asif"; names[4] = "Davinder"; names[5] = "Sunil"; names[6] = "Rubic"; for ( int i = 0; i < IndexedNames.size; i++ ) { Console.WriteLine(names[i]); } Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Zara Riz Nuha Asif Davinder Sunil Rubic N. A. N. A. N. A.
重载索引器
索引器可以重载。索引器也可以用多个参数声明,并且每个参数可以是不同的类型。索引不必是整数。C# 允许索引为其他类型,例如字符串。
以下示例演示了重载索引器 -
using System; namespace IndexerApplication { class IndexedNames { private string[] namelist = new string[size]; static public int size = 10; public IndexedNames() { for (int i = 0; i < size; i++) { namelist[i] = "N. A."; } } public string this[int index] { get { string tmp; if( index >= 0 && index <= size-1 ) { tmp = namelist[index]; } else { tmp = ""; } return ( tmp ); } set { if( index >= 0 && index <= size-1 ) { namelist[index] = value; } } } public int this[string name] { get { int index = 0; while(index < size) { if (namelist[index] == name) { return index; } index++; } return index; } } static void Main(string[] args) { IndexedNames names = new IndexedNames(); names[0] = "Zara"; names[1] = "Riz"; names[2] = "Nuha"; names[3] = "Asif"; names[4] = "Davinder"; names[5] = "Sunil"; names[6] = "Rubic"; //using the first indexer with int parameter for (int i = 0; i < IndexedNames.size; i++) { Console.WriteLine(names[i]); } //using the second indexer with the string parameter Console.WriteLine(names["Nuha"]); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Zara Riz Nuha Asif Davinder Sunil Rubic N. A. N. A. N. A. 2
C# - 委托
C# 委托类似于 C 或 C++ 中的函数指针。委托是一种引用类型变量,它保存对方法的引用。引用可以在运行时更改。
委托特别用于实现事件和回调方法。所有委托都隐式派生自System.Delegate类。
声明委托
委托声明确定委托可以引用的方法。委托可以引用与委托具有相同签名的方法。
例如,考虑一个委托 -
public delegate int MyDelegate (string s);
前面的委托可用于引用任何具有单个string参数并返回int类型变量的方法。
委托声明的语法为 -
delegate <return type> <delegate-name> <parameter list>
实例化委托
一旦声明了委托类型,就必须使用new关键字创建委托对象并将其与特定方法关联。创建委托时,传递给new表达式的参数写法类似于方法调用,但没有方法的参数。例如 -
public delegate void printString(string s); ... printString ps1 = new printString(WriteToScreen); printString ps2 = new printString(WriteToFile);
以下示例演示了可以用于引用接受整数参数并返回整数值的方法的委托的声明、实例化和使用。
using System; delegate int NumberChanger(int n); namespace DelegateAppl { class TestDelegate { static int num = 10; public static int AddNum(int p) { num += p; return num; } public static int MultNum(int q) { num *= q; return num; } public static int getNum() { return num; } static void Main(string[] args) { //create delegate instances NumberChanger nc1 = new NumberChanger(AddNum); NumberChanger nc2 = new NumberChanger(MultNum); //calling the methods using the delegate objects nc1(25); Console.WriteLine("Value of Num: {0}", getNum()); nc2(5); Console.WriteLine("Value of Num: {0}", getNum()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Value of Num: 35 Value of Num: 175
委托的多播
可以使用“+”运算符组合委托对象。组合的委托会调用它从中组合的两个委托。只能组合相同类型的委托。“-”运算符可用于从组合的委托中删除组件委托。
使用委托的此属性,您可以创建一个方法调用列表,这些方法将在调用委托时被调用。这称为委托的多播。以下程序演示了委托的多播 -
using System; delegate int NumberChanger(int n); namespace DelegateAppl { class TestDelegate { static int num = 10; public static int AddNum(int p) { num += p; return num; } public static int MultNum(int q) { num *= q; return num; } public static int getNum() { return num; } static void Main(string[] args) { //create delegate instances NumberChanger nc; NumberChanger nc1 = new NumberChanger(AddNum); NumberChanger nc2 = new NumberChanger(MultNum); nc = nc1; nc += nc2; //calling multicast nc(5); Console.WriteLine("Value of Num: {0}", getNum()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Value of Num: 75
使用委托
以下示例演示了委托的使用。委托printString可用于引用将字符串作为输入并返回空值的方法。
我们使用此委托来调用两个方法,第一个将字符串打印到控制台,第二个将字符串打印到文件 -
using System; using System.IO; namespace DelegateAppl { class PrintString { static FileStream fs; static StreamWriter sw; // delegate declaration public delegate void printString(string s); // this method prints to the console public static void WriteToScreen(string str) { Console.WriteLine("The String is: {0}", str); } //this method prints to a file public static void WriteToFile(string s) { fs = new FileStream("c:\\message.txt", FileMode.Append, FileAccess.Write); sw = new StreamWriter(fs); sw.WriteLine(s); sw.Flush(); sw.Close(); fs.Close(); } // this method takes the delegate as parameter and uses it to // call the methods as required public static void sendString(printString ps) { ps("Hello World"); } static void Main(string[] args) { printString ps1 = new printString(WriteToScreen); printString ps2 = new printString(WriteToFile); sendString(ps1); sendString(ps2); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
The String is: Hello World
C# - 事件
事件是用户操作,例如按键、点击、鼠标移动等,或某些事件,例如系统生成的通知。应用程序需要在事件发生时对其做出响应。例如,中断。事件用于进程间通信。
将委托与事件一起使用
事件在类中声明和引发,并使用同一类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这称为发布者类。接受此事件的其他一些类称为订阅者类。事件使用发布者-订阅者模型。
发布者是一个包含事件定义和委托的对象。事件-委托关联也在此对象中定义。发布者类对象调用事件,并将其通知给其他对象。
订阅者是一个接受事件并提供事件处理程序的对象。发布者类中的委托调用订阅者类的方法(事件处理程序)。
声明事件
要在类内部声明事件,首先必须为事件声明委托类型,如下所示
public delegate string BoilerLogHandler(string str);
然后,使用event关键字声明事件 -
event BoilerLogHandler BoilerEventLog;
前面的代码定义了一个名为BoilerLogHandler的委托和一个名为BoilerEventLog的事件,该事件在引发时调用委托。
示例
using System; namespace SampleApp { public delegate string MyDel(string str); class EventProgram { event MyDel MyEvent; public EventProgram() { this.MyEvent += new MyDel(this.WelcomeUser); } public string WelcomeUser(string username) { return "Welcome " + username; } static void Main(string[] args) { EventProgram obj1 = new EventProgram(); string result = obj1.MyEvent("Tutorials Point"); Console.WriteLine(result); } } }
当以上代码编译并执行时,会产生以下结果:
Welcome Tutorials Point
C# - 集合
集合类是用于数据存储和检索的专门类。这些类为堆栈、队列、列表和哈希表提供支持。大多数集合类都实现相同的接口。
集合类用于各种目的,例如动态为元素分配内存以及根据索引访问项目列表等。这些类创建 Object 类的对象的集合,Object 类是 C# 中所有数据类型的基类。
各种集合类及其用法
以下是System.Collection命名空间中常用的各种类。单击以下链接以检查其详细信息。
序号 | 类和说明及用法 |
---|---|
1 | ArrayList
它表示可以单独索引的对象的有序集合。 它基本上是数组的替代方案。但是,与数组不同,您可以使用索引在指定位置添加和删除列表中的项目,并且数组会自动调整自身大小。它还允许动态内存分配、添加、搜索和排序列表中的项目。 |
2 | Hashtable
它使用键访问集合中的元素。 当您需要使用键访问元素并且可以识别有用的键值时,使用哈希表。哈希表中的每个项目都有一个键/值对。键用于访问集合中的项目。 |
3 | SortedList
它使用键和索引来访问列表中的项目。 排序列表是数组和哈希表的组合。它包含一个项目列表,可以使用键或索引访问。如果使用索引访问项目,则为 ArrayList,如果使用键访问项目,则为 Hashtable。项目集合始终按键值排序。 |
4 | Stack
它表示对象的后进先出集合。 当您需要后进先出访问项目时使用它。当您在列表中添加项目时,称为推送项目,当您将其删除时,称为弹出项目。 |
5 | Queue
它表示对象的先进先出集合。 当您需要先进先出 (FIFO) 访问项目时使用它。当您在列表中添加项目时,称为入队 (enqueue),当您删除项目时,称为出队 (deque)。 |
6 | 位数组 (BitArray)
它表示使用值 1 和 0 的二进制表示的数组。 当您需要存储位但事先不知道位数时,可以使用它。您可以使用整数索引访问 BitArray 集合中的项目,索引从零开始。 |
C# - 泛型
泛型允许您在类或方法中定义编程元素的数据类型的规范,直到它实际在程序中使用。换句话说,泛型允许您编写可以处理任何数据类型的类或方法。
您使用替换数据类型参数来编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理特定的数据类型。一个简单的例子将有助于理解这个概念 -
using System; using System.Collections.Generic; namespace GenericApplication { public class MyGenericArray<T> { private T[] array; public MyGenericArray(int size) { array = new T[size + 1]; } public T getItem(int index) { return array[index]; } public void setItem(int index, T value) { array[index] = value; } } class Tester { static void Main(string[] args) { //declaring an int array MyGenericArray<int> intArray = new MyGenericArray<int>(5); //setting values for (int c = 0; c < 5; c++) { intArray.setItem(c, c*5); } //retrieving the values for (int c = 0; c < 5; c++) { Console.Write(intArray.getItem(c) + " "); } Console.WriteLine(); //declaring a character array MyGenericArray<char> charArray = new MyGenericArray<char>(5); //setting values for (int c = 0; c < 5; c++) { charArray.setItem(c, (char)(c+97)); } //retrieving the values for (int c = 0; c< 5; c++) { Console.Write(charArray.getItem(c) + " "); } Console.WriteLine(); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
0 5 10 15 20 a b c d e
泛型的特性
泛型是一种以以下方式丰富程序的技术 -
它有助于最大程度地提高代码重用性、类型安全性以及性能。
您可以创建泛型集合类。.NET Framework 类库在System.Collections.Generic命名空间中包含几个新的泛型集合类。您可以使用这些泛型集合类来代替System.Collections命名空间中的集合类。
您可以创建自己的泛型接口、类、方法、事件和委托。
您可以创建受约束的泛型类,以启用对特定数据类型上的方法的访问。
您可以通过反射在运行时获取有关泛型数据类型中使用的类型的信息。
泛型方法
在前面的示例中,我们使用了泛型类;我们可以使用类型参数声明泛型方法。以下程序说明了这个概念 -
using System; using System.Collections.Generic; namespace GenericMethodAppl { class Program { static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; } static void Main(string[] args) { int a, b; char c, d; a = 10; b = 20; c = 'I'; d = 'V'; //display values before swap: Console.WriteLine("Int values before calling swap:"); Console.WriteLine("a = {0}, b = {1}", a, b); Console.WriteLine("Char values before calling swap:"); Console.WriteLine("c = {0}, d = {1}", c, d); //call swap Swap<int>(ref a, ref b); Swap<char>(ref c, ref d); //display values after swap: Console.WriteLine("Int values after calling swap:"); Console.WriteLine("a = {0}, b = {1}", a, b); Console.WriteLine("Char values after calling swap:"); Console.WriteLine("c = {0}, d = {1}", c, d); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Int values before calling swap: a = 10, b = 20 Char values before calling swap: c = I, d = V Int values after calling swap: a = 20, b = 10 Char values after calling swap: c = V, d = I
泛型委托
您可以使用类型参数定义泛型委托。例如 -
delegate T NumberChanger<T>(T n);
以下示例显示了此委托的用法 -
using System; using System.Collections.Generic; delegate T NumberChanger<T>(T n); namespace GenericDelegateAppl { class TestDelegate { static int num = 10; public static int AddNum(int p) { num += p; return num; } public static int MultNum(int q) { num *= q; return num; } public static int getNum() { return num; } static void Main(string[] args) { //create delegate instances NumberChanger<int> nc1 = new NumberChanger<int>(AddNum); NumberChanger<int> nc2 = new NumberChanger<int>(MultNum); //calling the methods using the delegate objects nc1(25); Console.WriteLine("Value of Num: {0}", getNum()); nc2(5); Console.WriteLine("Value of Num: {0}", getNum()); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Value of Num: 35 Value of Num: 175
C# - 匿名方法
我们讨论了委托用于引用与委托签名相同的任何方法。换句话说,您可以使用该委托对象调用可以由委托引用的方法。
匿名方法提供了一种将代码块作为委托参数传递的技术。匿名方法是没有名称的方法,只有主体。
您不需要在匿名方法中指定返回类型;它是从方法体内的 return 语句推断出来的。
编写匿名方法
匿名方法是在创建委托实例时使用delegate关键字声明的。例如,
delegate void NumberChanger(int n); ... NumberChanger nc = delegate(int x) { Console.WriteLine("Anonymous Method: {0}", x); };
代码块Console.WriteLine("Anonymous Method: {0}", x);是匿名方法的主体。
委托可以用匿名方法和命名方法以相同的方式调用,即通过将方法参数传递给委托对象。
例如,
nc(10);
示例
以下示例演示了这个概念 -
using System; delegate void NumberChanger(int n); namespace DelegateAppl { class TestDelegate { static int num = 10; public static void AddNum(int p) { num += p; Console.WriteLine("Named Method: {0}", num); } public static void MultNum(int q) { num *= q; Console.WriteLine("Named Method: {0}", num); } public static int getNum() { return num; } static void Main(string[] args) { //create delegate instances using anonymous method NumberChanger nc = delegate(int x) { Console.WriteLine("Anonymous Method: {0}", x); }; //calling the delegate using the anonymous method nc(10); //instantiating the delegate using the named methods nc = new NumberChanger(AddNum); //calling the delegate using the named methods nc(5); //instantiating the delegate using another named methods nc = new NumberChanger(MultNum); //calling the delegate using the named methods nc(2); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Anonymous Method: 10 Named Method: 15 Named Method: 30
C# - 不安全代码
当代码块由unsafe修饰符标记时,C# 允许在代码块的函数中使用指针变量。不安全代码或非托管代码是使用指针变量的代码块。
注意 - 要在codingground上执行本章中提到的程序,请在项目 >> 编译选项 >> 编译命令中设置编译选项为
mcs *.cs -out:main.exe -unsafe"
指针
指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。与任何变量或常量类似,您必须在使用指针存储任何变量地址之前声明它。
指针声明的一般形式为 -
type *var-name;
以下是有效的指针声明 -
int *ip; /* pointer to an integer */ double *dp; /* pointer to a double */ float *fp; /* pointer to a float */ char *ch /* pointer to a character */
以下示例说明了在 C# 中使用指针,使用 unsafe 修饰符 -
using System; namespace UnsafeCodeApplication { class Program { static unsafe void Main(string[] args) { int var = 20; int* p = &var; Console.WriteLine("Data is: {0} ", var); Console.WriteLine("Address is: {0}", (int)p); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果 -
Data is: 20 Address is: 99215364
除了将整个方法声明为不安全之外,您还可以将代码的一部分声明为不安全。下一节中的示例演示了这一点。
使用指针检索数据值
您可以使用ToString()方法检索存储在指针变量引用的位置处的数据。以下示例演示了这一点 -
using System; namespace UnsafeCodeApplication { class Program { public static void Main() { unsafe { int var = 20; int* p = &var; Console.WriteLine("Data is: {0} " , var); Console.WriteLine("Data is: {0} " , p->ToString()); Console.WriteLine("Address is: {0} " , (int)p); } Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果 -
Data is: 20 Data is: 20 Address is: 77128984
将指针作为参数传递给方法
您可以将指针变量作为参数传递给方法。以下示例说明了这一点 -
using System; namespace UnsafeCodeApplication { class TestPointer { public unsafe void swap(int* p, int *q) { int temp = *p; *p = *q; *q = temp; } public unsafe static void Main() { TestPointer p = new TestPointer(); int var1 = 10; int var2 = 20; int* x = &var1; int* y = &var2; Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2); p.swap(x, y); Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
Before Swap: var1: 10, var2: 20 After Swap: var1: 20, var2: 10
使用指针访问数组元素
在 C# 中,数组名称和与数组数据相同的类型的数据指针不是相同的变量类型。例如,int *p 和 int[] p 不是相同的类型。您可以递增指针变量 p,因为它在内存中不是固定的,但数组地址在内存中是固定的,您不能递增它。
因此,如果您需要像我们在 C 或 C++ 中传统地那样使用指针变量访问数组数据(请查看:C 指针),则需要使用fixed关键字来固定指针。
以下示例演示了这一点:
using System; namespace UnsafeCodeApplication { class TestPointer { public unsafe static void Main() { int[] list = {10, 100, 200}; fixed(int *ptr = list) /* let us have array address in pointer */ for ( int i = 0; i < 3; i++) { Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i)); Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i)); } Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果 -
Address of list[0] = 31627168 Value of list[0] = 10 Address of list[1] = 31627172 Value of list[1] = 100 Address of list[2] = 31627176 Value of list[2] = 200
编译不安全代码
要编译不安全代码,您必须使用命令行编译器指定/unsafe命令行开关。
例如,要从命令行编译名为 prog1.cs 的包含不安全代码的程序,请使用以下命令 -
csc /unsafe prog1.cs
如果您使用的是 Visual Studio IDE,则需要在项目属性中启用不安全代码的使用。
为此 -
通过双击解决方案资源管理器中的属性节点打开项目属性。
点击生成选项卡。
选择“允许不安全代码”选项。
C# - 多线程
线程定义为程序的执行路径。每个线程定义一个唯一的控制流。如果您的应用程序涉及复杂且耗时的操作,则设置不同的执行路径或线程通常很有帮助,每个线程执行特定的作业。
线程是轻量级进程。线程的一个常见示例是现代操作系统并发编程的实现。使用线程可以节省 CPU 周期的浪费并提高应用程序的效率。
到目前为止,我们编写的程序只有一个线程作为一个进程运行,该进程是应用程序的运行实例。但是,这样应用程序一次只能执行一项作业。为了使其一次执行多个任务,可以将其划分为更小的线程。
线程生命周期
线程的生命周期从创建 System.Threading.Thread 类的对象开始,并在线程终止或完成执行时结束。
以下是线程生命周期中的各种状态 -
未启动状态 - 当创建线程实例但未调用 Start 方法时的情况。
就绪状态 - 当线程准备运行并等待 CPU 周期时的情况。
不可运行状态 - 当线程不可执行时,
- 已调用 Sleep 方法
- 已调用 Wait 方法
- 被 I/O 操作阻塞
死亡状态 - 当线程完成执行或被中止时的情况。
主线程
在 C# 中,System.Threading.Thread类用于处理线程。它允许在多线程应用程序中创建和访问各个线程。在进程中第一个执行的线程称为主线程。
当 C# 程序开始执行时,会自动创建主线程。使用Thread类创建的线程称为主线程的子线程。您可以使用 Thread 类的CurrentThread属性访问线程。
以下程序演示了主线程执行 -
using System; using System.Threading; namespace MultithreadingApplication { class MainThreadProgram { static void Main(string[] args) { Thread th = Thread.CurrentThread; th.Name = "MainThread"; Console.WriteLine("This is {0}", th.Name); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
This is MainThread
Thread 类的属性和方法
下表显示了Thread类的一些最常用的属性 -
序号 | 属性和描述 |
---|---|
1 | CurrentContext 获取线程正在执行的当前上下文。 |
2 | CurrentCulture 获取或设置当前线程的区域性。 |
3 | CurrentPrinciple 获取或设置线程的当前主体(用于基于角色的安全)。 |
4 | CurrentThread 获取当前正在运行的线程。 |
5 | CurrentUICulture 获取或设置 Resource Manager 在运行时查找特定于区域性的资源时使用的当前区域性。 |
6 | ExecutionContext 获取一个 ExecutionContext 对象,其中包含有关当前线程的各种上下文的信息。 |
7 | IsAlive 获取一个值,指示当前线程的执行状态。 |
8 | IsBackground 获取或设置一个值,指示线程是否为后台线程。 |
9 | IsThreadPoolThread 获取一个值,指示线程是否属于托管线程池。 |
10 | ManagedThreadId 获取当前托管线程的唯一标识符。 |
11 | Name 获取或设置线程的名称。 |
12 | Priority 获取或设置一个值,指示线程的调度优先级。 |
13 | ThreadState 获取一个包含当前线程状态的值。 |
下表显示了Thread类的一些最常用的方法 -
序号 | 方法和描述 |
---|---|
1 | public void Abort() 在调用它的线程中引发 ThreadAbortException,以开始终止线程的过程。调用此方法通常会终止线程。 |
2 | public static LocalDataStoreSlot AllocateDataSlot() 在所有线程上分配一个未命名的存储槽。为了获得更好的性能,请改用标记有 ThreadStaticAttribute 属性的字段。 |
3 | public static LocalDataStoreSlot AllocateNamedDataSlot(string name) 在所有线程上分配一个命名存储槽。为了获得更好的性能,请改用标记有 ThreadStaticAttribute 属性的字段。 |
4 | public static void BeginCriticalRegion() 通知主机执行即将进入代码区域,在此区域中,线程中止或未处理异常的影响可能会危及应用程序域中的其他任务。 |
5 | public static void BeginThreadAffinity() 通知主机托管代码即将执行依赖于当前物理操作系统线程身份的指令。 |
6 | public static void EndCriticalRegion() 通知主机执行即将进入代码区域,在此区域中,线程中止或未处理异常的影响仅限于当前任务。 |
7 | public static void EndThreadAffinity() 通知主机托管代码已完成执行依赖于当前物理操作系统线程身份的指令。 |
8 | public static void FreeNamedDataSlot(string name) 消除进程中所有线程的名称和插槽之间的关联。为了获得更好的性能,请改用标记有 ThreadStaticAttribute 属性的字段。 |
9 | public static Object GetData(LocalDataStoreSlot slot) 检索当前线程当前域中指定插槽中的值。为了获得更好的性能,请改用标记有 ThreadStaticAttribute 属性的字段。 |
10 | public static AppDomain GetDomain() 返回当前线程正在运行的当前域。 |
11 | public static AppDomain GetDomainID() 返回唯一的应用程序域标识符。 |
12 | public static LocalDataStoreSlot GetNamedDataSlot(string name) 查找命名数据插槽。为了获得更好的性能,请改用标记有 ThreadStaticAttribute 属性的字段。 |
13 | public void Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。 |
14 | public void Join() 阻塞调用线程,直到线程终止,同时继续执行标准 COM 和 SendMessage 消息循环。此方法具有不同的重载形式。 |
15 | public static void MemoryBarrier() 同步内存访问,如下所示:执行当前线程的处理器不能重新排序指令,以使在调用 MemoryBarrier 之前的内存访问在调用 MemoryBarrier 之后的内存访问之后执行。 |
16 | public static void ResetAbort() 取消对当前线程请求的 Abort。 |
17 | public static void SetData(LocalDataStoreSlot slot, Object data) 在当前运行线程的指定插槽中设置数据,用于该线程的当前域。为了获得更好的性能,请改用标记有 ThreadStaticAttribute 属性的字段。 |
18 | public void Start() 启动一个线程。 |
19 | public static void Sleep(int millisecondsTimeout) 使线程暂停一段时间。 |
20 | public static void SpinWait(int iterations) 使线程等待由 iterations 参数定义的次数。 |
21 | public static byte VolatileRead(ref byte address) public static double VolatileRead(ref double address) public static int VolatileRead(ref int address) public static Object VolatileRead(ref Object address) 读取字段的值。该值是计算机中任何处理器最新写入的值,无论处理器数量或处理器缓存状态如何。此方法具有不同的重载形式。上面只给出了一些。 |
22 | public static void VolatileWrite(ref byte address,byte value) public static void VolatileWrite(ref double address, double value) public static void VolatileWrite(ref int address, int value) public static void VolatileWrite(ref Object address, Object value) 立即将值写入字段,以便计算机中的所有处理器都能看到该值。此方法具有不同的重载形式。上面只给出了一些。 |
23 | public static bool Yield() 导致调用线程将执行权让给另一个已准备好在线程的当前处理器上运行的线程。操作系统选择要让出的线程。 |
创建线程
通过扩展 Thread 类来创建线程。扩展的 Thread 类然后调用 **Start()** 方法来开始子线程执行。
以下程序演示了这个概念:
using System; using System.Threading; namespace MultithreadingApplication { class ThreadCreationProgram { public static void CallToChildThread() { Console.WriteLine("Child thread starts"); } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine("In Main: Creating the Child thread"); Thread childThread = new Thread(childref); childThread.Start(); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
In Main: Creating the Child thread Child thread starts
管理线程
Thread 类提供了各种管理线程的方法。
以下示例演示了使用 **sleep()** 方法使线程暂停特定时间段。
using System; using System.Threading; namespace MultithreadingApplication { class ThreadCreationProgram { public static void CallToChildThread() { Console.WriteLine("Child thread starts"); // the thread is paused for 5000 milliseconds int sleepfor = 5000; Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000); Thread.Sleep(sleepfor); Console.WriteLine("Child thread resumes"); } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine("In Main: Creating the Child thread"); Thread childThread = new Thread(childref); childThread.Start(); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
In Main: Creating the Child thread Child thread starts Child Thread Paused for 5 seconds Child thread resumes
销毁线程
**Abort()** 方法用于销毁线程。
运行时通过抛出 **ThreadAbortException** 来中止线程。此异常无法捕获,控制权将发送到 finally 块(如果存在)。
以下程序说明了这一点:
using System; using System.Threading; namespace MultithreadingApplication { class ThreadCreationProgram { public static void CallToChildThread() { try { Console.WriteLine("Child thread starts"); // do some work, like counting to 10 for (int counter = 0; counter <= 10; counter++) { Thread.Sleep(500); Console.WriteLine(counter); } Console.WriteLine("Child Thread Completed"); } catch (ThreadAbortException e) { Console.WriteLine("Thread Abort Exception"); } finally { Console.WriteLine("Couldn't catch the Thread Exception"); } } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine("In Main: Creating the Child thread"); Thread childThread = new Thread(childref); childThread.Start(); //stop the main thread for some time Thread.Sleep(2000); //now abort the child Console.WriteLine("In Main: Aborting the Child thread"); childThread.Abort(); Console.ReadKey(); } } }
当以上代码编译并执行时,会产生以下结果:
In Main: Creating the Child thread Child thread starts 0 1 2 In Main: Aborting the Child thread Thread Abort Exception Couldn't catch the Thread Exception