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# 程序

您可以编译和执行所有可用的示例。

using System;

namespace HelloWorldApplication {
   
   class HelloWorld {

      static void Main(string[] args) {
         /* 我的第一个 C# 程序 */
         Console.WriteLine("Hello World");
         Console.ReadKey();
      }
   }
}

在本章中,我们将讨论创建 C# 编程所需的工具。我们已经提到过,C# 是 .Net 框架的一部分,用于编写 .Net 应用程序。因此,在讨论运行 C# 程序的可用工具之前,让我们先了解一下 C# 与 .Net 框架之间的关系。

.Net 框架

.Net 框架是一个革命性的平台,可帮助您编写以下类型的应用程序 -

  • Windows 应用程序
  • Web 应用程序
  • Web 服务

.Net 框架应用程序是多平台应用程序。该框架的设计使其能够通过以下任何一种语言使用:C#、C++、Visual Basic、Jscript、COBOL 等。所有这些语言都可以访问该框架并相互通信。

.Net 框架包含一个庞大的代码库,可供 C# 等客户端语言使用。以下是 .Net 框架的一些组件 -

  • 公共语言运行时 (CLR)
  • .Net 框架类库
  • 公共语言规范
  • 通用类型系统
  • 元数据和程序集
  • Windows 窗体
  • ASP.Net 和 ASP.Net AJAX
  • ADO.Net
  • Windows 工作流基础 (WF)
  • Windows 演示基础
  • Windows 通信基础 (WCF)
  • LINQ

有关每个组件执行的操作,请参阅 ASP.Net - 简介,有关每个组件的详细信息,请参阅微软的文档。

C# 集成开发环境 (IDE)

微软提供了以下 C# 编程开发工具:

  • Visual Studio 2010 (VS)
  • Visual C# 2010 Express (VCE)
  • Visual Web Developer

后两个工具可从微软官方网站免费获取。使用这些工具,您可以编写各种 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) {
         /* 我的第一个 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");

    WriteLineConsole 类的一个方法,该类定义在 System 命名空间中。此语句会显示消息"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# 是一种面向对象的编程语言。在面向对象编程方法论中,程序由各种对象组成,这些对象通过操作相互交互。对象可能执行的操作称为方法。同类对象被称为具有相同类型,或者属于同一类。

例如,我们考虑一个 Rectangle 对象。它具有长度和宽度等属性。根据设计,它可能需要一些方法来接受这些属性的值、计算面积以及显示详细信息。

让我们看一下 Rectangle 类的实现,并讨论 C# 的基本语法 -

using System;

namespace RectangleApplication {
   
   class Rectangle {
      // 成员变量
      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# 程序中的多行注释以 /* 开头,以字符 */ 结尾,如下所示 -

/* 此程序演示了
C# 编程语言的基本语法 */

单行注释用"//"符号表示。例如:

}//end class Rectangle

成员变量

变量是类的属性或数据成员,用于存储数据。在上面的程序中,Rectangle 类有两个成员变量,分别为 lengthwidth

成员函数

函数是执行特定任务的语句集合。类的成员函数在类内部声明。我们的示例类 Rectangle 包含三个成员函数:AcceptDetailsGetAreaDisplay

实例化类

在上面的程序中,类 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 (generic modifier) int
interface internal is lock long namespace new
null object operator out out (generic modifier) 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
Contextual Keywords
add alias ascending descending dynamic from get
global group into join let orderby partial (type)
partial
(method)
remove select set

C# - 数据类型

C# 中的变量分为以下几种类型:

  • 值类型
  • 引用类型
  • 指针类型

值类型

值类型变量可以直接赋值。它们派生自 System.ValueType 类。

值类型直接包含数据。例如 int、char 和 float,分别存储数字、字母和浮点数。当你声明 int 类型时,系统会分配内存来存储该值。

下表列出了 C# 2010 中可用的值类型 -

类型 表示 范围 默认值
bool Boolean value True or False False
byte 8-bit unsigned integer 0 to 255 0
char 16-bit Unicode character U +0000 to U +ffff '\0'
decimal 128-bit precise decimal values with 28-29 significant digits (-7.9 x 1028 to 7.9 x 1028) / 100 to 28 0.0M
double 64-bit double-precision floating point type (+/-)5.0 x 10-324 to (+/-)1.7 x 10308 0.0D
float 32-bit single-precision floating point type -3.4 x 1038 to + 3.4 x 1038 0.0F
int 32-bit signed integer type -2,147,483,648 to 2,147,483,647 0
long 64-bit signed integer type -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 0L
sbyte 8-bit signed integer type -128 to 127 0
short 16-bit signed integer type -32,768 to 32,767 0
uint 32-bit unsigned integer type 0 to 4,294,967,295 0
ulong 64-bit unsigned integer type 0 to 18,446,744,073,709,551,615 0
ushort 16-bit unsigned integer type 0 to 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

引用类型

引用类型不包含变量中存储的实际数据,但包含对变量的引用。

换句话说,它们指向一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果其中一个变量更改了该内存位置中的数据,则另一个变量的值会自动反映此更改。内置引用类型的示例包括:objectdynamicstring

对象类型

对象类型是 C# 通用类型系统 (CTS) 中所有数据类型的最终基类。Object 是 System.Object 类的别名。对象类型可以被赋值为任何其他类型、值类型、引用类型、预定义或用户定义类型的值。但是,在赋值之前,需要进行类型转换。

将值类型转换为对象类型称为装箱;另一方面,将对象类型转换为值类型称为拆箱

object obj;
obj = 100; // 这是装箱

动态类型

您可以在动态数据类型变量中存储任何类型的值。这些类型变量的类型检查在运行时进行。

声明动态类型的语法为:-

dynamic  = value;

例如:

dynamic d = 20;

动态类型与对象类型类似,不同之处在于对象类型变量的类型检查在编译时进行,而动态类型变量的类型检查在运行时进行。

字符串类型

字符串类型允许您将任何字符串值赋给变量。字符串类型是 System.String 类的别名。它派生自对象类型。字符串类型的值可以使用两种形式的字符串字面量赋值:带引号和 @quoted。

例如:

String str = "Tutorials Point";

@quoted 字符串字面量如下所示 -

@"Tutorials Point";

用户定义的引用类型包括:类、接口或委托。我们将在后面的章节中讨论这些类型。

指针类型

指针类型变量存储其他类型的内存地址。C# 中的指针与 C 或 C++ 中的指针具有相同的功能。

声明指针类型的语法为 -

type* identifier;

例如,

char* cptr;
int* iptr;

我们将在"不安全代码"一章中讨论指针类型。

C# - 类型转换

类型转换是将一种类型的数据转换为另一种类型。它也称为类型强制转换。在 C# 中,类型强制转换有两种形式 -

隐式类型转换

隐式转换由 C# 编译器以类型安全的方式执行。例如,一个值可以从一种数据类型转换为另一种数据类型,而无需显式强制转换;可以从较小的整数类型转换为较大的整数类型;或者从派生类转换为基类。

示例

在此示例中,我们演示了隐式强制转换,当将较小的类型传递给较大的类型时,会自动进行隐式强制转换 -

using System;
namespace MyExample {
   class Example {
      static void Main(string[] args) {
         int myInt = 9;
         
         // 自动转换:int 到 double
         double myDouble = myInt;
		 
         Console.WriteLine(myInt);
         Console.WriteLine(myDouble);
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

9
9

显式类型转换

显式转换由用户使用预定义函数显式完成。显式转换需要强制类型转换运算符。

示例 1

以下示例展示了一个显式类型转换,这里我们将 double 转换为 int -

using System;
namespace TypeConversionApplication {
   class ExplicitConversion {
      static void Main(string[] args) {
         double d = 5673.74; 
         int i;
         
         // 将 double 转换为 int。
         i = (int)d;
         Console.WriteLine(i);
         Console.ReadKey();
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

5673

示例 2

在以下示例中,我们在 C# 中使用显式类型转换手动将 int 转换为 float:

using System;
namespace MyApplication {
   class Program {
      static void Main(string[] args) {
         int myInt = 10;
         
         // 手动转换:int 到 float
         float myFloat = (float) myInt;
		 
         Console.WriteLine(myInt);
         Console.WriteLine(myFloat);
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

10
10

使用 Convert 类进行类型转换

C# 中的 Convert 类提供了用于显式转换各种数据类型的方法。

示例

using System;
namespace ConversionExample {
   class Program {
      static void Main(string[] args) {
         string str = "123";
         int num = Convert.ToInt32(str);
         Console.WriteLine(num);
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

123

使用 Parse() 方法进行类型转换

Parse() 方法用于将数字的字符串表示形式转换为其相应的数据类型。

示例

using System;
namespace ParseExample {
   class Program {
      static void Main(string[] args) {
         string str = "456";
         int num = int.Parse(str);
         Console.WriteLine(num);
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

456

使用 TryParse() 方法进行类型转换

TryParse() 方法可以安全地将字符串转换为数字数据类型,并返回指示成功或失败的布尔值。

示例

using System;
namespace TryParseExample {
   class Program {
      static void Main(string[] args) {
         string str = "789";
         if (int.TryParse(str, out int result)) {
            Console.WriteLine(result);
         } else {
            Console.WriteLine("Conversion failed.");
         }
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

789

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

将类型转换为无符号整型。

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; 	/* 初始化 d 和 f。 */
byte z = 22; 		/* 初始化 z。*/
double pi = 3.14159; /* 声明 pi 的近似值。*/
char x = 'x';		 /* 变量 x 的值为 '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# 中有两种表达式 -

  • 左值 - 左值表达式可以出现在赋值语句的左侧或右侧。

  • 右值 - 右值表达式可以出现在赋值语句的右侧,但不能出现在赋值语句的左侧。

变量是左值,因此它们可以出现在赋值语句的左侧。数字字面量是右值,因此不能被赋值,也不能出现在左侧。以下是一条有效的 C# 语句 -

int g = 20;

但以下语句无效,会导致编译时错误 -

10 = 20;

C# - 常量和字面量

常量指的是程序在执行过程中无法更改的固定值。这些固定值也称为字面量。常量可以是任何基本数据类型,例如整型常量、浮点型常量、字符型常量或字符串字面量。此外,还有枚举常量。

常量与常规变量类似,只是它们的值在定义后无法修改。

整型字面量

整型字面量可以是十进制或十六进制常量。前缀指定基数:十六进制为 0x 或 0X,十进制没有前缀 id。

整型字面量还可以带有后缀 U 和 L,分别表示无符号和长整型。后缀可以是大写或小写,顺序不限。

示例

以下是一些整数文字的示例 -

212 	/* 合法 */
215u 	/* 合法 */
0xFeeL 	/* 合法 */

以下是其他各种类型的整数文字的示例 -

85 		/* 十进制 */
0x4b 	/* 十六进制 */
30 		/* 整数 */
30u 	/* 无符号整数 */
30l 	/* 长整型 */
30ul 	/* 无符号长整型 */

浮点字面量

浮点字面量包含整数部分、小数点、小数部分和指数部分。浮点字面量可以用十进制或指数形式表示。

示例

以下是一些浮点字面量的示例 -

3.14159 		/* 合法 */
314159E-5F 		/* 合法 */
510E 			/* 非法:指数不完整 */
210f 			/* 非法:无小数或指数 */
.e55 			/* 非法:缺少整数或分数 */

以十进制形式表示时,必须包含小数点、指数或两者;以指数形式表示时,必须包含整数部分、小数部分或两者。有符号指数由 e 或 E 引入。

字符常量

字符字面量用单引号括起来。例如,"x"可以存储在 char 类型的简单变量中。字符字面量可以是普通字符(例如 'x')、转义序列(例如 ' ')或通用字符(例如 '\u02C0')。

C# 中,某些字符前面带有反斜杠。它们具有特殊含义,用于表示换行符 ( ) 或制表符 ( )。以下列出了一些此类转义序列代码 -

转义序列 含义
\ \ 字符
\' ' 字符
\" " 字符
\? ? 字符
\a 提醒或铃声
\b 退格键
\f 换页符
换行符
回车键
水平制表符
\v 垂直制表符
\xhh . . . 一位或多位十六进制数

示例

以下示例展示了几个转义序列字符 -

using System;
namespace EscapeChar {
   class Program {
      static void Main(string[] args) {
         Console.WriteLine("Hello	World");
         Console.ReadLine();
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

Hello   World

字符串字面量

字符串字面量或常量用双引号 "" 或 @"" 括起来。字符串包含与字符字面量类似的字符:普通字符、转义序列和通用字符。

您可以使用字符串字面量将长行拆分成多行,并使用空格分隔各部分。

示例

以下是一些字符串字面量的示例。这三种形式都是相同的字符串。

"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"
@"hello dear"

定义常量

常量使用 const 关键字定义。定义常量的语法为:-

const <data_type> <constant_name> = value;

访问常量

只要常量在作用域内,就可以使用其名称访问常量。

以下是在 C# 中访问常量的语法:

//当常量位于局部作用域时
constant_name

//当常量位于类中时
class_name.constant_name

类常量

类常量在类内部使用 const 关键字声明。它们是隐式静态的,这意味着它们属于类而不是实例。

可以使用类名访问类常量。

语法

您可以使用以下语法定义类常量:

class ClassName
{
    public const datatype ConstantName = value;
}

示例

在下面的示例中,我们定义了一个名为 MathConstants 的类,其中包含两个类常量:PiSpeedOfLight:

using System;

class MathConstants
{
    public const double Pi = 3.14159;
    public const int SpeedOfLight = 299792458;
}

class Program
{
    static void Main()
    {
        Console.WriteLine("Value of Pi: " + MathConstants.Pi);
        Console.WriteLine("Speed of Light: " + MathConstants.SpeedOfLight);
    }
}

当编译并执行上述代码时,它会产生以下结果 -

Value of Pi: 3.14159
Speed of Light: 299792458

更多关于常量和字面量的示例

以下是如何在 C# 中声明和访问不同类型的常量和字面量的示例:

示例 1

以下程序演示了如何在 C# sharp 程序中定义和使用常量 -

using System;
namespace DeclaringConstants {
   class Program {
      static void Main(string[] args) {
         const double pi = 3.14159;   
            
         // 常量声明
         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# 算术运算符 在数值数据类型之间执行数学运算,例如加、减、乘、除和模。这些运算符是二元运算符,需要两个操作数才能执行运算。

下表列出了 C# 支持的所有算术运算符。假设变量 A 为 10,变量 B 为 20,则 −

运算符 说明 示例
+ 将两个操作数相加 A + B = 30
- 从第一个操作数中减去第二个操作数 A - B = -10
* 将两个操作数相乘 A * B = 200
/ 将分子除以分母 B / A = 2
% 模运算符,求整数除法后的余数 B % A = 0
++ 增量运算符将整数值加一 A++ = 11
-- 减量运算符将整数值减一 A-- = 9

示例

在下面的示例中,我们演示了对整数的算术运算。

using System;

class Program
{
    static void Main()
    {
        int x = 10, y = 4;
        Console.WriteLine("Addition: " + (x + y));
        Console.WriteLine("Multiplication: " + (x * y));
        Console.WriteLine("Modulo: " + (x % y)); 
    }
}

当编译并执行上述代码时,它会产生以下结果 -

Addition: 14
Multiplication: 40
Modulo: 2

关系运算符

C# 关系运算符比较两个操作数并返回 true 或 false。这些运算符可用于决策。

下表列出了 C# 支持的所有关系运算符。假设变量 A 为 10,变量 B 为 20,则 −

运算符 描述 示例
== 检查两个操作数的值是否相等,如果相等,则条件成立。 (A == B) 不成立。
!= 检查两个操作数的值是否相等,如果值不相等,则条件成立。 (A != B) 为真。
> 检查左操作数的值是否大于右操作数的值,如果是,则条件成立。 (A > B) 不为真。
< 检查左操作数的值是否小于右操作数的值,如果是,则条件成立。 (A < B) 为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是,则条件成立。 (A >= B) 不为真。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是,则条件成立。 (A <= B) 为真。

示例

在下面的示例中,我们使用关系运算符比较两个整数。

using System;

class Program
{
    static void Main()
    {
        int a = 20, b = 15;
        Console.WriteLine("Is a > b? " + (a > b));
        Console.WriteLine("Is a == b? " + (a == b));
    }
}

当编译并执行上述代码时,它会产生以下结果 -

Is a > b? True
Is a == b? False

逻辑运算符

C# 逻辑运算符 用于组合布尔表达式。当您有多个条件并希望组合和检查它们时,这些运算符非常有用。这些运算符的结果为真或假;也就是说,这些运算符仅返回布尔值。

下表列出了 C# 支持的所有逻辑运算符。假设变量 A 的布尔值为 true,变量 B 的布尔值为 false,则 −

运算符 描述 示例
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 (A && B) 为假。
|| 称为逻辑或运算符。如果两个操作数中有一个非零,则条件为真。 (A || B) 为真。
! 称为逻辑非运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑非运算符将返回假。 !(A && B) 为真。

示例

在下面的示例中,我们使用逻辑运算符比较两个布尔值。

using System;

class Program
{
    static void Main()
    {
        bool x = true, y = false;
        Console.WriteLine("AND (&&) : " + (x && y));
        Console.WriteLine("OR (||)  : " + (x || y));
        Console.WriteLine("NOT (!)  : " + (!x));
    }
}

当编译并执行上述代码时,它会产生以下结果 -

AND (&&) : False
OR (||)  : True
NOT (!)  : False

位运算符

位运算符作用于位,并执行逐位运算。&、| 和 ^ 的真值表如下 -

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

示例

在下面的示例中,我们对两个整数执行按位"与"和"或"运算。

using System;

class Program
{
    static void Main()
    {
        int a = 5, b = 3;
        Console.WriteLine("Bitwise AND: " + (a & b)); 
        Console.WriteLine("Bitwise OR: " + (a | b));  
    }
}

当编译并执行上述代码时,它会产生以下结果 -

Bitwise AND: 1
Bitwise OR: 7

赋值运算符

C# 赋值运算符 为变量赋值,在某些情况下还执行算术运算。

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 相同

示例

在下面的示例中,我们使用 += 运算符来增加一个数字。

using System;

class Program
{
    static void Main()
    {
        int num = 10;
        num += 5;  // 相当于 num = num + 5
        Console.WriteLine(num);
    }
}

当编译并执行上述代码时,它会产生以下结果 -

15

三元运算符

C# 三元运算符需要三个操作数并执行条件检查;它是 if-else 语句的快捷方式。

示例

在下面的示例中,我们使用三元运算符来检查投票资格。

using System;

class Program
{
    static void Main()
    {
        int age = 18;
        string result = (age >= 18) ? "Eligible to vote" : "Not eligible";
        Console.WriteLine(result);
    }
}

当编译并执行上述代码时,它会产生以下结果 -

Eligible to vote

杂项运算符

C# 杂项运算符是不属于算术、逻辑、关系或位运算类别的特殊运算符。这些运算符主要用于类型检查、空值处理和内存引用。

还有其他一些重要的运算符,包括 sizeof、typeof? : C# 支持。

运算符 描述 示例
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# 中的各种运算符。

using System;

class Program
{
    static void Main()
    {
        // typeof 运算符
        Console.WriteLine("Type of int: " + typeof(int));
        
        // sizeof 运算符
        Console.WriteLine("Size of int: " + sizeof(int));
        
        // is 运算符
        object obj = "Hello";
        Console.WriteLine("Is obj a string? " + (obj is string));
        
        // as 运算符
        object number = 42;
        string str = number as string;
        Console.WriteLine("Using 'as' operator: " + (str ?? "Conversion failed"));
        
        // nameof 运算符
        string variableName = nameof(number);
        Console.WriteLine("Variable name: " + variableName);
    }
}

当编译并执行上述代码时,它会产生以下结果 -

Type of int: System.Int32
Size of int: 4
Is obj a string? True
Using 'as' operator: Conversion failed
Variable name: number

C# 中的运算符优先级

运算符优先级决定了表达式中项的分组。这会影响表达式的求值。某些运算符的优先级高于其他运算符;例如,乘法运算符的优先级高于加法运算符。

例如 x = 7 + 3 * 2;这里,x 被赋值为 13,而不是 20,因为运算符 * 的优先级高于 +,因此首先对 3*2 进行求值,然后再将 7 加到其中。

此处,优先级最高的运算符显示在表格顶部,优先级最低的运算符显示在表格底部。在表达式中,优先级较高的运算符先求值。

类别 运算符 结合律
后缀 () [] -> . ++ - - 从左到右
一元运算符 + - ! ~ ++ - - (type)* & sizeof 从右到左
乘法 * / % 从左到右
加法 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
按位与 & 从左到右
按位异或 ^ 从左到右
按位或 | 从左到右
逻辑与 && 从左到右
逻辑或 || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= %=>>= <<= &= ^= |= 从右到左
逗号 , 从左到右

C# - 决策语句

决策制定结构要求程序员指定程序要评估或测试的一个或多个条件,以及在条件成立时执行的一个或多个语句,以及(可选)在条件不成立时执行的其他语句。

以下是大多数编程语言中常见的典型决策制定结构的一般形式 -

C# 中的决策制定语句

C# 提供了以下类型的决策制定语句。点击以下链接查看其详细信息。

Sr.No. 声明 &说明
1 if 语句

if 语句由一个布尔表达式后跟一个或多个语句组成。

2 if...else 语句

if 语句后面可以跟一个可选的 else 语句,当布尔表达式为 false 时执行该语句。

3 嵌套 if 语句

您可以在一个 ifelse if 语句中嵌套另一个 ifelse 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 语句

终止 loopswitch 语句,并将执行转移到紧接着该循环或 switch 语句的语句。

2 continue 语句

使循环跳过其主体的剩余部分,并在重新迭代之前立即重新测试其条件。

无限循环

如果满足以下条件,则循环变为无限循环never 变为 false。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

公共访问说明符

公共访问说明符允许类将其成员变量和成员函数暴露给其他函数和对象。任何公共成员都可以从类外部访问。

以下示例说明了这一点 -

using System;

namespace RectangleApplication {

   class Rectangle {
      //成员变量
      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,因此可以使用名为 r 的 Rectangle 类实例从 Main() 函数访问它们。

成员函数 Display()GetArea() 也可以直接访问这些变量,而无需使用任何类实例。

成员函数 Display() 也被声明为 public,因此也可以使用名为 r 的 Rectangle 类实例从 Main() 函数访问它。

私有访问说明符

私有访问说明符允许类向其他函数和对象隐藏其成员变量和成员函数。只有同一类的函数才能访问其私有成员。即使是类的实例也无法访问其私有成员。

以下示例说明了这一点 -

using System;

namespace RectangleApplication {

   class Rectangle {
      //成员变量
      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,因此可以使用名为 r 的 Rectangle 类实例从 Main() 访问它们。

受保护访问说明符

受保护访问说明符允许子类访问其基类的成员变量和成员函数。这有助于实现继承。我们将在继承章节中更详细地讨论这一点。

内部访问说明符

内部访问说明符允许类将其成员变量和成员函数暴露给当前程序集中的其他函数和对象。换句话说,任何带有内部访问说明符的成员都可以从定义该成员的应用程序中的任何类或方法访问。

以下程序演示了这一点 -

using System;

namespace RectangleApplication {

   class Rectangle {
      //成员变量
      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

受保护的内部访问说明符

受保护的内部访问说明符允许类将其成员变量和成员函数隐藏,使其对同一应用程序中的其他类对象和函数(子类除外)不可见。这也适用于实现继承。

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) {
      /*局部变量声明*/
      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) {
         /*局部变量声明*/
         int result;
         
         if (num1 > num2)
            result = num1;
         else
            result = num2;
         return result;
      }
      
      static void Main(string[] args) {
         /* 局部变量定义 */
         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) {
         /*局部变量声明*/
         int result;
         
         if(num1 > num2)
            result = num1;
         else
            result = num2;
         
         return result;
      }
   }
   
   class Test {
   
      static void Main(string[] args) {
         /* 局部变量定义 */
         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) {
         /*局部变量声明*/
         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# 提供了一种特殊的数据类型,即 可空值 类型,您可以为其分配正常范围的值以及空值。

例如,您可以在 Nullable 变量中存储 -2,147,483,648 到 2,147,483,647 之间的任何值或空值。同样,您可以在 Nullable 变量中赋值 true、false 或空值。声明 可空值 类型的语法如下:-

< 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?();

         // 显示值
         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:

空合并运算符 (??)

空合并运算符用于可空值类型和引用类型。它用于将一个操作数转换为另一个可空(或不可空)值类型操作数的类型,其中可以进行隐式转换。

如果第一个操作数的值为空,则该运算符返回第二个操作数的值,否则返回第一个操作数的值。以下示例对此进行了解释 -

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# 中的数组

声明数组

要在 C# 中声明数组,可以使用以下语法 -

datatype[] arrayName;

其中,

  • 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 是一个包含 10 个整数的数组 */
         int i,j;

         /* 初始化数组 n 的元素 */
         for ( i = 0; i < 10; i++ ) {
            n[ i ] = i + 100;
         }
         
         /* 输出每个数组元素的值 */
         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 是一个包含 10 个整数的数组 */
         
         /* 初始化数组 n 的元素 */
         for ( int i = 0; i < 10; i++ ) {
            n[i] = i + 100;
         }
         
         /* 输出每个数组元素的值 */
         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 数组类

它定义在 System 命名空间中,是所有数组的基类,并提供用于操作数组的各种属性和方法。

C# - 字符串

在 C# 中,你可以将字符串用作字符数组。然而,更常见的做法是使用 string 关键字来声明字符串变量。 string 关键字是 System.String 类的别名。

创建字符串对象

您可以使用以下方法之一创建字符串对象 -

  • 通过将字符串文字赋值给字符串变量

  • 通过使用字符串类构造函数

  • 通过使用字符串连接运算符 (+)

  • 通过检索属性或调用返回字符串的方法

  • 通过调用格式化方法将值或对象转换为其字符串表示形式

以下示例演示了这一点 -

using System;
namespace StringApplication {
   class Program {
      static void Main(string[] args) {
         //来自字符串文字和字符串连接
         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);

        //通过使用字符串构造函数 { 'H', 'e', 'l', 'l','o' };
         string greetings = new string(letters);
         Console.WriteLine("Greetings: {0}", greetings);

         string message = String.Join(" ", sarray);
         Console.WriteLine("Message: {0}", message);

         //格式化方法来转换值
         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 Clone()

返回此字符串实例的引用。

2 CompareOrdinal()

通过计算字符串中对应字符的数值来比较两个字符串。

3 Compare()

比较使用指定规则(区分大小写、特定于文化等)连接两个指定的字符串对象。

4 Concat()

连接两个字符串对象。

5 Contains()

返回一个值,指示指定的字符串对象是否出现在此字符串中。

6 CopyTo()

将指定数量的字符从此字符串复制到字符数组中的指定位置。

7 EndsWith()

判断此字符串实例的末尾是否与指定字符串匹配。

8 Equals()

判断两个指定的 String 对象是否具有相同的值。

9 Format()

它将字符串中的格式项替换为相应对象的字符串表示形式。

10 GetEnumerator()

它检索一个可以遍历此字符串中各个字符的枚举器。

11 GetHashCode()

它返回此字符串的哈希码字符串。

12 IndexOfAny()

它在指定的字符数组中查找任意字符首次出现的索引。

13 IndexOf()

它返回此字符串中指定子字符串首次出现的从零开始的索引。

14 Insert()

在当前字符串的指定索引处插入一个字符串。

15 IsNullOrEmpty()

检查字符串是否为 null 或空。

16 IsNullOrWhiteSpace()

检查字符串是否为 null、空或仅包含空格。

17 Join()

使用指定的分隔符连接对象数组的字符串表示形式。

18 LastIndexOf()

返回字符串中指定子字符串最后一次出现的索引(从零开始)。

19 PadLeft()

使用指定字符填充字符串左侧,以达到给定的总长度。

20 PadRight()

使用指定字符填充字符串右侧,以达到给定的总长度。

21 Remove()

从当前字符串的指定位置开始,删除指定数量的字符。

22 Replace()

将所有出现的指定子字符串替换为另一个子字符串。

23 Split()

根据指定的分隔符将字符串拆分为多个子字符串。

24 StartWith()

判断此字符串实例的开头是否与指定字符串匹配。

25 Substring()

从指定索引处开始检索子字符串,直到字符串末尾。

26 ToCharArray()

转换将字符串转换为字符数组。

27 ToLower()

将字符串的所有字符转换为小写。

28 ToString()

返回对象的字符串表示形式。

29 ToUpper()

将字符串的所有字符转换为大写。

30 TrimEnd()

从当前字符串中删除所有尾随空格。

31 TrimStart()

从当前字符串中删除所有前导空格。

32 Trim()

该函数用于从当前字符串中删除所有前导和尾随空格。

本章总结了 C# 编程中常用的字符串类的关键方法。完整的方法列表和字符串类的构造函数,请访问 MSDN 库。

示例

以下示例演示了上述一些方法 -

比较字符串

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("", 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 关键字用于创建结构体。

结构体用于表示一条记录。假设您想跟踪图书馆中的图书。您可能需要跟踪每本书的以下属性 -

  • Title
  • Author
  • Subject
  • Book 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;   /* 声明 Book1 为 Book 类型 */
      Books Book2;   /* 声明 Book2 为 Book 类型 */

      /* book 1 规范 */
      Book1.title = "C Programming";
      Book1.author = "Nuha Ali"; 
      Book1.subject = "C Programming Tutorial";
      Book1.book_id = 6495407;

      /* book 2 规范 */
      Book2.title = "Telecom Billing";
      Book2.author = "Zara Ali";
      Book2.subject =  "Telecom Billing Tutorial";
      Book2.book_id = 6495700;

      /* 打印 Book1 信息 */
      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);

      /* 打印 Book2 信息 */
      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();   /* 将 Book1 声明为 Book 类型 */
      Books Book2 = new Books();   /* 将 Book2 声明为 Book 类型 */

      /* book 1 规范 */
      Book1.getValues("C Programming",
      "Nuha Ali", "C Programming Tutorial",6495407);

      /* book 2 规范 */
      Book2.getValues("Telecom Billing",
      "Zara Ali", "Telecom Billing Tutorial", 6495700);

      /* 打印 Book1 信息 */
      Book1.display();

      /* 打印 Book2 信息 */
      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 指定枚举类型名称。

  • 枚举列表 是一个以逗号分隔的标识符列表。

枚举列表中的每个符号代表一个整数值,比其前面的符号大 1。默认情况下,第一个枚举符号的值为 0。例如:-

enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

示例

以下示例演示了枚举变量的使用:-

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 {
   // 成员变量
   <access specifier> <data type> variable1;
   <access specifier> <data type> variable2;
   ...
   <access specifier> <data type> variableN;
   // 成员方法
   <access specifier> <return type> method1(parameter_list) {
      // 方法主体
   }
   <access specifier> <return type> method2(parameter_list) {
      // 方法主体
   }
   ...
   <access specifier> <return type> methodN(parameter_list) {
      // 方法主体
   }
}

注意 −

  • 访问说明符指定成员以及类本身的访问规则。如果没有指定,则类类型的默认访问说明符为 internal。成员的默认访问权限为 private

  • 数据类型指定变量的类型,返回类型指定方法返回的数据(如果有)的数据类型。

  • 要访问类成员,请使用点 (.) 运算符。

  • 点运算符将对象名称与成员名称连接起来。

以下示例说明了到目前为止讨论的概念 −

using System;

namespace BoxApplication {
   class Box {
        public double length; // 盒子的长度
        public double breadth; // 盒子的宽度
        public double height; // 盒子的高度
   }
   class Boxtester {
      static void Main(string[] args) {
        Box Box1 = new Box(); // 声明 Box 类型的 Box1
        Box Box2 = new Box(); // 声明 Box 类型的 Box2
        doublevolume = 0.0; // 在此处存储盒子的体积
        
        // 盒子 1 的规格
        Box1.height = 5.0;
        Box1.length = 6.0;
        Box1.breadth = 7.0;
        
        // 盒子 2 的规格
        Box2.height = 10.0;
        Box2.length = 12.0;
        Box2.breadth = 13.0;
        
        // 盒子 1 的体积
        volume = Box1.height * Box1.length * Box1.breadth;
        Console.WriteLine("Box1 的体积 : {0}",volume);
        
        // 盒子 2 的体积
        volume = Box2.height * Box2.length * Box2.breadth;
        Console.WriteLine("Box2 的体积:{0}",volume);
        Console.ReadKey();
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

Box1 的体积:210
Box2 的体积:1560

成员函数与封装

类的成员函数是指在类定义中拥有其定义或其原型的函数,类似于任何其他变量。它对其所属类的任何对象进行操作,并可以访问该对象的所有类成员。

成员变量是对象的属性(从设计角度来看),它们被保留为私有以实现封装。这些变量只能使用公共成员函数访问。

让我们运用上述概念来设置和获取类中不同类成员的值 -

using System;

namespace BoxApplication {
   class Box {
      private double length; // 盒子的长度
      private double breadth; // 盒子的宽度
      private double height; // 盒子的高度
      
      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(); // 声明 Box1 类型为 Box
        Box Box2 = new Box();
        double volume;
        
        // 声明 Box2 类型为 Box
        // 盒子 1 的规格
        Box1.setLength(6.0);
        Box1.setBreadth(7.0);
        Box1.setHeight(5.0);
        
        // 盒子 2 的规格
        Box2.setLength(12.0);
        Box2.setBreadth(13.0);
        Box2.setHeight(10.0);
        
        // 盒子 1 的体积
        volume = Box1.getVolume();
        Console.WriteLine("Box1 的体积:{0}",volume);
        
        // 盒子 2 的体积
        volume = Box2.getVolume();
        Console.WriteLine("Box2 的体积:{0}",volume);
        
        Console.ReadKey();
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

Box1 的体积:210
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关系。例如,哺乳动物是一种动物,狗是一种哺乳动物,因此狗也是一种动物,等等。

基类和派生类

一个类可以从多个类或接口派生,这意味着它可以从多个基类或接口继承数据和函数。

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;
   }

   // 派生类
   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);

         // 打印对象的面积。
         Console.WriteLine("Total area: {0}",  Rect.getArea());
         Console.ReadKey();
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

Total area: 35

初始化基类

派生类继承了基类的成员变量和成员方法。因此,应该在创建子类之前创建超类对象。您可以在成员初始化列表中指定超类的初始化方法。

以下程序演示了这一点 -

using System;

namespace RectangleApplication {
   class Rectangle {
      
      //成员变量
      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;
   }

   // 基类 PaintCost
   public interface PaintCost {
      int getCost(int area);
   }
   
   // 派生类
   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();
         
         // 打印对象的面积。
         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();
        
        // 调用 print 打印整数
        p.print(5);
        
        // 调用 print 打印浮点数
        p.print(500.263);
        
        // 调用 print 打印字符串
        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; // Box 的长度
      private double breadth; // Box 的宽度
      private double height; // 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;
      }
      
      // 重载 + 运算符以添加两个 Box 对象。
      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(); // 声明 Box1 为 Box 类型
        Box Box2 = new Box(); // 声明 Box2 为 Box 类型
        Box Box3 = new Box(); // 声明 Box3 为 Box 类型
        doublevolume = 0.0; // 在此处存储盒子的体积
        
        // Box1 的规格
         Box1.setLength(6.0);
         Box1.setBreadth(7.0);
         Box1.setHeight(5.0);

         // box 2 的规格
         Box2.setLength(12.0);
         Box2.setBreadth(13.0);
         Box2.setHeight(10.0);

         // Box1 的体积
         volume = Box1.getVolume();
         Console.WriteLine("Volume of Box1 : {0}", volume);

         // Box2 的体积
         volume = Box2.getVolume();
         Console.WriteLine("Volume of Box2 : {0}", volume);

         // 添加两个对象如下:
         Box3 = Box1 + Box2;

         // Box3 的体积
         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; // Box 的长度
      private double breadth; // Box 的宽度
      private double height; // 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;
      }
      
      // 重载 + 运算符以添加两个 Box 对象。
      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(); // 声明 Box1 类型为 Box
        Box Box2 = new Box(); // 声明 Box2 类型为 Box
        Box Box3 = new Box(); // 声明 Box3 类型为 Box
        Box Box4 = new Box();
        doublevolume = 0.0; // 在此处存储盒子的体积
        
        // Box1 的规格
        Box1.setLength(6.0);
        Box1.setBreadth(7.0);
        Box1.setHeight(5.0);
        
        // Box2 的规格
        Box2.setLength(12.0);
        Box2.setBreadth(13.0);
        Box2.setHeight(10.0);
        
        // 使用重载的 ToString() 显示盒子:
        Console.WriteLine("Box 1: {0}", Box1.ToString());
        Console.WriteLine("Box 2: {0}", Box2.ToString());
        
        // Box1 的体积
        volume = Box1.getVolume();
        Console.WriteLine("Volume of Box1: {0}",volume);
        
        // Box2 的体积
        volume = Box2.getVolume();
        Console.WriteLine("Volume of  Box2: {0}",volume);
        
        // 添加两个对象,如下所示:
        Box3 = Box1 + Box2;
        Console.WriteLine("Box3: {0}",Box3.ToString());
        
        // Box3 的体积
        volume = Box3.getVolume();
        Console.WriteLine("Volume of Box3 : {0}",volume);
        
        // 比较盒子

         if (Box1 > Box2)
            Console.WriteLine("Box1 is greater than Box2");
         else
            Console.WriteLine("Box1 is not 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。以下是接口声明的示例 -

public interface ITransactions {
    // 接口成员
    void showTransaction();
    double getAmount();
}

示例

以下示例演示了上述接口的实现 -

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

namespace InterfaceApplication {
   
   public interface ITransactions {
      // 接口成员
      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 namespace_name {
    // 代码声明
}

要调用启用命名空间的函数或变量版本,请在前面添加命名空间名称,如下所示:-

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 namespace 指令避免在名称空间前添加前缀。该指令告诉编译器后续代码正在使用指定命名空间中的名称。因此,以下代码将隐含该命名空间 -

让我们使用 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 {

    // 代码声明
    namespace namespace_name2 {
        // 代码声明
    }
}

您可以使用点 (.) 运算符访问嵌套命名空间的成员,如下所示 -

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 指令的表达式,该表达式的计算结果为 true。其语法如下 -

#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 类用于表示正则表达式。它具有以下常用方法 -

Sr.No. 方法 &说明
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)

将输入字符串拆分为一个子字符串数组,拆分位置由正则表达式中指定的正则表达式模式定义。构造函数。

有关方法和属性的完整列表,请阅读 Microsoft C# 文档。

示例 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# 异常处理基于四个关键字:trycatchfinallythrow

  • try − try 块标识触发特定异常的代码块。try 块后跟一个或多个 catch 块。

  • catch − 程序会在程序中需要处理问题的位置使用异常处理程序来捕获异常。 catch 关键字表示捕获异常。

  • finally − finally 块用于执行一组给定的语句,无论是否引发异常。例如,如果您打开一个文件,无论是否引发异常,都必须关闭它。

  • throw − 当出现问题时,程序会抛出异常。这是使用 throw 关键字实现的。

语法

假设一个块引发异常,则方法会使用 try 和 catch 关键字的组合来捕获异常。try/catch 块放置在可能产生异常的代码周围。 try/catch 块内的代码被称为受保护代码,使用 try/catch 的语法如下所示 -

try {
    // 引发异常的语句
} catch( ExceptionName e1 ) {
    // 错误处理代码
} catch( ExceptionName e2 ) {
    // 错误处理代码
} catch( ExceptionName eN ) {
    // 错误处理代码
} finally {
    // 待执行的语句
}

您可以列出多个 catch 语句来捕获不同类型的异常,以防您的 try 块在不同情况下引发多个异常。

C# 中的异常类

C# 异常由类​​表示。C# 中的异常类主要直接或间接地派生自 System.Exception 类。从 System.Exception 类派生的一些异常类包括 System.ApplicationExceptionSystem.SystemException 类。

System.ApplicationException 类支持由应用程序生成的异常。因此,程序员定义的异常应该从此类派生。

System.SystemException 类是所有预定义系统异常的基类。

下表列出了一些从 Sytem.SystemException 类派生的预定义异常类 -

Sr.No. 异常类 &说明
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 块的形式提供了结构化的异常处理解决方案。使用这些块,核心程序语句与错误处理语句分离。

这些错误处理块使用 trycatchfinally 关键字实现。以下是在除以零的条件发生时抛出异常的示例 -

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 枚举器的成员包括 -

  • Append - 打开一个现有文件并将光标置于文件末尾,如果文件不存在,则创建文件。

  • Create - 创建一个新文件。

  • CreateNew - 指定操作系统创建一个新文件。

  • Open - 打开一个现有文件。

  • OpenOrCreate - 指定操作系统如果文件存在则打开文件,否则创建新文件。

  • Truncate - 打开一个现有文件并将其大小截断为零字节。

2

FileAccess

FileAccess 枚举器包含以下成员:ReadReadWriteWrite

3

FileShare

FileShare 枚举器包含以下成员 -

  • Inheritable - 允许文件句柄将继承传递给子进程

  • None - 拒绝共享当前文件

  • Read - 允许打开文件进行读取。

  • ReadWrite - 允许打开文件进行读取和写入

  • Write − 允许打开文件进行写入

示例

以下程序演示了 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 读取和写入文本文件

本示例涉及读取和写入文本文件。 StreamReaderStreamWriter 类可帮助实现此操作。

2 读取和写入二进制文件

这涉及读取和写入二进制文件。BinaryReaderBinaryWriter 类可帮助实现此操作。

3 操作 Windows 文件系统

它使 C# 程序员能够浏览和定位 Windows 文件和目录。

C# - 属性

属性是一种声明性标签,用于向运行时传递程序中各种元素(例如类、方法、结构体、枚举器、程序集等)行为的信息。您可以使用属性向程序添加声明性信息。声明性标签用方括号 ([ ]) 表示,该方括号位于其所针对的元素上方。

属性用于向程序添加元数据,例如编译器指令和其他信息,例如注释、描述、方法和类。 .Net 框架提供两种类型的属性:预定义属性和自定义属性。

指定属性

指定属性的语法如下:-

[attribute(positional_parameters, name_parameter = value, ...)]
element

属性的名称及其值在应用该属性的元素前的方括号内指定。位置参数指定必需信息,名称参数指定可选信息。

预定义属性

.Net 框架提供了三个预定义属性 -

  • AttributeUsage
  • Conditional
  • Obsoleated

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)]

Conditional

此预定义属性标记一个条件方法,其执行取决于指定的预处理标识符。

它会根据指定的值(例如 DebugTrace)对方法调用进行条件编译。例如,它在调试代码时显示变量的值。

指定此属性的语法如下:-

[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 (
   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 框架允许创建自定义属性,这些属性可用于存储声明性信息,并可在运行时检索。根据设计标准和应用需求,此信息可以与任何目标元素关联。

创建和使用自定义属性包含四个步骤 -

  • 声明自定义属性
  • 构造自定义属性
  • 将自定义属性应用于目标程序元素
  • 通过反射访问属性

最后一步是编写一个简单的程序来读取元数据以查找各种符号。元数据是关于数据的数据或用于描述其他数据的信息。该程序应该使用反射在运行时访问属性。我们将在下一章讨论这一点。

声明自定义属性

新的自定义属性应该派生自 System.Attribute 类。例如:

//一个自定义属性 BugFix,赋值给一个类及其成员
[AttributeUsage(
   AttributeTargets.Class |
   AttributeTargets.Constructor |
   AttributeTargets.Field |
   AttributeTargets.Method |
   AttributeTargets.Property,
   AllowMultiple = true)]

public class DeBugInfo : System.Attribute

在上面的代码中,我们声明了一个名为 DeBugInfo 的自定义属性。

构造自定义属性

让我们构造一个名为 DeBugInfo 的自定义属性,它用于存储调试任何程序时获得的信息。它存储以下信息:

  • 错误的代码
  • 发现错误的开发人员姓名
  • 上次代码审查日期
  • 用于存储开发人员备注的字符串消息

DeBugInfo 类有三个私有属性用于存储前三个信息,以及一个公共属性用于存储消息。因此,错误编号、开发人员姓名和审核日期是 DeBugInfo 类的位置参数,而消息是可选参数或命名参数。

每个属性必须至少有一个构造函数。位置参数应通过构造函数传递。以下代码展示了 DeBugInfo 类 -

//一个自定义属性 BugFix,将被赋值给一个类及其成员
[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 {
   //成员变量
   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# - 反射

Reflection(反射)对象用于在运行时获取类型信息。允许访问正在运行的程序元数据的类位于 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 {
   //将自定义属性 BugFix 分配给类及其成员
   [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 {
      //成员变量
      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);
         
         //遍历 Rectangle 类的属性
         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);
            }
         }
         
         //迭代方法属性
         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的类,其中包含age、name和code等私有字段。我们不能从类作用域之外直接访问这些字段,但我们可以拥有访问这些私有字段的属性。

访问器

属性的访问器包含可执行语句,用于获取(读取或计算)或设置(写入)属性。访问器声明可以包含 get 访问器、set 访问器或两者。例如 -

// 声明一个字符串类型的 Code 属性:
public string Code {
   get {
      return code;
   }
   set {
      code = value;
   }
}

// 声明一个字符串类型的 Name 属性:
public string Name {
   get {
      return name;
   }
   set {
      name = value;
   }
}

// 声明一个 int 类型的 Age 属性:
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;
      
      // 声明一个字符串类型的 Code 属性:
      public string Code {
         get {
            return code;
         }
         set {
            code = value;
         }
      }
      
      // 声明一个字符串类型的 Name 属性:
      public string Name {
         get {
            return name;
         }
         set {
            name = value;
         }
      }
      
      // 声明一个 int 类型的 Age 属性:
      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() {
      
         // 创建一个新的 Student 对象:
         Student s = new Student();
         
         // 设置学生代码、姓名和年龄
         s.Code = "001";
         s.Name = "Zara";
         s.Age = 9;
         Console.WriteLine("Student Info: {0}", s);
         
         //增加年龄
         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;
      
      // 声明一个字符串类型的 Code 属性:
      public string Code {
         get {
            return code;
         }
         set {
            code = value;
         }
      }
      
      // 声明一个字符串类型的 Name 属性:
      public override string Name {
         get {
            return name;
         }
         set {
            name = value;
         }
      }
      
      // 声明一个 int 类型的 Age 属性:
      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() {
         // 创建一个新的 Student 对象:
         Student s = new Student();
         
         // 设置学生代码、姓名和年龄
         s.Code = "001";
         s.Name = "Zara";
         s.Age = 9;
         Console.WriteLine("Student Info:- {0}", s);
         
         //增加年龄
         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] {

    // get 访问器。
    get {
        // 返回索引指定的值
    }
    
    // set 访问器。
    set {
        // 设置索引指定的值
    }
}

索引器的使用

索引器的行为声明在某种程度上类似于属性的声明。与属性类似,您可以使用 getset 访问器来定义索引器。但是,属性返回或设置特定的数据成员,而索引器则返回或设置对象实例中的特定值。换句话说,它将实例数据分解为更小的部分,并对每个部分进行索引,并获取或设置每个部分。

定义属性需要提供属性名称。索引器不是通过名称来定义的,而是通过 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";
         
         //使用带有 int 参数的第一个索引器
         for (int i = 0; i < IndexedNames.size; i++) {
            Console.WriteLine(names[i]);
         }
         
         //使用带有字符串参数的第二个索引器
         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) {
         //创建委托实例
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         
         //使用委托对象调用方法
         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) {
         //创建委托实例
         NumberChanger nc;
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         
         nc = nc1;
         nc += nc2;
         
         //调用多播
         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;
      
      // 委托声明
      public delegate void printString(string s);

      // 此方法打印到控制台
      public static void WriteToScreen(string str) {
         Console.WriteLine("The String is: {0}", str);
      }
      
      //此方法打印到文件
      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();
      }
      
      // 此方法将委托作为参数并使用它来
      // 根据需要调用方法
      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

它表示一个先进先出的对象集合。

当需要先进先出访问项目时使用它。在列表中添加项目时,它被称为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) {
         
        //声明一个 int 数组
        MyGenericArray<int> intArray = new MyGenericArray<int>(5);
        
        //设置值
        for (int c = 0; c < 5; c++) {
        intArray.setItem(c, c*5);
        }
        
        //获取值
        for (int c = 0; c < 5; c++) {
        Console.Write(intArray.getItem(c) + " ");
        }
        
        Console.WriteLine();
        
        //声明一个字符数组
        MyGenericArray<char> charArray = new MyGenericArray<char>(5);
        
        //设置值
        for (int c = 0; c < 5; c++) {
        charArray.setItem(c, (char)(c+97));
        }
        
        //获取值
         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';
         
         //交换前显示值:
         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);
         
         //交换后显示值:
         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) {
         //创建委托实例
         NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
         NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
         
         //使用委托对象调用方法
         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) {
        //使用匿名方法创建委托实例
        NumberChanger nc = delegate(int x) {
        Console.WriteLine("Anonymous Method: {0}", x);
        };
        
        //使用匿名方法调用委托
        nc(10);
        
        //使用命名方法实例化委托
        nc = new NumberChanger(AddNum);
        
        //使用命名方法调用委托
        nc(5);
        
        //使用其他命名方法实例化委托
        nc = new NumberChanger(MultNum);
        
        //使用命名方法调用委托
        nc(2);
        Console.ReadKey();
      }
   }
}

当编译并执行上述代码时,它会产生以下结果 -

Anonymous Method: 10
Named Method: 15
Named Method: 30

C# - Unsafe 代码

C# 允许在带有 unsafe 修饰符的代码块函数中使用指针变量。非安全代码或非托管代码是指使用了 指针 变量的代码块。

指针

指针是一个变量,其值是另一个变量的地址,即内存位置的直接地址。与任何变量或常量类似,必须先声明指针,然后才能使用它来存储任何变量的地址。

指针声明的一般形式为:-

type *var-name;

以下是有效的指针声明:-

int *ip; 	/* 指向整数的指针 */
double *dp; /* 指向双精度浮点数的指针 */
float *fp; 	/* 指向浮点数的指针 */
char *ch 	/* 指向字符的指针*/

以下示例演示了在 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)
         
         /* 让我们在指针中拥有数组地址 */
         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

获取或设置资源管理器在运行时查找特定于文化的资源时使用的当前文化。

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()

取消当前线程的中止请求。

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() 方法来开始子线程的执行。

The following program demonstrates the concept −

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");
         
         // 线程暂停 5000 毫秒
         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");
            
            // 做一些工作,比如数到 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();
         
         //停止主线程一段时间
         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