C#学习小笔记(完整版)—— Patience

PS:国庆放假回家把C#又重新过了一遍,之前本科的时候学过,读研之后主要玩python,工作之后玩C++,项目需要,故重新快速过了一遍C#,写此博文方便后续复习使用,无任何商业用途。

本博文学习参考资料:C语言中文网------C#教程,如有侵权,联系立删。

1,环境

有.NetFramework平台都可以运行C#

需要确定在环境变量里面存在:C:\Windows\Microsoft.NET\Framework\v4.0.30319

编辑好C#代码(YY.cs),cmd中可以运行(csc YY.cs),即可在当前路径下生成一个可执行文件(YY.exe),双击即可运行

csharp 复制代码
using System;

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

成员变量 是用来存储 中要使用的数据或属性的。

成员函数 (也可以称为成员方法)是执行特定任务的语句集,一个的成员函数需要在类中声明。

通过一个已有的类(class)创建出这个类的对象(object)的过程叫做类的实例化,类的实例化需要使用 new 关键字。

标识符是用来为类、变量、函数或任何其他自定义内容命名。C# 中标识符的定义规则如下所示:

  • 标识符必须以英文字母A-Z、a-z开头,后面可以跟英文字母A-Z、a-z、数字0-9或下划线_
  • 标识符中的第一个字符不能是数字;
  • 标识符中不能包含空格或特殊符号,例如? - + ! @ # % ^ & * ( ) [ ] { } . ; : " ' / \,但是可以使用下划线_
  • 标识符不能是 C# 关键字。

2,关键字

在关键字前面加上@前缀即可,例如@if就是一个有效的标识符,而if则是一个关键字。

3,数据类型

数据类型用来指定程序中变量可以存储的数据的类型,C# 中的数据类型可以大致分为三类:

  • 值类型(Value types);
  • 引用类型(References types);
  • 指针类型(Pointer types)。

①值类型

类型 描述 范围 默认值
bool 布尔值 True 或 False False
byte 8 位无符号整数 0 到 255 0
char 16 位 Unicode 字符 U +0000 到 U +ffff '\0'
decimal 128 位精确的十进制值,具有 28~29 个有效位数 (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 0.0M
double 64 位双精度浮点型 (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 0.0D
float 32 位单精度浮点型 -3.4 x 1038 到 + 3.4 x 1038 0.0F
int 32 位有符号整数类型 -2,147,483,648 到 2,147,483,647 0
long 64 位有符号整数类型 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 0L
sbyte 8 位有符号整数类型 -128 到 127 0
short 16 位有符号整数类型 -32,768 到 32,767 0
uint 32 位无符号整数类型 0 到 4,294,967,295 0
ulong 64 位无符号整数类型 0 到 18,446,744,073,709,551,615 0
ushort 16 位无符号整数类型 0 到 65,535 0

可以使用 sizeof 方法获取类型或变量的确切大小

csharp 复制代码
using System;
namespace c.biancheng.net{
    class Program {
        static void Main(string[] args) {
            Console.WriteLine("int 类型的大小为: {0}", sizeof(int));//int 类型的大小为: 4
            int b = 123;
            Console.WriteLine(Marshal.SizeOf(b));//4    
            Console.ReadLine();
        }
    }
}

②引用类型

引用类型的变量中存储的是数据在内存中的位置

当多个变量都引用同一个内存地址时,如果其中一个变量改变了内存中数据的值,那么所有引用这个内存地址的变量的值都会改变

C# 中内置的引用类型包括 Object(对象)、Dynamic(动态)和 string(字符串)。

Ⅰ Object

Object 是 System.Object 类的别名,任何类型的值都可以分配给对象类型,但是在分配值之前,需要对类型进行转换。

将值类型转换为对象类型的过程被称为"装箱",反之将对象类型转换为值类型的过程则被称为"拆箱"。

注意,只有经过装箱的数据才能进行拆箱。

对象类型变量的类型检查是在编译时进行的

Ⅱ Dynamic

可以在动态类型的变量中存储任何类型的值 ,这些变量的类型检查是在程序运行时进行的。

csharp 复制代码
dynamic <variable_name> = value;
dynamic d = 20; //这个d在程序运行时被确定为int值

动态类型变量的类型检查是在程序运行时进行的。

Ⅲ string

在 C# 中有两种定义字符串类型的方式,分别是使用" "@" "

使用@" "形式声明的字符串称为"逐字字符串",逐字字符串会将转义字符\当作普通字符对待

例如string str = @"C:\Windows";等价于string str = "C:\\Windows";

csharp 复制代码
//使用引号的声明方式
String str = "http://c.biancheng.net/";
//使用 @ 加引号的声明形式
@"http://c.biancheng.net/";

Ⅳ 指针

C# 语言中的指针是一个变量,也称为定位器或指示符,其中可以存储另一种类型的内存地址

C# 中的指针与 C 或 C++中的指针具有相同的功能

type* identifier;

csharp 复制代码
char* cptr;
int* iptr;

4,变量

C# 中的基本变量类型可以归纳为以下几种

类型 示例
整型(整数类型) sbyte、byte、short、ushort、int、uint、long、ulong、char
浮点型 float、double
十进制类型 decimal
布尔型 true、false
空类型 可为空值的数据类型

①声明变量

csharp 复制代码
data_type variable_list;
//其中,data_type 为变量的类型,可以是 C# 中任何有效的数据类型,例如 char、int、float 等,也可以是我们自定义的数据类型;
//variable_list 为要声明的变量名称(标识符),variable_list 中可以包含多个变量名称,每个变量名之间使用逗号进行分隔,这样我们就可以同时定义多个变量

int i, j, k;
double a;
char b, c;
float d;

C# 中变量的名称并不是可以随意定义的,需要遵循如下所示的规则:

  • 变量名中可以包含英文字母a-z, A-Z、数字0-9和下划线_
  • 变量名只能以英文字母a-z, A-Z或下划线_开头,不能以数字开头;
  • 变量名中不允许使用空格;
  • 变量名不能是任何 C# 中的保留字或关键字,例如 char、float 等。

private 只能在类中访问

static 只能通过类名访问

②初始化变量

csharp 复制代码
variable_name = value;//C# 中变量可以通过等号后跟一个常量表达式的形式进行初始化(赋值)
data_type variable_name = value;//也可以在变量声明时直接进行初始化,只需要在声明变量后使用等号后跟一个常量表达式即可

int a, b, c;
a = 1;
b = 2;
c = 3;
char d = '';
float e = 3.14, f = 1.23;

③接受用户输入的值

ReadLine() 函数可以接受来自用户输入的内容并将其存储到变量中,由 System 命名空间中的 Console 类提供

csharp 复制代码
using System;

namespace c.biancheng.net{
    class Program {
        static void Main(string[] args) {
            int a, b;
            Console.WriteLine("请输入第一个数字:");
            a = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine("请输入第二个数字:");
            b = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine("{0}+{1}={2}", a, b, a+b);
        }
    }
}

因为使用 Console.ReadLine() 接收的数据是字符串格式的,所以示例中我们需要使用 Convert.ToInt32() 函数来将用户输入的数据转换为 int 类型

④Lvalues 和 Rvalues 表达式

  • Lvalues:又称左值,左值表达式可以出现在赋值语句的左边或右边;
  • Rvalues:又称右值,右值表达式只可以出现在赋值语句的右边。

左值有地址,会开辟内存空间;右值没地址内存分配

csharp 复制代码
int age = 27;//age是左值,可以赋值

123 = 321;//数字是右值,不可以赋值

5,数据类型转换

数据类型转换就是将一种类型的数据转换为另一种类型

有两种形式的类型转换方式,分别是隐式类型转换和显示类型转换

①隐式转换

隐式类型转换是由C#以类型安全的方式执行的,转换的过程中不会导致数据丢失,由小到大

隐式转换不需要编写额外的代码

csharp 复制代码
int a = 10;
double b = a;

②显示转换

显式类型转换也叫强制类型转换,这种转换需要使用(type)value的形式或者预定义函数显式的完成

显式转换需要用户明确的指定要转换的类型,而且在转换的过程中可能会造成数据丢失

csharp 复制代码
using System;

namespace c.biancheng.net
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            double d = 5673.74;
            int i;

            i = (int)d; // 将 double 类型转换为 int
            Console.WriteLine("转换前{0},转换后{1}", d, i);
            Console.WriteLine(i.ToString()); // 转换为string
            
            Console.ReadKey();
        }
    }
}

转换前5673.74,转换后5673

内置的类型转换方法

方法 描述
ToBoolean 将类型转换为布尔型
ToByte 将类型转换为字节类型
ToChar 将类型转换为单个 Unicode 字符类型
ToDateTime 将类型(整数或字符串类型)转换为日期时间的结构
ToDecimal 将浮点型或整数类型转换为十进制类型
ToDouble 将类型转换为双精度浮点型
ToInt16 将类型转换为 16 位整数类型
ToInt32 将类型转换为 32 位整数类型
ToInt64 将类型转换为 64 位整数类型
ToSbyte 将类型转换为有符号字节类型
ToSingle 将类型转换为小浮点数类型
ToString 将类型转换为字符串类型
ToType 将类型转换为指定类型
ToUInt16 将类型转换为 16 位无符号整数类型
ToUInt32 将类型转换为 32 位无符号整数类型
ToUInt64 将类型转换为 64 位无符号整数类型

6,运算符

运算符其实就是一个符号,用来告诉编译器执行特定的数学或逻辑运算。大致可以分为如下几类:

  • 算术运算符;
  • 关系运算符;
  • 逻辑运算符;
  • 位运算符;
  • 赋值运算符;
  • 其它运算符。

①算术运算符

A++,表示先用再加

--A,表示先减再用

用可以理解为输出的意思

运算符 描述 实例(假设变量 A = 10,变量 B = 20)
+ 加法运算符,对运算符左右两边的操作数执行加法操作 A + B 值为 30
- 减法运算符,对运算符左右两边的操作数执行减法操作 A - B 值为 -10
* 乘法运算符,将运算符左右两边的操作数相乘 A * B 值为 200
/ 除法运算符,使用运算符左边的操作数除以右边的操作数 B / A 值为 2
% 取模运算符,整除后的余数 B % A 值为 0
++ 自增运算符,整数值增加 1 A++ 值为 11
-- 自减运算符,整数值减少 1 A-- 值为 9
csharp 复制代码
using System;

namespace beyond.yy
{
    class Beyond
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;
            Console.WriteLine(a++);//10
            Console.WriteLine(++b);//21

            Console.WriteLine(a);//11
            Console.WriteLine(b);//21
        }
    }
}

②关系运算符

运算符 描述 实例(假设变量 A = 10,变量 B = 20)
== 检查两个操作数的值是否相等,如果相等则条件为真 (A == B) 不为真
!= 检查两个操作数的值是否相等,如果不相等则条件为真 (A != B) 为真
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真 (A > B) 不为真
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真 (A < B) 为真
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真 (A >= B) 不为真
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真 (A <= B) 为真

③逻辑运算符

运算符 描述 实例(假设变量 A 是 true,变量 B 是 false) 备注
&& 逻辑与运算符,如果两个操作数都为真,则结果为真 (A && B) 为假 俩都为真才为真
|| 逻辑或运算符,如果两个操作数中有任意一个为真,则结果为真 (A || B) 为真 一个为真就是真
! 逻辑非运算符,用来对操作数的逻辑状态取反,如果结果为真,那么取反后则为假 !(A && B) 为真 取反p

④位运算符

位运算符用来对二进制位进行操作,&、| 和 ^ 的真值表如下:

p q p & q p | q p ^ q 备注
0 0 0 0 0 &与操作,有一个0就是0
0 1 0 1 1 或|操作,有一个1就是1
1 1 1 1 0 异或^,俩不一样就是1,俩一样就是0
1 0 0 1 1
运算符 描述 实例(假设变量 A = 60,变量 B = 13)
& 按位与,对两个操作数的二进制位进行按位与运算,即当两个数对应的二进制位均为 1 时,结果位为 1,否则为 0 (A & B) 将得到 12,即为 0000 1100
| 按位或,对两个操作数的二进制位进行按位或运算,即当两个数对应的二进制位有一个为 1 时,结果就为 1,否则为 0 (A | B) 将得到 61,即为 0011 1101
^ 按位异或,对两个操作数的二进制位进行按位异或运算,即当两个数对应的二进制位不同时,结果为 1,否则为 0 (A ^ B) 将得到 49,即为 0011 0001
~ 按位取反,该运算符具有"翻转"位效果,即 0 变成 1,1 变成 0,包括符号位 (~A ) 将得到 -61,即为 1100 0011
<< 二进制左移运算符,左操作数的值向左移动右操作数指定的位数 A << 2 将得到 240,即为 1111 0000
>> 二进制右移运算符,左操作数的值向右移动右操作数指定的位数 A >> 2 将得到 15,即为 0000 1111

⑤赋值运算符

运算符 描述 实例
= 最简单的赋值运算符,把右边操作数的值赋给左边的操作数 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
csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            int a = 15;   //0000 0000 0000 0000 0000 0000 0000 1111
            int b = a<<2; //0000 0000 0000 0000 0000 0000 0011 1100

            Console.WriteLine(a);//15
            Console.WriteLine(b);//32+16+8+4 = 60
        }
    }
}

⑥其它运算符

运算符 描述 实例
sizeof() 返回数据类型的大小 sizeof(int),将返回 4
typeof() 返回 class 的类型 typeof(StreamReader);
& 返回变量的地址 &a 将得到变量的实际地址
* 变量的指针 *a; 将指向一个变量。
? : 三元(三目)运算符 a>b ? X : Y; 如果条件为真,则值为 X : 否则值为 Y
is 判断对象是否为某一类型 if( Ford is Car) // 检查 Ford 是否是 Car 类的一个对象
as 强制转换,即使转换失败也不会抛出异常。 Object obj = new StringReader("Hello"); StringReader r = obj as StringReader;

7,运算符优先级


8,常量

①定义常量

常量的值在程序的编译阶段就已经确定了,而且在程序的运行期间不允许修改

csharp 复制代码
const data_type constant_name = value;
//data_type 为常量的数据类型;constant_name 为常量名(类似于变量名);value 为常量的值

②整数常量

整数常量可以是八进制、十进制或者十六进制,可以使用前缀指定具体的进制

例如0x0X表示十六进制,0表示八进制,没有前缀则表示十进制

整数常量还可以包含后缀,后缀可以是 U 和 L 的组合,U 和 L 分别表示 unsigned 和 long

后缀既可以大写也可以小写,而且可以以任意顺序进行组合,但是不能重复。

csharp 复制代码
85    // 合法:十进制常量
0213  // 合法:八进制常量
0x4b  // 合法:十六进制常量
30    // 合法:int 类型常量
30u   // 合法:无符号 int 类型常量
30l   // 合法:long 类型常量
30ul  // 合法:无符号 long 类型常量
068   // 非法:8 不是一个八进制数字
032UU  // 非法:后缀不能重复

③浮点常量

浮点常量由整数部分、小数点、小数部分和指数部分组成,可以用小数或指数形式来表示浮点常量。

当使用小数形式表示浮点常量时,必须包含小数点、指数或同时包含两者。

当使用指数形式表示浮点常量时,必须包括整数部分、小数部分或同时包含两者。有符号的指数使用 e 或 E 表示。

csharp 复制代码
3.14159       // 合法
314159E-5L    // 合法
510E         // 非法:不完全指数
210f         // 非法:没有小数或指数
.e55         // 非法:缺少整数或小数

④字符常量

字符常量需要使用单引号括起来,类似于定义字符串类型的变量,例如'x'

一个字符常量可以是一个普通字符(例如'x')、转义序列(例如'\t')或 Unicode 字符(例如'\u02C0')。

转义序列 含义
\ \ 字符
' ' 字符
" " 字符
? ? 字符
\a Alert 或 bell
\b 退格键(Backspace)
\f 换页符(Form feed)
\n 换行符(Newline)
\r 回车
\t 水平制表符 tab
\v 垂直制表符 tab
\ooo 一到三位的八进制字符
\xhh . . . 一个或多个数字的十六进制字符

⑤字符串常量

字符串常量需要使用双引号" "@" "引起来。字符串常量与字符常量相似,可以是纯字符、转义序列或 Unicode 字符。

9,条件判断语句

  • if 语句;
  • if else 语句;
  • if else if 语句。

10,switch语句

  • switch 语句中表达式的值必须是一个整型 或者枚举类型;
  • 在一个 switch 语句中可以有任意数量的 case 语句,每个 case 关键字后面要跟一个与表达式比较的值和一个冒号;
  • case 关键字后面的值必须与 switch 中表达式的值具有相同的数据类型,并且必须是一个常量(也可以理解为是一个固定的值,不会随程序的运行发生改变);
  • 当表达式的值等于 case 中的值时,就会执行 case 后面的语句,在遇到 break 关键字时停止;
  • 当遇到 break 关键字时,switch 语句会停止运行,并跳转到 switch 语句以外的下一行代码继续运行;
  • 并不是每一个 case 语句后面都需要包含 break 关键字,如果 case 语句为空(case 语句后面没有要执行的代码),则可以不包含 break 关键字,这时程序会继续执行后续的 case 语句,直至遇到 break 关键字为止;
  • C# 不允许从一个 case 语句连续执行到下一个 case 语句,因此如果 case 语句中包含要执行的语句,就必须包含 break 关键字或其他跳转语句;
  • 一个 switch 语句的末尾可以有一个可选的 default(默认选项),当所有 case 语句都不能与表达式相匹配时则会执行 default 部分中的代码,而且 default 中的 break 语句可以省略;
  • C# 不支持从一个 case 语句跳转到另一个 case 语句,如果要从一个 case 语句跳转到另一个 case 语句的话,可以使用 goto 语句,例如goto default
csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请输入考试成绩:");
            int result = Convert.ToInt32(Console.ReadLine());
            switch (result / 10) 
            {
                case 10:
                    Console.WriteLine("一等优秀");
                    break;
                case 9:
                    Console.WriteLine("二等优秀");
                    break;
                case 8:
                    Console.WriteLine("及格");
                    break;
                case 7:
                    Console.WriteLine("勉强及格");
                    break;
                default:
                    Console.WriteLine("叫家长");
                    break;
            }
            
        }
    }
}

11,for循环

C# 中支持 for 循环、foreach 循环、while 循环和 do while 循环等循环语句。

①for循环

csharp 复制代码
for(初始化语句; 判断条件; 迭代器){ 
    // 循环主体(要执行的代码)
} 

for 循环语句的执行流程如下所示:

  • 首先执行初始化语句(通常是一个变量),并且只执行一次,在某些情况下初始化语句可以省略,只保留后面的分号即可;
  • 接下来进行条件判断,如果为 true,则执行循环主体,如果为假,则跳出 for 循环,执行 for 循环以外的代码;
  • 循环主体执行完成后,更新迭代器的值(增加或减少),然后再进行条件判断,如果为真则再次执行循环主体,重复执行此步骤,直至判断条件为假,跳出循环。

②嵌套循环

可以在一个 for 循环内再使用一个或多个 for 循环

csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            for (int i = 1; i <= 9; i++)
            {
                for (int j = 1; j <= i; j++)
                {
                    Console.Write("{0} x {1} = {2}  ", j, i, i * j);
                }
                Console.WriteLine();
            }

        }
    }
}

③无限循环

csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            for (; ; )
            {
                Console.WriteLine("滚去学习!");
            }
        }
    }
}

12,while循环

while 循环用于多次迭代一部分程序,特别是在迭代的次数不固定的情况下,建议使用 while 循环而不是for循环

循环主体可以是一个单独的语句,也可以是多条语句组成的代码块

while 循环也可以嵌套使用

csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            int i = 1;
            while (i <= 9)
            {
                int j = 1;
                while (j <= i)
                {
                    Console.Write("{0} x {1} = {2}  ", j, i, i * j);
                    j++;
                }
                i++;
                Console.WriteLine();
            }
            Console.ReadLine();
        }
    }
}

13,do while循环

与 for 循环和 while 循环不同,do while 循环需要以分号;结尾。

do while 循环中,程序会先执行do{ }中的循环主体,执行完成后再去判断while( )中的表达式,如果表达式为真,则继续执行do{ }中的循环主体,如果表达式为假,则跳出 do while 循环。

csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            int i = 1;
            do
            {
                int j = 1;
                do
                {
                    Console.Write("{0} x {1} = {2}  ", j, i, j * i);
                    j++;
                } while (j <= i);
                i++;
                Console.WriteLine();
            } while (i <= 9);
            Console.ReadLine();
        }
    }
}

14,foreach

foreach 可以遍历数组或者集合对象中的每一个元素

csharp 复制代码
foreach(数据类型 变量名 in 数组或集合对象){
    语句块;
}
csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            int[] arr = new int[100];
            for (int i = 0; i < 100; i++)
            {
                arr[i] = i + 1;
            }
            int sum = 0;
            foreach (int j in arr)
            {
                sum = sum + j;
            }
            Console.WriteLine("1~100 以内数字的和为:{0}", sum);
            Console.ReadLine();
        }
    }
}

15,跳出循环------break、continue、goto

①break

不仅可以用来终止 switch 语句,在循环语句中使用时还可以用来跳出循环,执行循环外的下一条语句。

csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            for (int i = 1; i <= 9; i++)
            {
                if (i == 5)
                {
                    break;
                }
                Console.Write("{0} ", i);
            }
            Console.ReadLine();
        }
    }
}

②continue

continue 语句并不会跳出整个循环,而是跳过本次循环继续执行下一次的循环

csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            for (int i = 1; i <= 9; i++)
            {
                if (i == 5)
                {
                    continue;
                }
                Console.Write("{0} ", i);
            }
            Console.ReadLine();
        }
    }
}

③goto

goto 语句也称为跳转语句,使用它可以控制程序跳转到指定的位置执行

csharp 复制代码
goto Labels;
语句块1;
Labels:
    语句块2;
csharp 复制代码
using System;

namespace beyond.yy
{
    class ExplicitConversion
    {
        static void Main(string[] args)
        {
            int count = 1;
        login:
            Console.WriteLine("请输入用户名");
            string username = Console.ReadLine();
            Console.WriteLine("请输入密码");
            string userpwd = Console.ReadLine();
            if (username == "HZXDW" && userpwd == "123456")
            {
                Console.WriteLine("登录成功");
            }
            else
            {
                count++;
                if (count > 3)
                {
                    Console.WriteLine("用户名或密码错误次数过多!退出!");
                }
                else
                {
                    Console.WriteLine("用户名或密码错误");
                    goto login;//返回login标签处重新输入用户名密码
                }
            }
        }
    }
}

16,注释

①单行注释

单行注释以//(双斜杠)开头,并且不需要任何结束符。需要注意的是,单行注释只对所在的行有效,//之后的内容才会被当作注释的内容,//之前的内容编译器会正常编译。

②多行注释

C# 中多行注释以/*开头并以*/结尾,使用多行注释可以跨越多行对代码进行注释,/**/之间的内容都将被视作注释的内容。在使用多行注释时,要特别注意/**/一个也不能省略。

17,函数/方法

函数(也可以称为方法)是一段具有签名(由函数名、参数类型和参数修饰符组成的函数信息)的代码块,可以用来实现特定的功能

  • 访问权限修饰符:用于指定函数对一个类的可见性;
  • 返回值类型:用于指定函数返回值的数据类型;
  • 函数名称:用于进行函数调用的唯一名称;
  • 参数列表:在调用函数时需要传递给函数的参数,参数列表是可选的,可以为空;
  • 函数主体:其中包含了实现函数功能的若干代码。

①函数声明

csharp 复制代码
Access_Specifier Return_Type FunctionName(Parameter List)
{
   Function_Body
   Return_Statement
}

Access_Specifier 为访问权限修饰符;Return_Type 为返回值类型;FunctionName 为函数名称;Parameter List 为参数列表;Function_Body 为函数主体;Return_Statement 为返回语句,用来返回数据。另外需要注意的是,访问权限修饰符、参数列表和返回语句是可选的,可以省略。

②函数调用

想要调用我们定义好的函数,首先需要将函数所在的类实例化为对象,然后通过对象.函数名() 的形式即可调用指定的函数

  • 若函数在定义时参数列表中定义了若干参数,那么在调用时也应该在函数名后面的括号中填入相应数量的参数,并且与参数列表中的参数类型一一对应;
  • 若函数在定义时没有定义参数列表,那么在调用函数时也不需要在函数名后面填入参数;
  • 对于有返回值的函数,在调用函数时可以使用一个变量(可选)来接收函数的返回值,变量的类型要与函数返回值的类型相同。

③没有参数和返回值的函数

如果函数不需要返回任何内容,则可以在定义函数时使用 void 来修饰返回值类型

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZXDW yy = new HZXDW();//实例化当前类的对象
            yy.HzPut();//调用定义好的HzPut函数

        }
        public void HzPut()
        {
            Console.WriteLine("beyondyanyu");
        }
    }
}

④有参数但没有返回值的函数

函数可以接收一个或多个数据作为参数,并在函数内部使用或处理传入的参数

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZXDW yy = new HZXDW();//实例化当前类的对象
            yy.HzPut("beyondyanyu");//调用定义好的HzPut函数

        }
        public void HzPut(string message)
        {
            Console.WriteLine(message);
        }
    }
}

⑤有参数且有返回值的函数

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZXDW yy = new HZXDW();//实例化当前类的对象
            string msg = yy.HzPut("beyondyanyu");//调用定义好的HzPut函数
            Console.WriteLine(msg);
        }
        public string HzPut(string message)
        {
            string str = "欢迎访问:" + message;
            return str;
        }
    }
}

⑥类中的静态函数

静态函数指的是,在一个类中使用 static 修饰的函数,调用静态函数比调用普通函数要简单很多,只需要函数名即可

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZXDW yy = new HZXDW();//实例化当前类的对象
            string msg = HzPut("beyondyanyu");//调用类中的静态函数,只需要函数名
            Console.WriteLine(msg);
        }
        static string HzPut(string message)
        {
            string str = "欢迎访问:" + message;
            return str;
        }
    }
}

18,封装

面向对象编程语言有三大特性,分别是封装、继承和多态

封装就是将一个或多个项目(函数)集合在一个单元中 ,这个单元称之为类,我们可以根据需要通过访问权限修饰符 来设定类中成员的范围和可见性

  • public:公共的,所有对象都可以访问,但是需要引用命名空间;
  • private:私有的,类的内部才可以访问;
  • internal:内部的,同一个程序集的对象可以访问,程序集就是命名空间;
  • protected:受保护的,类的内部或类的父类和子类中可以访问;
  • Protected internal:protected 和 internal 的并集,符合任意一条都可以访问。

①public

类中使用 public 访问权限修饰符修饰的成员变量或成员函数 可以在其他函数和对象 访问,我们可以从类的外部访问类中的公共成员(使用 public 修饰的类成员)

成员变量 length 和 width 都是使用 public 修饰的,所以可以在 Main 函数中使用 Rectangle 类的实例来进行访问

csharp 复制代码
using System;
using System.Drawing;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            Rectangle Obj = new Rectangle();
            Obj.width = 5.5;
            Obj.length = 3;
            Obj.Display();
            Console.ReadLine();
        }
        class Rectangle
        {
            // 成员变量
            public double width, length;
            public double GetArea()
            {
                return width * length;
            }
            public void Display()
            {
                Console.WriteLine("长方形的长:{0}", length);
                Console.WriteLine("长方形的宽:{0}", width);
                Console.WriteLine("长方形的面积:{0}", GetArea());
            }
        }
    }
}

②private

类中使用 private 访问权限修饰符修饰的成员变量或成员函数 不能在其它函数或对象 访问,即使是类的实例也不能访问这个类中的私有成员 ,只有同属一个类的函数才可以访问这个类的私有成员

成员变量 length 和 width 使用 private 修饰,因此无法通过 HZXDW类中的 Main 函数访问,只能通过 Rectangle 类中的成员函数 AcceptDetails() 和 Display() 来访问这些变量。又因为 Rectangle 中的成员函数 AcceptDetails() 和 Display() 是使用 public 修饰的,因此可以使用 Rectangle 类的实例 Obj 来调用它们。

csharp 复制代码
using System;
using System.Drawing;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            Rectangle Obj = new Rectangle();
            Obj.AcceptDetails();
            Obj.Display();
            Console.ReadLine();
        }
    }
    class Rectangle
    {
        // 成员变量
        private double width, length;
        public void AcceptDetails()
        {
            Console.WriteLine("请输入长方形的长度: ");
            length = Convert.ToDouble(Console.ReadLine());
            Console.WriteLine("请输入长方形的宽度: ");
            width = Convert.ToDouble(Console.ReadLine());
        }
        public double GetArea()
        {
            return width * length;
        }
        public void Display()
        {
            Console.WriteLine("长方形的长:{0}", length);
            Console.WriteLine("长方形的宽:{0}", width);
            Console.WriteLine("长方形的面积:{0}", GetArea());
        }
    }
}

③protected

类中使用 protected 访问权限修饰符修饰的成员变量和成员函数可以在其子类中访问,也就是说基类(父类)中使用 protected 访问权限修饰符修饰的成员变量和成员函数可以在其子类中访问,这样有助于实现继承

④internal

类中使用 internal 访问权限修饰符修饰的成员变量和成员函数可以在当前程序集 中的其他函数或对象中使用

任何使用 internal 修饰的成员都可以被同一命名空间下的任何类或方法访问

csharp 复制代码
using System;
using System.Drawing;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            Rectangle Obj = new Rectangle();
            Obj.width = 10;
            Obj.length = 5;
            Obj.Display();
            Console.ReadLine();
        }
    }
    class Rectangle
    {
        // 成员变量
        internal double width, length;
        public double GetArea()
        {
            return width * length;
        }
        public void Display()
        {
            Console.WriteLine("长方形的长:{0}", length);
            Console.WriteLine("长方形的宽:{0}", width);
            Console.WriteLine("长方形的面积:{0}", GetArea());
        }
    }
}

⑤protected internal

类中使用 protected internal 访问权限修饰符修饰的成员可以在本类、派生类或者包含该类(使用 using 引入)的程序集中访问,在实现继承时也使用此方法。

19,值传递、引用传递、输出传递

  • 形式参数:在定义函数阶段参数列表中定义的参数称之为形式参数,简称形参,可以将它看作变量的名称它没有具体的值,只是用来接收函数调用时传递过来的数据;
  • 实际参数:在函数被调用时传递给函数的参数称之为实际参数,简称实参,可以将它看作变量的值,用来为形参赋值。
方式 描述
值传递 值传递会复制参数的实际值并赋值给函数的形式参数,实参和形参使用的是两个不同内存位置中的值,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全
引用传递 引用传递会复制参数的内存位置并传递给形式参数,当形参的值发生改变时,同时也会改变实参的值
输出传递 输出传递可以一次返回多个值

①值传递

值传递是将参数传递给函数的默认方式

在函数内部对形参 val 的值进行的修改,但是并不会影响函数外面实参 val 的值

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int val = 10;
            HZXDW Obj = new HZXDW();
            Console.WriteLine("调用函数之前:{0}", val);//10
            Obj.Func(val);
            Console.WriteLine("调用函数之后:{0}", val);//10
        }
        public void Func(int val)
        {
            val = val + 10;
            Console.WriteLine("函数内部的值:{0}", val);//20
        }
    }
}

②引用传递

引用传递是对变量内存位置的引用。与值传递不同,使用引用传递的形式传递参数时,并不会为形参创建新的内存地址,而是与实参共同指向相同的内存地址

当修改形参的值时,实参的值也会被修改

使用 ref 关键字来使用引用传递

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int val = 10;
            HZXDW Obj = new HZXDW();
            Console.WriteLine("调用函数之前:{0}", val);//10
            Obj.Func(ref val);
            Console.WriteLine("调用函数之后:{0}", val);//20
        }
        public void Func(ref int val)
        {
            val = val + 10;
            Console.WriteLine("函数内部的值:{0}", val);//20
        }
    }
}

③输出传递

使用 return 语句可以从函数中返回一个值;使用输出传递 则可以从函数中一次性返回多个值

输出传递与引用传递相似,不同之处在于输出传递是将数据从函数中传输出来而不是传输到函数中

使用 out 关键字来使用输出传递

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int val = 5;
            HZXDW Obj = new HZXDW();
            Console.WriteLine("调用函数之前 val 的值:{0}", val);//5
            Obj.getValue(out val);
            Console.WriteLine("调用函数之后 val 的值:{0}", val);//31
        }
        public void getValue(out int x)
        {
            int temp = 11;
            x = temp;
            x = x + 20;
        }
    }
}

在使用输出传递时,也可以不为实参赋值

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int a, b;
            HZXDW Obj = new HZXDW();
            Obj.getValue(out a, out b);
            Console.WriteLine("a 的值:{0}", a);//10
            Console.WriteLine("b 的值:{0}", b);//5
        }
        public void getValue(out int x, out int y)
        {
            x = 10;
            y = 5;
        }
    }
}

20,可空类型

①可空类型------?

使用 nullable 类型可以定义包含 null 值的数据

csharp 复制代码
data_type? variable_name = null;

data_type 为要声明的数据类型,后面紧跟一个问号;variable_name 则为变量的名称

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int? a;
            int? b = 1005;
            a = null;

            float? c = new float?();
            double? d = 20001005;
            bool? e = null;

            Console.WriteLine("a={0}\nb={1}\nc={2}\nd={3}\ne={4}",a,b,c,d,e);
            Console.ReadLine();
        }
    }
}

②Null合并运算符------??

Null 合并运算符用于定义可空类型和引用类型的默认值

如果此运算符的左操作数不为 null,那么运算符将返回左操作数,否则返回右操作数

表达式a??b中,如果 a 不为空,那么表达式的值则为 a,反之则为 b

Null 合并运算符左右两边操作数的类型必须相同,或者右操作数的类型可以隐式的转换为左操作数的类型,否则将编译错误

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int? a = null;
            int? b = 1005;
            int c;
            c = a ?? 10;//10
            Console.WriteLine("c = {0}", c);

            c = b ?? 10;//1005
            Console.WriteLine("c = {0}", c);
        }
    }
}

21,数组

数组是一个用来存储相同类型数据的、固定大小的、具有连续内存位置的顺序集合

①声明数组

csharp 复制代码
data_type[] array_name;

int[] array1;        // 声明一个整型数组
double[] array2;     // 声明一个浮点型数组

data_type 用来指定数组中元素的类型;[ ]用来指定数组的维度;array_name 为数组的名称

②初始化数组

数组是引用类型的,需要使用 new 关键字来对数组进行初始化

csharp 复制代码
int[] arr1 = new int[5];

③数组赋值

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int[] arr1;
            int[] arr2 = { 1, 2, 3, 4 };
            
            int[] arr3 = new int[3];
            int[] arr4 = new int[3] { 1, 2, 3 };
            int[] arr5 = new int[] { 1, 2, 3 };

            int[] arr6 = arr3;
        }
    }
}

④数组访问

csharp 复制代码
int[] arr = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int a = arr[0];
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int[] arr = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("arr[{0}] = {1}", i, arr[i]);
            }
            Console.ReadLine();
        }
    }
}

⑤foreach数组遍历

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int[] arr = new int[] { 100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
            int index = 0;
            foreach (int i in arr)
            {
                Console.WriteLine("arr[{0}] = {1}", index, i);
                index++;
            }
            Console.ReadLine();
        }
    }
}

22,二维数组与多维数组

要创建多维数组,需要在声明数组的方括号内添加逗号

①二维数组

csharp 复制代码
int[] arr1 = new int[5];         //1*5
int[,] arr2 = new int[2, 5];     //2*5
int[,,] arr3 = new int[3, 3, 5]; //3*3*5

②初始化二维数组

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            // 第一种方式
            int[,] arr1 = new int[3, 4]
            {
                {0, 1, 2, 3},
                {4, 5, 6, 7},
                {8, 9, 10, 11}
            };
            // 第二种方式
            int[,] arr2 = new int[,]
            {
                {0, 1, 2, 3},
                {4, 5, 6, 7},
                {8, 9, 10, 11}
            };
            // 第三种方式
            int[,] arr3 =
            {
                {0, 1, 2, 3},
                {4, 5, 6, 7},
                {8, 9, 10, 11}
            };
        }
    }
}

③二维数组访问

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int[,] arr = new int[3, 4]
            {
                {0, 1, 2, 3},
                {4, 5, 6, 7},
                {8, 9, 10, 11}
            };

            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 4; j++)
                {
                    Console.WriteLine("arr[{0},{1}] = {2}", i, j, arr[i, j]);
                }
            }
            Console.ReadLine();
        }
    }
}

23,交错数组

交错数组其实就是元素为数组的数组,换句话说就是交错数组中的每个元素都可以是维度和大小不同的数组,所以有时交错数组也被称为"数组的数组"。

①声明交错数组

csharp 复制代码
data_type[][] array_name;

int[][] jaggedArray = new int[3][];//声明一个具有三个元素的一维交错数组,并且数组中的每个元素都是一个一维的整型数组

②初始化交错数组

交错数组也需要初始化后才可以使用

交错数组中包含三个元素,第一个元素是长度为 5 的整型数组,第二个元素是长度为 4 的整型数组,第三个元素是长度为 2 的整型数组

不能从元素初始化中省略 new 运算符,因为不存在元素的默认初始化

csharp 复制代码
//方法一
int[][] jaggedArray = new int[3][]; // 定义一个交错数组
jaggedArray[0] = new int[5];        // 对数组的第一个元素初始化
jaggedArray[1] = new int[4];        // 对数组的第二个元素初始化
jaggedArray[2] = new int[2];        // 对数组的第三个元素初始化

//方法二
int[][] jaggedArray = new int[3][]; // 定义一个交错数组
jaggedArray[0] = new int[] {1, 2, 3, 4, 5};
jaggedArray[1] = new int[] {6, 7, 8, 9};
jaggedArray[2] = new int[] {10, 11};

//方法三
int[][] jaggedArray = new int[][]{
    new int[] {1, 2, 3, 4, 5},
    new int[] {6, 7, 8, 9},
    new int[] {10, 11}
};

//方法四
int[][] jaggedArray = {
    new int[] {1, 2, 3, 4, 5},
    new int[] {6, 7, 8, 9},
    new int[] {10, 11}
};

③访问数组中的元素

csharp 复制代码
int a = jaggedArray[0][1];    // 变量 a 的值为:2
jaggedArray[2][1] = 0;        // 将交错数组中第三个数组元素中的第二个元素赋值为 0

④交错数组和多维数组

交错数组中的元素不仅可以是一维数组,还可以是多维数组

包含三个二维数组元素的一维交错数组:

csharp 复制代码
int[][,] jaggedArray = new int[3][,]
{
    new int[,] {
        {1, 1},
        {2, 3}
    },
    new int[,] {
        {5, 8},
        {13, 21},
        {34, 55}
    },
    new int[,] {
        {89, 144},
        {233, 377},
        {610, 987}
    }
};

int a = jaggedArray[1][1,1]     // 变量 a 的值为 21
int b = jaggedArray[2][0,0]     // 变量 b 的值为 89
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            int[][] arr = new int[3][]{
                new int[]{31, 22, 16, 88},
                new int[]{21, 54, 6, 77, 98, 52},
                new int[]{112, 25}
            };
            // 遍历数组
            for (int i = 0; i < arr.Length; i++)
            {
                for (int j = 0; j < arr[i].Length; j++)
                {
                    Console.Write(arr[i][j] + " ");
                }
                Console.WriteLine();
            }
            Console.ReadLine();
        }
    }
}
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            string[] names = { "小明", "小红", "小强" };
            string[] course = { "语文", "数学", "英语" };
            int[][] achievement = {
                new int[]{98, 85, 90},
                new int[]{91, 95, 93},
                new int[]{88, 89, 97},
            };
            for (int i = 0; i < names.Length; i++)
            {
                string str = "";
                for (int j = 0; j < achievement[i].Length; j++)
                {
                    str += course[j] + " " + achievement[i][j] + ",";
                }
                Console.WriteLine("{0}的成绩为:{1}", names[i], str);
            }
            Console.ReadLine();
        }
    }
}

24,参数数组

参数数组通常用于为函数传递未知数量的参数

使用参数数组时,既可以直接为函数传递一个数组作为参数,也可以使用函数名(参数1, 参数2, ..., 参数n)的形式传递若干个具体的值作为参数

csharp 复制代码
访问权限修饰符 返回值类型 函数名(params 类型名称[] 数组名称)
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZXDW Obj = new HZXDW();
            string str = Obj.getSum(1, 2, 3, 4, 5, 6);
            Console.WriteLine(str);
            int[] arr = { 2, 4, 6, 8, 10 };
            string str2 = Obj.getSum(arr);
            Console.WriteLine(str2);
        }
        public string getSum(params int[] arr)
        {
            int sum = 0;
            string str = "";
            foreach (int i in arr)
            {
                sum += i;
                str += "+ " + i + " ";
            }
            str = str.Trim('+');
            str += "= " + sum;
            return str;
        }
    }
}

25,Array类

Array 类是所有数组的基类,其中提供了一系列用来处理数组的操作,例如对数组元素进行排序、搜索数组中指定的元素等。

①Array类中的属性

属性 描述
IsFixedSize 检查数组是否具有固定大小
IsReadOnly 检查数组是否为只读
IsSynchronized 检查是否同步对数组的访问(线程安全)
Length 获取数组中所有维度中元素的总数
LongLength 获取数组中所有维数中元素的总数,并返回一个 64 位整数
Rank 获取数组的秩(维数),例如一维数组返回 1,二维数组返回 2,依次类推
SyncRoot 用来获取一个对象,该对象可以用于同步对数组的访问

②Array类中的方法

方法 描述
Clear(Array, Int32, Int32) 将数组中指定范围内的元素设置为该元素所属类型的默认值
Copy(Array, Array, Int32) 从第一个元素开始拷贝数组中指定长度的元素,并将其粘贴到另一个数组中(从第一个元素开始粘贴),使用 32 位整数来指定要拷贝的长度
CopyTo(Array, Int32) 从指定的目标数组索引处开始,将当前一维数组的所有元素复制到指定的一维数组中,索引使用 32 位整数指定
GetLength 获取数组指定维度中的元素数,并返回一个 32 位整数
GetLongLength 获取数组指定维度中的元素数,并返回一个 64 位整数
GetLowerBound 获取数组中指定维度第一个元素的索引
GetType 获取当前实例的类型(继承自 Object )
GetUpperBound 获取数组中指定维度最后一个元素的索引
GetValue(Int32) 获取一维数组中指定位置的值
IndexOf(Array, Object) 在一个一维数组中搜索指定对象,并返回其首个匹配项的索引
Reverse(Array) 反转整个一维数组中元素的顺序
SetValue(Object, Int32) 设置一维数组中指定元素的值
Sort(Array) 对一维数组中的元素排序
ToString() 返回一个表示当前对象的字符串(继承自 Object)
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            // 创建一个数组并赋值 
            int[] arr = new int[6] { 15, 33, 29, 55, 10, 11 };
            // 创建一个空数组
            int[] arr2 = new int[6];
            // 获取数组的长度
            Console.WriteLine("数组 arr 的长度为:" + arr.Length);
            // 为数组排序
            Array.Sort(arr);
            Console.Write("排序后的数组 arr 为:");
            // 打印排序后的 arr
            PrintArray(arr);
            // 查找数组元素的索引
            Console.WriteLine("\n数组 arr 中值为 29 的元素的索引为:" + Array.IndexOf(arr, 29));
            // 拷贝 arr 到 arr2 
            Array.Copy(arr, arr2, arr.Length);
            Console.Write("打印数组 arr2:");
            // 打印数组 arr2 
            PrintArray(arr2);
            Array.Reverse(arr);
            Console.Write("\n反序排列数组 arr: ");
            PrintArray(arr);
        }
        // 遍历数组元素
        static void PrintArray(int[] arr)
        {
            foreach (Object elem in arr)
            {
                Console.Write(elem + " ");
            }
        }
    }
}

26,string字符串

string关键字是 System.String 类的别名

①声明和初始化字符串

  • 为 String 类型的变量赋值一个字符串;
  • 使用 String 类的构造函数;
  • 使用字符串串联运算符+
  • 通过检索属性或调用返回字符串的方法;
  • 通过调用格式化方法将值或对象转换为其字符串表示形式。
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            // 使用常规字符串为字符串变量赋值
            string name = "HZXDW";
            // 声明一个字符串并初始化为空
            string str1 = "欢迎访问:" + name;
            Console.WriteLine("str1 的值为:" + str1);
            // 使用 System.String.Empty 定义一个空字符串
            string str2 = System.String.Empty;
            Console.WriteLine("str2 的值为:" + str2);
            // 使用 System.String 类
            System.String url = "beyondyanyu"; ;
            Console.WriteLine("url 的值为:" + url);
            // 在局部变量中(即在方法体中)可以使用 var 来代替具体数据类型来定义变量
            var temp = "C#教程";
            Console.WriteLine("temp 的值为:" + temp);
            // 定义一个常量字符串
            const string str3 = "这是一个常量字符串";
            Console.WriteLine("str3 的值为:" + str3);
            // 使用字符串构造函数定义字符串
            char[] letters = { 'H', 'e', 'l', 'l', 'o' };
            // string[] letters = { "C","语","言"};
            string message = new string(letters);
            Console.WriteLine("message 的值为:" + message);
        }
    }
}

②String类的属性

String 类中提供了两个属性

属性 描述
Chars[Int32] 获取指定字符在字符串中的位置
Length 获取当前 String 对象中的字符数(字符串的长度)

③String类的方法

官网教程https://learn.microsoft.com/zh-cn/dotnet/api/system.string?view=netcore-3.1#methods

方法 描述
Clone() 返回对此 String 实例的引用
Compare(String, String) 比较两个指定的 String 对象,并返回一个指示二者在排序顺序中的相对位置的整数
CompareOrdinal(String, String) 通过比较每个字符串中的字符,来比较两个字符串是否相等
CompareTo(String) 将一个字符串与另一个字符串进行比较
Concat(String, String) 连接两个指定的字符串
Contains(String) 判断一个字符串中是否包含零一个字符串
Copy(String) 将字符串的值复制一份,并赋值给另一个字符串
CopyTo(Int32, Char[], Int32, Int32) 从字符串中复制指定数量的字符到一个字符数组中
EndsWith(String) 用来判断字符串是否以指定的字符串结尾
Equals(String, String) 判断两个字符串是否相等
Format(String, Object) 将字符串格式化为指定的字符串表示形式
GetEnumerator() 返回一个可以循环访问此字符串中的每个字符的对象
GetHashCode() 返回该字符串的哈希代码
GetType() 获取当前实例的类型
GetTypeCode() 返回字符串的类型代码
IndexOf(String) 返回字符在字符串中的首次出现的索引位置,索引从零开始
Insert(Int32, String) 在字符串的指定位置插入另一个字符串,并返回新形成的字符串
Intern(String) 返回指定字符串的内存地址
IsInterned(String) 返回指定字符串的内存地址
IsNormalized() 判断此字符串是否符合 Unicode 标准
IsNullOrEmpty(String) 判断指定的字符串是否为空(null)或空字符串("")
IsNullOrWhiteSpace(String) 判断指定的字符串是否为 null、空或仅由空白字符组成
Join(String, String[]) 串联字符串数组中的所有元素,并将每个元素使用指定的分隔符分隔开
LastIndexOf(Char) 获取某个字符在字符串中最后一次出现的位置
LastIndexOfAny(Char[]) 获取一个或多个字符在字符串中最后一次出现的位置
Normalize() 返回一个新字符串,新字符串与原字符串的值相等,但其二进制表示形式符合 Unicode 标准
PadLeft(Int32) 返回一个指定长度的新字符串,新字符串通过在原字符串左侧填充空格来达到指定的长度,从而实现右对齐
PadRight(Int32) 返回一个指定长度的新字符串,新字符串通过在原字符串右侧填充空格来达到指定的长度,从而实现左对齐
Remove(Int32) 返回一个指定长度的新字符串,将字符串中超出长度以外的部分全部删除
Replace(String, String) 使用指定字符替换字符串中的某个字符,并返回新形成的字符串
Split(Char[]) 按照某个分隔符将一个字符串拆分成一个字符串数组
StartsWith(String) 判断字符串是否使用指定的字符串开头
Substring(Int32) 从指定的位置截取字符串
ToCharArray() 将字符串中的字符复制到 Unicode 字符数组
ToLower() 将字符串中的字母转换为小写的形式
ToLowerInvariant() 使用固定区域性的大小写规则将字符串转换为小写的形式
ToString() 将其它数据类型转换为字符串类型
ToUpper() 将字符串中的字母转换为大写形式
Trim() 删除字符串首尾的空白字符
TrimEnd(Char[]) 删除字符串尾部的空白字符
TrimStart(Char[]) 删除字符串首部的空白字符

比较两个字符串是否相同

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            string str1 = "beyondyanyu";
            string str2 = "BeyondYanYu";
            if (String.Compare(str1, str2) == 0)
            {
                Console.WriteLine(str1 + " 与 " + str2 + " 相同");
            }
            else
            {
                Console.WriteLine(str1 + " 与 " + str2 + " 不同");
            }
            Console.ReadKey();
        }
    }
}

判断一个字符串是否包含在另一个字符串中

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            string str1 = "beyondyanyu";
            string str2 = "BeyondYanYu";
            if (str1.Contains(str2))
            {
                Console.WriteLine(str1 + " 中包含 " + str2);
            }
            else
            {
                Console.WriteLine(str1 + " 中不包含 " + str2);
            }
            Console.ReadKey();
        }
    }
}

从一个字符串中截取指定长度的字符串

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            string str = "beyondyanyu";
            Console.WriteLine("原字符串:" + str);
            string substr = str.Substring(7);
            Console.WriteLine("截取之后的字符串:" + substr);
        }
    }
}

将数组中的元素合并为字符串

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            string[] strarray = new string[]{
                "Beyond",
                "YanYu",
                "HZXDW"
            };
            string str = String.Join(" ", strarray);
            Console.WriteLine(str);
        }
    }
}

27,struct结构体

结构体是一种可封装数据和相关功能的值类型,在语法上结构体与类(class)非常相似,它们都可以用来封装数据,并且都可以包含成员属性和成员方法

①定义结构体

定义一个结构体需要使用 struct 关键字,每个结构体都可以被看作是一种新的数据类型,其中可以包含多个成员(成员属性和成员方法)

  • 不能为结构体声明无参数的构造函数,因为每个结构体中都已经默认创建了一个隐式的、无参数的构造函数;
  • 不能在声明成员属性时对它们进行初始化,静态属性和常量除外;
  • 结构体的构造函数必须初始化该结构体中的所有成员属性;
  • 结构体不能从其他类或结构体中继承,也不能作为类的基础类型,但是结构类型可以实现接口;
  • 不能在结构体中声明析构函数
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZ hz1;
            HZ hz2;

            hz1.title = "HZXDW";
            hz1.author = "YHZ";
            hz1.subject = "EAT";
            hz1.uuid = 1005;

            hz2.title = "HZXDW_1";
            hz2.author = "YHZ1_";
            hz2.subject = "EAT_1";
            hz2.uuid = 10051;

            Console.WriteLine("HZ1 title : {0}", hz1.title);
            Console.WriteLine("HZ1 author : {0}", hz1.author);
            Console.WriteLine("HZ1 subject : {0}", hz1.subject);
            Console.WriteLine("HZ1 uuid : {0}", hz1.uuid);

            Console.WriteLine("HZ2 title : {0}", hz2.title);
            Console.WriteLine("HZ2 author : {0}", hz2.author);
            Console.WriteLine("HZ2 subject : {0}", hz2.subject);
            Console.WriteLine("HZ2 uuid : {0}", hz2.uuid);
        }
    }

    struct HZ
    {
        public string title;
        public string author;
        public string subject;
        public int uuid;
    }
}

②结构体的特征

C# 中的结构体与 C/C++ 中的结构体有很大的不同,在 C# 中结构体具有以下功能:

  • 结构体中可以具有方法、字段、索引、属性、运算符方法和事件;
  • 结构体中可以定义构造函数,但不能定义析构函数,需要注意的是,定义的构造函数不能没有参数,因为没有参数的构造函数是 C# 默认自动定义的,而且不能更改;
  • 与类不同,结构体不能继承其他结构体或类
  • 结构体不能用作其他结构体或类的基础结构;
  • 一种结构体可以实现一个或多个接口;
  • 结构体成员不能被设定为 abstract、virtual 或 protected;
  • 与类不同,结构体可以不用 New 操作符来实例化,当使用 New 操作符来实例化结构体时会自动调用结构体中的构造函数;
  • 如果不使用 New 操作符来实例化结构体,结构体对象中的字段将保持未分配状态,并且在所有字段初始化之前无法使用该结构体实例。

③类与结构体

  • 类是引用类型,结构体是值类型;
  • 结构体不支持继承,但可以实现接口;
  • 结构体中不能声明默认的构造函数。
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZ hz1 = new HZ();
            HZ hz2 = new HZ();
            hz1.getValue("C#教程", "C语言中文网", "C#编程教程", 123456);
            hz2.getValue("C#教程", "C语言中文网", "C#编程教程", 123456);
            hz1.disPlay();
            hz2.disPlay();
        }
    }

    struct HZ
    {
        public string title;
        public string author;
        public string subject;
        public int uuid;

        public void getValue(string t, string a, string s, int id)
        {
            title = t;
            author = a;
            subject = s;
            uuid = id;
        }
        public void disPlay()
        {
            Console.WriteLine("Title:{0}", title);
            Console.WriteLine("Author:{0}", author);
            Console.WriteLine("Subject:{0}", subject);
            Console.WriteLine("Uuid:{0}", uuid);
        }
    }
}

28,枚举类型

枚举类型由一组具有独立标识符(名称)的整数类型常量构成

枚举类型不仅可以在类或结构体的内部声明,也可以在类或结构体的外部声明,默认情况下枚举类型中成员的默认值是从 0 开始的,然后逐一递增

也可以显式的为每个枚举类型的成员赋值

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        enum Day { Sun, Mon, Tue, Wed=4, Thu=8, Fri=12, Sat=16 };
        static void Main(string[] args)
        {
            Console.WriteLine("Sun = {0}", (int)Day.Sun);
            Console.WriteLine("Mon = {0}", (int)Day.Mon);
            Console.WriteLine("Tue = {0}", (int)Day.Tue);
            Console.WriteLine("Wed = {0}", (int)Day.Wed);
            Console.WriteLine("Thu = {0}", (int)Day.Thu);
            Console.WriteLine("Fri = {0}", (int)Day.Fri);
            Console.WriteLine("Sat = {0}", (int)Day.Sat);
            Console.ReadKey();
        }
    }
}

使用 GetValues() 遍历枚举类型中的所有成员

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        enum Day { Sun, Mon, Tue, Wed=4, Thu=8, Fri=12, Sat };
        //enum 
        static void Main(string[] args)
        {
            foreach (Day i in Enum.GetValues(typeof(Day))) 
            {
                Console.WriteLine("{0} = {1}", i, (int)i);
            }
            Console.ReadKey();
        }
    }
}

使用 GetNames() 遍历枚举类型中的所有成员

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        enum Day { Sun, Mon, Tue, Wed=4, Thu=8, Fri=12, Sat };
        //enum 
        static void Main(string[] args)
        {
            foreach (String s in Enum.GetNames(typeof(Day))) 
            {
                Console.WriteLine(s);
            }
            Console.ReadKey();
        }
    }
}

29,类

类是引用类型的,其中包括状态(成员属性 )和操作(成员方法和其它函数成员),我们可以动态创建类的实例(instance),这个实例也被称为对象(object),我们可以通过类和对象来设计程序

①类的定义

类的定义需要使用 class 关键字

csharp 复制代码
<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;
  • class_name 为类的名称;
  • 为数据类型,用来指定成员属性的数据类型;
  • variable1、variable2 等为成员属性的名称,类似于变量名;
  • 为返回值类型,用来指定成员函数的返回值类型;
  • method1、method2 等为成员函数的名称。

②对象

类和对象是不同的概念,类决定了对象的类型,但不是对象本身。另外,类是在开发阶段 创建的,而对象则是在程序运行期间创建的

我们可以将对象看作是基于类创建的实体,所以对象也可以称为类的实例

想要创建一个类的实例需要使用 new 关键字

csharp 复制代码
Student Object = new Student();
//前面的 Student 是我们要创建的对象类型,而 Object 则是一个变量,它引用了 Student 类实例(Student 类的对象)的内存地址。
//new 关键字在这里的作用主要是在程序运行时为类的实例分配内存。

Student Object2;//Object2并没有被赋值,所以不能使用 Object2 来访问对象中的属性和方法

//对象创建完成后如果要通过对象来访问类中的成员,则需要使用点.运算符连接对象的名称和成员的名称
Object.method();// 访问成员函数
Object.variable;// 访问成员属性

③类和对象的使用

创建一个公共的学生类,类中包括一些成员属性和成员方法,然后实例化这个类,并通过类的对象调用类中的成员属性和成员方法

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student();
            Student stu2 = new Student();
            stu1.insert(101, "张三", "男", 18);
            stu1.display();
            stu2.insert(102, "李四", "女", 16);
            stu2.display();
        }
    }
    public class Student
    {
        public int id;
        public string name;
        public string sex;
        public int age;
        public void insert(int i, string n, string s, int a)
        {
            id = i;
            name = n;
            sex = s;
            age = a;
        }
        public void display()
        {
            Console.WriteLine("编号:{0}  姓名:{1}  性别:{2}  年龄:{3}", id, name, sex, age);
        }
    }
}

30,构造函数

构造函数就是与类(或结构体)具有相同名称的成员函数 ,它在类中的地位比较特殊,不需要我们主动调用,当创建一个类的对象时会自动调用类中的构造函数

在程序开发的过程中,我们通常使用类中的构造函数来初始化类中的成员属性

C# 中的构造函数有三种:

  • 实例构造函数;
  • 静态构造函数;
  • 私有构造函数。

如果不对构造函数使用访问权限修饰符,则默认它为私有构造函数

①实例构造函数

构造函数是类中特殊的成员函数,它的名称与它所在类的名称相同,并且没有返回值

使用 new 关键字创建类的对象时,可以使用实例构造函数来创建和初始化类中的任意成员属性

只要创建 Person 类的对象,就会调用类中的实例构造函数

只需要在实例化对象时将具体的值传递给类中的构造函数即可

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        private string name_;
        private int age_;
        public HZXDW(string n,int a) 
        {
            name_ = n;
            age_ = a;

            Console.WriteLine("ctor name_ is {0}, age_ is {1}", name_, age_);
        }
        static void Main(string[] args)
        {
            HZXDW hz = new HZXDW("HZ", 18);//自动调用类中的实例构造函数
        }
    }
}

如果没有为类显式的创建构造函数,那么 C# 将会为这个类隐式的创建一个没有参数的构造函数(无参数构造函数),这个无参的构造函数会在实例化对象时为类中的成员属性设置默认值

在结构体中也是如此,如果没有为结构体创建构造函数,那么 C# 将隐式的创建一个无参数的构造函数,用来将每个字段初始化为其默认值

若要初始化静态类 或非静态类中的静态属性,则需要使用下静态构造函数

②静态构造函数

静态构造函数用于初始化类中的静态数据执行仅需执行一次的特定操作

静态构造函数将在创建第一个实例引用类中的静态成员之前自动调用。

  • 静态构造函数不使用访问权限修饰符修饰或不具有参数;
  • 类或结构体中只能具有一个静态构造函数;
  • 静态构造函数不能继承或重载;
  • 静态构造函数不能直接调用,仅可以由公共语言运行时 (CLR) 调用;
  • 用户无法控制程序中静态构造函数的执行时间;
  • 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数以初始化类;
  • 静态构造函数会在实例构造函数之前运行。
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        private static int age_ = 0;//1

        HZXDW() { age_ = 10; }//5

        static HZXDW() { age_ = 18; }//2

        static void Main(string[] args)
        {
            Console.WriteLine("age = {0} ", age_);//3
            HZXDW hz = new HZXDW();//4
            Console.WriteLine("age = {0} ", age_);//6
            Console.Read();//7  
        }
    }
}

③私有构造函数

私有构造函数是一种特殊的实例构造函数,通常用在只包含静态成员的类

如果一个类中具有一个或多个私有构造函数而没有公共构造函数 的话,那么其他类(除嵌套类外)则无法创建该类的实例

定义空的私有构造函数,这么做的好处就是空构造函数可阻止自动生成无参数构造函数

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZ.id = 18;
            HZ.name = "hz";
            HZ.Display();
            Console.ReadKey();
        }
    }
    public class HZ 
    {
        private HZ() { }
        public static int id;
        public static string name;
        public static void Display() 
        {
            Console.WriteLine("name is {0}, id is {1}", name, id);
        }
    }
}

31,析构函数

析构函数(也被称作"终结器")同样是类中的一个特殊成员函数,主要用于在垃圾回收器回收类实例时执行一些必要的清理操作

  • 析构函数只能在类中定义,不能用于结构体;
  • 一个类中只能定义一个析构函数;
  • 析构函数不能继承或重载;
  • 析构函数没有返回值;
  • 析构函数是自动调用的,不能手动调用;
  • 析构函数不能使用访问权限修饰符修饰,也不能包含参数。

析构函数的名称同样与类名相同,不过需要在名称的前面加上一个波浪号~作为前缀

析构函数不能对外公开,所以我们不能在析构函数上应用任何访问权限修饰符

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZ hz1 = new HZ();
            HZ hz2 = new HZ();
            Console.ReadLine();
        }
    }
    public class HZ 
    {
        public HZ() 
        {
            Console.WriteLine("public构造");
        }
        ~HZ() 
        {
            Console.WriteLine("析构");
        }
    }
}

32,this关键字

使用 this 关键字来表示当前对象,可以使用 this 关键字来访问类中的成员属性以及函数

①使用 this 表示当前类的对象

csharp 复制代码
using System;

namespace c.biancheng.net
{
    class Demo
    {
        static void Main(string[] args) 
        {
            Website site = new Website("C语言中文网","http://c.biancheng.net/");
            site.Display();
        }
    }
    public class Website
    {
        private string name;
        private string url;
        public Website(string n, string u){
            this.name = n;
            this.url = u;
        }
        public void Display(){
            Console.WriteLine(name +" "+ url);
        }
    }
}

②使用 this 关键字串联构造函数

csharp 复制代码
using System;

namespace c.biancheng.net
{
    class Demo
    {
        static void Main(string[] args) 
        {
            Test test = new Test("C语言中文网");
        }
    }
    public class Test
    {
        public Test()
        {
            Console.WriteLine("无参构造函数");
        }
        // 这里的 this()代表无参构造函数 Test()
        // 先执行 Test(),后执行 Test(string text)
        public Test(string text) : this()
        {
            Console.WriteLine(text);
            Console.WriteLine("实例构造函数");
        }
    }
}

③使用 this 关键字作为类的索引器

csharp 复制代码
using System;

namespace c.biancheng.net
{
    class Demo
    {
        static void Main(string[] args) 
        {
            Test a = new Test();
            Console.WriteLine("Temp0:{0}, Temp1:{1}", a[0], a[1]);
            a[0] = 15;
            a[1] = 20;
            Console.WriteLine("Temp0:{0}, Temp1:{1}", a[0], a[1]);
        }
    }
    public class Test
    {
        int Temp0;
        int Temp1;
        public int this[int index]
        {
            get
            {
                return (0 == index) ? Temp0 : Temp1;
            }
    
            set
            {
                if (0==index)
                    Temp0 = value;
                else
                    Temp1 = value;
            }
        }
    }
}

④使用 this 关键字作为原始类型的扩展方法

csharp 复制代码
using System;

namespace c.biancheng.net
{
    class Demo
    {
        static void Main(string[] args) 
        {
            string str = "C语言中文网";
            string newstr = str.ExpandString();
            Console.WriteLine(newstr);
        }
    }
    public static class Test
    {
        public static string ExpandString(this string name)
        {
            return name+" http://c.biancheng.net/";
        }
    }
}

33,static:静态成员

static 关键字声明属于类型本身而不是属于特定对象的静态成员,因此不需要使用对象来访问静态成员

类、接口和结构体 中可以使用 static 关键字修饰变量、函数、构造函数、类、属性、运算符和事件

索引器和析构函数不能是静态的

若在定义某个成员时使用 static 关键字,则表示该类仅存在此成员的一个实例,也就是说当我们将一个类的成员声明为静态成员时,无论创建多少个该类的对象,静态成员只会被创建一次,这个静态成员会被所有对象共享

①静态属性

使用 static 定义的成员属性称为"静态属性",静态属性可以直接通过类名.属性名的形式直接访问,不需要事先创建类的实例

静态属性不仅可以使用成员函数来初始化,还可以直接在类外进行初始化。

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZ.hzdw_ = "66666";
            Console.WriteLine(HZ.hzdw_);//66666

            HZ hz1 = new HZ();
            hz1.getHz();//66666

            HZ hz2 = new HZ();
            hz2.getHz();//66666
            hz2.setHz("11111");

            hz1.getHz();//11111
            hz2.getHz();//11111
        }
    }
    public class HZ 
    {
        public static string hzdw_;
        public void setHz(string s) 
        {
            hzdw_ = s;
        }
        public void getHz() 
        {
            Console.WriteLine(hzdw_);
        }
    }
}

②静态函数

使用 static 定义的成员函数称为"静态函数",静态函数只能访问静态属性

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZ.hzdw_ = "66666";
            Console.WriteLine(HZ.hzdw_);//66666

            HZ hz1 = new HZ();
            hz1.setHz("1005");
            HZ.getHz();//1005

            HZ hz2 = new HZ();
            hz2.setHz("beyondyanyu");
            HZ.getHz();//beyondyanyu
        }
    }
    public class HZ 
    {
        public static string hzdw_;
        public void setHz(string s) 
        {
            hzdw_ = s;
        }
        public static void getHz() 
        {
            Console.WriteLine(hzdw_);
        }
    }
}

34,继承

继承与封装和多态被统称为面向对象编程的三大特性

在创建一个新类时,我们可以使用这个新定义的类继承一个已有的类,通过继承可以在创建新类时重用、扩展和修改被继承类中定义的成员

被继承的类称为"基类(父类)",继承基类的类称为"派生类(子类)"

C#只支持单继承 ,也就是说一个派生类只能继承一个基类 ,但是继承是可以传递的,例如 ClassC 继承了 ClassB,而 ClassB 继承了 ClassA,那么 ClassC 将继承 ClassB 和 ClassA 中的所有成员

①基类和派生类

要使用一个类继承另一个类需要使用到冒号:

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZDW hzdw = new HZDW();
            hzdw.setHobby("eat rice and meat");
            hzdw.setBirthday(1005);

            int yy_birthday = hzdw.getHzBirthday();
            string yy_hobby = hzdw.getHzHobby();

            Console.WriteLine("birthday is {0}, hobby is {1}", yy_birthday, yy_hobby);
        }
    }
    class HZ//基类
    {
        protected string hobby_;
        protected int birthday_;
        public void setHobby(string h)
        {
            hobby_ = h;
        }
        public void setBirthday(int d)
        {
            birthday_ = d;
        }
    }
    class HZDW : HZ
    {
        public int getHzBirthday()
        {
            return birthday_;
        }

        public string getHzHobby()
        {
            return hobby_;
        }
    }
}

②多重继承

与单继承相反,多重继承则是指一个类可以同时继承多个基类,C# 并不支持多重继承,但是可以借助接口来实现多重继承

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZDW hzdw = new HZDW();
            hzdw.setHobby("eat rice and meat");
            hzdw.setBirthday(1005);

            int yy_birthday = hzdw.getHzBirthday();
            string yy_hobby = hzdw.getHzHobby();

            Console.WriteLine("birthday is {0}, hobby is {1}", yy_birthday, yy_hobby);
            Console.WriteLine(hzdw.getAll());
        }
    }
    class HZ//基类
    {
        protected string hobby_;
        protected int birthday_;
        public void setHobby(string h)
        {
            hobby_ = h;
        }
        public void setBirthday(int d)
        {
            birthday_ = d;
        }
    }

    public interface Yy { string getAll(); }

    class HZDW : HZ,Yy
    {
        public int getHzBirthday()
        {
            return birthday_;
        }

        public string getHzHobby()
        {
            return hobby_;
        }

        public string getAll() 
        {
            return birthday_.ToString() + "  " +  hobby_;
        }
    }
}

35,多态

多态(Polymorphism)是一个希腊词,指"多种形态"

  • 编译时多态:通过 C# 中的方法重载运算符重载来实现编译时多态,也称为静态绑定或早期绑定;
  • 运行时多态:通过方法重载实现的运行时多态,也称为动态绑定或后期绑定

①编译时多态

在编译期间将函数与对象链接的机制称为早期绑定,也称为静态绑定

函数重载和运算符重载可实现编译时多态,运算符重载后续章节介绍,现在主要介绍函数重载

函数重载

在同一个作用域中,可以定义多个同名的函数 ,但是这些函数彼此之间必须有所差异,比如参数个数不同参数类型不同 等等,返回值类型不同除外

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        void print(int i) { Console.WriteLine("int is : {0}",i); }
        void print(double d) { Console.WriteLine("double is : {0}", d); }
        void print(string s) { Console.WriteLine("string is : {0}", s); }
        static void Main(string[] args)
        {
            HZXDW hz = new HZXDW();
            hz.print(15);
            hz.print(10.05);
            hz.print("hzxdw");
        }
    }
}

②运行时多态

使用 abstract 关键字来创建抽象类,抽象类用于实现部分接口。另外,抽象类包含抽象方法,可以在派生类中实现。

抽象类的规则:

  • 不能创建一个抽象类的实例;
  • 不能在一个抽象类外部声明抽象方法;
  • 通过在类定义时使用 sealed 关键字,可以将类声明为密封类,密封类不能被继承,因此抽象类中不能声明密封类。
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZDW hz = new HZDW(1005,10);
            int result = hz.hzid();
            Console.WriteLine("uuid is {0}",result);
            Console.ReadKey();
        }
    }
    abstract class HZ 
    {
        public abstract int hzid();
    }

    class HZDW : HZ
    {
        private int birthday_, age_;
        public HZDW(int a,int b) 
        {
            birthday_ = b; 
            age_ = a;
        }
        public override int hzid()
        {
            return birthday_ * age_;
        }
    }
}

36,运算符重载

所谓运算符重载就是我们可以使用自定义类型来重新定义 C# 中大多数运算符的功能

运算符重载需要通过 operator 关键字后跟运算符的形式来定义的,可以将被重新定义的运算符看作是具有特殊名称的函数,与其他函数一样,该函数也有返回值类型和参数列表

对加法运算符+的重载,该函数需要两个 Box 对象的属性,并返回一个 Box 对象

csharp 复制代码
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;
}

①运算符重载的实现

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            Box box1 = new Box();
            Box box2 = new Box();
            Box box3 = new Box();
            Box box_all = new Box();
            double volume1 = 0.0, volume2 = 0.0, volume3 = 0.0, volume_all = 0.0;

            box1.setLength(1);
            box1.setWidth(2);
            box1.setHight(3);
            volume1 = box1.getVolume();
            Console.WriteLine("Box1 volume1 is : {0}", volume1);

            box2.setLength(4);
            box2.setWidth(5);
            box2.setHight(6);
            volume2 = box2.getVolume();
            Console.WriteLine("Box2 volume2 is : {0}", volume2);

            box3.setLength(7);
            box3.setWidth(8);
            box3.setHight(9);
            volume3 = box3.getVolume();
            Console.WriteLine("Box3 volume3 is : {0}", volume3);

            box_all = box1 + box2 + box3;
            volume_all = box_all.getVolume();
            Console.WriteLine("Box_all volume_all is : {0}", volume_all);

            Console.ReadKey();
        }
    }


    class Box
    {
        private double lenght_;
        private double height_;
        private double width_;
        public double getVolume(){ return lenght_ * height_ * width_; }
        public void setLength(double l) { lenght_ = l; }
        public void setHight(double h) { height_ = h; }
        public void setWidth(double w) { width_ = w; }
        public static Box operator+ (Box a, Box b) 
        {
            Box hzBox = new Box();
            hzBox.height_ = a.height_ + b.height_;
            hzBox.width_ = a.width_ + b.width_;
            hzBox.lenght_ = a.lenght_ + b.lenght_;
            return hzBox;
        }
    }
}

②可重载与不可重载的运算符

比较运算符必须成对重载,也就是说,如果重载一对运算符中的任意一个,则另一个运算符也必须重载。

比如==!=运算符、<>运算符、<=>=运算符。

运算符 可重载性
+、-、!、~、++、-- 这些一元运算符可以进行重载
+、-、*、/、%、&、|、^、<<、>>、=、!=、<、>、<=、>= 这些二元运算符可以进行重载,需要注意的是某些运算符必须成对重载
&&、|| 无法重载逻辑运算符
(type)var_name 强制类型转换运算符不能重载
+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>= 复合赋值运算符不能显式重载。 但在重载二元运算符时,也会隐式重载相应的复合赋值运算符,例如重载了+运算符也会隐式的重载+=
^、=、.、?.、? : 、??、??=、...、->、=>、as、await、checked、unchecked、default、delegate、is、nameof、new、sizeof、stackalloc、switch、typeof 这些运算符无法进行重载
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            Box box1 = new Box();
            Box box2 = new Box();
            Box box3 = new Box();
            Box box_all = new Box();
            Box box_hz = new Box();
            double volume1 = 0.0, volume2 = 0.0, volume3 = 0.0, volume_all = 0.0;

            box1.setLength(1);
            box1.setWidth(2);
            box1.setHight(3);
            Console.WriteLine(box1.ToString());
            volume1 = box1.getVolume();
            Console.WriteLine("Box1 volume1 is : {0}", volume1);

            box2.setLength(4);
            box2.setWidth(5);
            box2.setHight(6);
            Console.WriteLine(box2.ToString());
            volume2 = box2.getVolume();
            Console.WriteLine("Box2 volume2 is : {0}", volume2);

            box3.setLength(7);
            box3.setWidth(8);
            box3.setHight(9);
            Console.WriteLine(box3.ToString());
            volume3 = box3.getVolume();
            Console.WriteLine("Box3 volume3 is : {0}", volume3);

            box_all = box1 + box2 + box3;
            Console.WriteLine(box_all.ToString());
            volume_all = box_all.getVolume();
            Console.WriteLine("Box_all volume_all is : {0}", volume_all);

            if (box1 > box2) Console.WriteLine("Box1 大于 Box2");
            else Console.WriteLine("Box1 不大于 Box2");
            
            if (box1 < box2) Console.WriteLine("Box1 小于 Box2");
            else Console.WriteLine("Box1 不小于 Box2");
            
            if(box1 >=box2) Console.WriteLine("Box1 大于等于 Box2");
            else Console.WriteLine("Box1 不大于等于 Box2");

            if (box1 <= box2) Console.WriteLine("Box1 小于等于 Box2");
            else Console.WriteLine("Box1 不小于等于 Box2");

            if (box1 != box2) Console.WriteLine("Box1 不等于 Box2");
            else Console.WriteLine("Box1 等于 Box2");

            box_hz = box_all;
            if(box_hz == box_all) Console.WriteLine("Boxhz 等于 Boxall");
            else Console.WriteLine("Boxhz 不等于 Boxall");

            Console.ReadKey();
        }
    }


    class Box
    {
        private double lenght_;
        private double height_;
        private double width_;
        public double getVolume(){ return lenght_ * height_ * width_; }
        public void setLength(double l) { lenght_ = l; }
        public void setHight(double h) { height_ = h; }
        public void setWidth(double w) { width_ = w; }
        public static Box operator+ (Box a, Box b) 
        {
            Box hzBox = new Box();
            hzBox.height_ = a.height_ + b.height_;
            hzBox.width_ = a.width_ + b.width_;
            hzBox.lenght_ = a.lenght_ + b.lenght_;
            return hzBox;
        }

        public static bool operator== (Box a, Box b) 
        {
            if (a.lenght_ == b.lenght_ && a.width_ == b.width_ && a.height_ == b.height_) 
            {
                return true;
            }
            return false;
        }
        public static bool operator!=(Box a, Box b)
        {
            if (a.lenght_ != b.lenght_ || a.width_ != b.width_ || a.height_ != b.height_)  
            {
                return true;
            }
            return false;
        }

        public override bool Equals(object? obj)
        {
            if (obj == null) return false;
            if (GetType() != obj.GetType()) return false;
            return true;
        }
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public static bool operator <(Box a, Box b) 
        {
            if (a.lenght_ < b.lenght_ && a.width_ < b.width_ && a.height_ < b.height_) return true;
            return false;
        }
        public static bool operator >(Box a, Box b) 
        {
            if (a.lenght_>b.lenght_ && a.width_>b.width_ && a.height_>b.height_) return true;
            return false;
        }
        public static bool operator <=(Box a, Box b)
        {
            if (a.lenght_ <= b.lenght_ && a.width_ <= b.width_ && a.height_ <= b.height_) return true;
            return false;
        }
        public static bool operator >=(Box a, Box b)
        {
            if (a.lenght_ >= b.lenght_ && a.width_ >= b.width_ && a.height_ >= b.height_) return true;
            return false;
        }
        public override string ToString()
        {
            return String.Format("({0},{1},{2})", lenght_, width_, height_);
        }
    }
}

37,接口

接口可以看作是一个约定,其中定义了类或结构体继承接口后需要实现功能,接口的特点如下所示:

  • 接口是一个引用类型,通过接口可以实现多重继承;
  • 接口中只能声明"抽象"成员,所以不能直接对接口进行实例化;
  • 接口中可以包含方法、属性、事件、索引器等成员;
  • 接口名称一般习惯使用字母"I"作为开头(不是必须的,不这样声明也可以);
  • 接口中成员的访问权限默认为 public,所以我们在定义接口时不用再为接口成员指定任何访问权限修饰符,否则编译器会报错;
  • 在声明接口成员的时候,不能为接口成员编写具体的可执行代码,也就是说,只要在定义成员时指明成员的名称和参数就可以了;
  • 接口一旦被实现(被一个类继承),派生类就必须实现接口中的所有成员,除非派生类本身也是抽象类。

①声明接口

csharp 复制代码
public interface InterfaceName{
    returnType funcName1(type parameterList);
    returnType funcName2(type parameterList);
    ... ...
}
//InterfaceName 为接口名称,returnType 为返回值类型,funcName 为成员函数的名称,parameterList 为参数列表
csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            HZ hz = new HZ();
            hz.setValue("HZXDW",18);
            hz.disPlay();
            Console.ReadKey();
        }
    }
    public interface Ihz
    {
        void setValue(string s, int a);
        void disPlay();
    }
    public class HZ : Ihz
    {
        string name;
        int age;
        public void setValue(string n, int a)
        {
            name = n;
            age = a;
        }
        public void disPlay()
        {
            Console.WriteLine("name is {0}, age is {1}", name, age);
        }
    }
}

②接口继承

一个接口可以继承另一个接口,例如可以使用接口 1 继承接口 2,当用某个类来实现接口 1 时,必须同时实现接口 1 和接口 2 中的所有成员

csharp 复制代码
using System;

namespace beyond.yy
{
    public interface Ihz { void ParentInterface(); }
    public interface Ihzxdw : Ihz { void SonInterface(); }
    class HZXDW:Ihzxdw
    {
        static void Main(string[] args)
        {
            HZXDW hz = new HZXDW();
            hz.ParentInterface();
            hz.SonInterface();
        }
        public void ParentInterface() { Console.WriteLine("ParentInterface"); }
        public void SonInterface() { Console.WriteLine("SonInterface"); }
    }
}

38,命名空间

可以将命名空间看作是一个范围,用来标注命名空间中成员的归属,一个命名空间中类与另一个命名空间中同名的类互不冲突,但在同一个命名空间中类的名称必须是唯一的

我们要输出某些数据,就需要使用System.Console.WriteLine(),其中 System 就是命名空间,而 Console 是类的名字,WriteLine 则是具体要使用方法

如果要访问某个命名空间中的类,我们需要使用namespacename.classname.funcname()的形式

可以使用 using 关键字来引用需要的命名空间,using System,这样我们就可以直接使用Console.WriteLine()来输出指定的数据了

命名空间的结构类似于我们计算机系统中的目录,我们可以将某个目录看作是一个命名空间,在这个目录下可以存在若干不同的文件夹,这些文件夹就可以看作是命名空间下的类。而在每个文件夹下又存放着一些文件或文件夹,这些文件和文件夹则可以看作是类中的成员

使用命名空间的好处是可以避免命名冲突,同时也便于查找类的位置

①定义命名空间

定义命名空间需要使用 namespace 关键字

要调用指定命名空间下的成员,则需要使用namespaceName.className.funcName()的形式

csharp 复制代码
using System;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            YY.demoClass yYClass = new YY.demoClass();
            HZ.demoClass hZClass = new HZ.demoClass();

            yYClass.Test();
            hZClass.Test();
        }
    }
}

namespace YY 
{
    public class demoClass 
    {
        public void Test() { Console.WriteLine("YY::YYClass::Test"); }
    }
}

namespace HZ 
{
    public class demoClass
    {
        public void Test() { Console.WriteLine("HZ::HZClass::Test"); }
    }
}

②using关键字

using 关键字用来引用指定的命名空间,它可以告诉编译器后面的代码中我们需要用到某个命名空间

在程序中需要使用到 System 命名空间,只需要在程序的开始使用using System引用该命名空间即可,这时我们在使用 System 命名空间下的类时就可以将System.省略,例如Console.WriteLine();

csharp 复制代码
using HZ;
using YY;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            YyClass yYClass = new YyClass();
            HzClass hZClass = new HzClass();

            yYClass.Test();
            hZClass.Test();
        }
    }
}

namespace YY 
{
    public class YyClass 
    {
        public void Test() { Console.WriteLine("YY::YYClass::Test"); }
    }
}

namespace HZ 
{
    public class HzClass
    {
        public void Test() { Console.WriteLine("HZ::HZClass::Test"); }
    }
}

③命名空间嵌套

命名空间可以嵌套使用,也就是说我们可以在一个命名空间中再定义一个或几个命名空间

以使用点.运算符来访问嵌套的命名空间成员,例如namespaceName1.namespaceName2

csharp 复制代码
using YY;
using YY.HZ;

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
            YyClass yYClass = new YyClass();
            HzClass hZClass = new HzClass();

            yYClass.Test();
            hZClass.Test();
        }
    }
}

namespace YY 
{
    public class YyClass 
    {
        public void Test() { Console.WriteLine("YY::YYClass::Test"); }
    }
    namespace HZ
    {
        public class HzClass
        {
            public void Test() { Console.WriteLine("YY::HZ::HZClass::Test"); }
        }
    }
}

39,预处理器指令

预处理指令的作用主要是向编译器发出指令,以便在程序编译开始之前对信息进行一些预处理操作

预处理器指令均以#开头,并且预处理器指令之前只能出现空格不能出现任何代码

预处理器指令不是语句,因此它们不需要 以分号;结尾。

预处理器指令 描述
#define 用于定义一系列字符,可以将这些字符称为符号
#undef 用于取消一个已定义符号
#if 用于测试符号是否为真
#else 用于创建复合条件指令,与 #if 一起使用
#elif 用于创建复合条件指令
#endif 指定一个条件指令的结束
#line 用于修改编译器的行数以及(可选地)输出错误和警告的文件名
#error 用于在代码的指定位置生成一个错误
#warning 用于在代码的指定位置生成一级警告
#region 用于在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块
#endregion 用于标识 #region 块的结束

①#define 预处理器

#define 预处理器指令用来创建符号常量,这个符号可以作为传递给 #if 指令的表达式,表达式将返回 true

csharp 复制代码
#define PI

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
#if (PI)
            Console.WriteLine("PI已定义");
#else
            Console.WriteLine("PI未定义");
#endif
        }
    }
}

②条件指令

可以使用 #if 来创建条件指令,条件指令可以用于测试一个或多个符号的值是否为 true

如果符号的值为 true,那么编译器将评估 #if 指令和下一个指令之间的所有代码

条件指令中仅可以使用运算符==(相等)和!=(不相等)来测试布尔值 true 或 false

还可以使用&& (and)|| (or)! (not)运算符来同时测试多个符号,以及使用括号对符号和运算符分组

以 #if 指令开头的条件指令必须以 #endif 指令显式结束

csharp 复制代码
#define YY
#define HZ

namespace beyond.yy
{
    class HZXDW
    {
        static void Main(string[] args)
        {
#if YY && !HZ
            Console.WriteLine("YY已定义");
#elif !YY && HZ
            Console.WriteLine("HZ已定义");
#elif YY&&HZ
            Console.WriteLine("YY&&HZ已定义");
#else
            Console.WriteLine("YY&&HZ都未定义");
#endif
        }
    }
}

40,正则表达式

正则表达式是一种匹配输入文本的模式 ,可以用于解析和验证给定文本以及模式之间是否匹配,模式可以包含运算符、字符字面值或结构

①正则表达式的组成

  • 转义字符;
  • 字符类;
  • 定位符;
  • 分组构造;
  • 限定符;
  • 反向引用构造;
  • 备用构造;
  • 替换;
  • 杂项构造。

Ⅰ转义字符

反斜杠\用来表示它后面跟随的字符是特殊字符,具有特殊的含义

转义字符 描述 正则表达式 示例
\a 与报警 (bell) 符 \u0007 匹配 \a 匹配 "Warning!" + '\u0007' 中的 "\u0007"
\b 在字符类中,与退格键 \u0008 匹配 [\b]{3,} 匹配 "\b\b\b\b" 中的 "\b\b\b\b"
\t 与制表符 \u0009 匹配 (\w+)\t 匹配 "Name\tAddr\t" 中的 "Name\t" 和 "Addr\t"
\r 与回车符 \u000D 匹配,(\r 与换行符 \n 不是等效的) \r\n(\w+) 匹配 "\r\nHello\nWorld." 中的 "\r\nHello"
\v 与垂直制表符 \u000B 匹配 [\v]{2,} 匹配 "\v\v\v" 中的 "\v\v\v"
\f 与换页符 \u000C 匹配 [\f]{2,} 匹配 "\f\f\f" 中的 "\f\f\f"
\n 与换行符 \u000A 匹配 \r\n(\w+) 匹配 "\r\nHello\nWorld." 中的 "\r\nHello"
\e 与转义符 \u001B 匹配 \e 匹配 "\x001B" 中的 "\x001B"
\nnn 使用八进制形式指定一个字符(nnn 由二到三位数字组成) \w\040\w 匹配 "a bc d" 中的 "a b" 和 "c d"
\xnn 使用十六进制形式指定字符(nn 由两位数字组成) \w\x20\w 匹配 "a bc d" 中的 "a b" 和 "c d"
\cX\cx 匹配 X 或 x 指定的 ASCII 字符,其中 X 或 x 是控件字符的字母 \cC 匹配 "Ctrl" 中的 "C"
\unnnn 使用十六进制形式匹配一个 Unicode 字符(nnnn 表示一个四位数) \w\u0020\w 匹配 "a bc d" 中的 "a b" 和 "c d"
\ 在后面带有不识别的转义字符时,与该字符匹配 \d+[±x*]\d+\d+[±x*\d+ 匹配 "(2+2) * 39" 中的 "2+2" 和 "39"

Ⅱ字符类

可以与一个字符串中的任何一个字符相匹配

字符类 描述 正则表达式 示例
[character_group] 匹配 character_group 中的任何一个字符,默认情况下 character_group 中的字符区分大小写 [mn] 匹配 "mat" 中的 "m","moon" 中的 "m" 和 "n"
[^character_group] 匹配不在 character_group 中的任何单个字符,默认情况下 character_group 中的字符区分大小写 [^aei] 匹配 "avail" 中的 "v" 和 "l"
[first-last] 字符范围,匹配与从 first 到 last 范围内的任何单个字符 [b-d] [b-d]irds 可以匹配 Birds、 Cirds、 Dirds
. 通配符:匹配任何字符,若要匹配句号(. 或 \u002E),则必须在该字符前面加上转义符 (.) a.e 匹配 "have" 中的 "ave", "mate" 中的 "ate"
\p{name} 匹配 name 中指定的 Unicode 字符 \p{Lu} 匹配 "City Lights" 中的 "C" 和 "L"
\P{name} 匹配不在 name 中指定的 Unicode 字符 \P{Lu} 匹配 "City" 中的 "i"、 "t" 和 "y"
\w 匹配字母、数字、下划线 \w 匹配 "Room#1" 中的 "R"、 "o"、 "m" 和 "1"
\W 匹配字母、数字或下划线以外的字符 \W 匹配 "Room#1" 中的 "#"
\s 匹配任何空白字符(包括换行符) \w\s 匹配 "ID A1.3" 中的 "D "
\S 匹配任何非空白字符(包括换行符) \s\S 匹配 "int __ctr" 中的 " _"
\d 匹配任何十进制数字 \d 匹配 "4 = IV" 中的 "4"
\D 匹配不是十进制数的任意字符 \D 匹配 "4 = IV" 中的 " "、 "="、 " "、 "I" 和 "V"

Ⅲ定位符

可以根据字符串出现的具体位置来判断匹配是成功还是失败

断言 描述 正则表达式 示例
^ 从字符串的开始位置进行匹配 ^\d{3} 匹配 "567-777-" 中的 "567"
$ 从字符串的结尾位置进行匹配 -\d{4}$ 匹配 "8-12-2012" 中的 "-2012"
\A 匹配字符串的开始位置 \A\w{4} 匹配 "Code-007-" 中的 "Code"
\Z 匹配字符串的结尾位置或字符串结尾换行之前的位置 -\d{3}\Z 匹配 "Bond-901-007" 中的 "-007"
\z 匹配字符串的结尾位置 -\d{3}\z 匹配 "-901-333" 中的 "-333"
\G 匹配上一个匹配结束的位置 \G(\d) 匹配 "(1)(3)(5)7" 中的 "(1)"、 "(3)" 和 "(5)"
\b 匹配一个单词的开始或结束的位置 er\b 匹配"never"中的"er",但不能匹配"verb"中的"er"。
\B 匹配一个单词的中间位置 er\B 匹配"verb"中的"er",但不能匹配"never"中的"er"。

Ⅳ分组构造

分组构造描述了正则表达式的子表达式,并捕获输入字符串的子字符串

分组构造 描述 正则表达式 示例
(subexpression) 捕获匹配的子表达式并将其分配到一个从零开始的序号中 (\w)\1 "deep" 中的 "ee"
(?subexpression) 将匹配的子表达式捕获到一个命名组中 (?< double>\w)\k< double> "deep" 中的 "ee"
(?<name1 -name2>subexpression) 定义平衡组 (((?'Open'()[()]*)+((?'Close-Open'))[()])+)(?(Open)(?!))$ "3+2^((1-3)(3-1))" 中的 "((1-3)(3-1))"
(?: subexpression) 定义非捕获组 Write(?:Line)? "Console.WriteLine()" 中的 "WriteLine"
(?imnsx-imnsx:subexpression) 应用或禁用 subexpression 中指定的选项 A\d{2}(?i:\w+)\b "A12xl A12XL a12xl" 中的 "A12xl" 和 "A12XL"
(?= subexpression) 零宽度正预测先行断言 \w+(?=.) "He is. The dog ran. The sun is out." 中的 "is"、 "ran" 和 "out"
(?! subexpression) 零宽度负预测先行断言 \b(?!un)\w+\b "unsure sure unity used" 中的 "sure" 和 "used"
(?<=subexpression) 零宽度正回顾后发断言 (?<=19)\d{2}\b "1851 1999 1950 1905 2003" 中的 "99"、"50"和 "05"
(? 零宽度负回顾后发断言 (? "Hi woman Hi man" 中的 "man"
(?> subexpression) 非回溯(也称为"贪婪")子表达式 13579 "1ABB 3ABBC 5AB 5AC" 中的 "1ABB"、 "3ABB" 和 "5AB"

Ⅴ限定符

用来指定在字符串中必须存在某个元素(可以是字符、组或字符类)才能匹配成功

限定符 描述 正则表达式 示例
* 匹配上一个元素零次或多次 \d*.\d ".0"、 "19.9"、 "219.9"
+ 匹配上一个元素一次或多次 "be+" 匹配 "been" 中的 "bee", "bent" 中的 "be"
? 匹配上一个元素零次或一次 "rai?n" "ran"、 "rain"
{n} 匹配上一个元素 n 次 ",\d{3}" 匹配 "1,043.6" 中的 ",043", "9,876,543,210" 中的 ",876"、 ",543" 和 ",210"
{n, } 匹配上一个元素至少 n 次 "\d{2,}" "166"、 "29"、 "1930"
{n, m} 匹配上一个元素至少 n 次,但不多于 m 次 "\d{3,5}" 匹配 "166", "17668", "193024" 中的 "19302"
*? 匹配上一个元素零次或多次,但次数尽可能少 \d*?.\d ".0"、 "19.9"、 "219.9"
+? 匹配上一个元素一次或多次,但次数尽可能少 "be+?" 匹配 "been" 中的 "be", "bent" 中的 "be"
?? 匹配上一个元素零次或一次,但次数尽可能少 "rai??n" "ran"、 "rain"
{n}? 匹配前导元素恰好 n 次 ",\d{3}?" 匹配 "1,043.6" 中的 ",043", "9,876,543,210" 中的 ",876"、 ",543" 和 ",210"
{n,}? 匹配上一个元素至少 n 次,但次数尽可能少 "\d{2,}?" "166"、 "29" 和 "1930"
{n, m}? 匹配上一个元素的次数介于 n 和 m 之间,但次数尽可能少 "\d{3,5}?" 匹配 "166", "17668", "193024" 中的 "193" 和 "024"

Ⅵ反向引用构造

允许先前匹配的子表达式随后在相同的正则表达式中进行标识

反向引用构造 描述 正则表达式 示例
\number 反向引用,匹配编号子表达式的值 (\w)\1 匹配 "seek" 中的 "ee"
\k 命名反向引用,匹配命名表达式的值 (?< char>\w)\k< char> 匹配 "seek" 中的 "ee"

Ⅶ备用构造

用于修改正则表达式以启用 either/or 匹配

备用构造 描述 正则表达式 示例
| 匹配以竖线` `字符分隔的任何一个元素 th(e|is|at)
(?( expression )yes | no) 如果正则表达式模式由 expression 匹配指定,则匹配 yes 部分;否则匹配可选的 no 部分,expression 被解释为零宽度断言 (?(A)A\d{2}\b|\b\d{3}\b) 匹配 "A10 C103 910" 中的 "A10" 和 "910"
(?( name )yes | no ) 如果 name 或已命名或已编号的捕获组具有匹配,则匹配 yes 部分;否则匹配可选的 no 部分。 (?< quoted>")?(?(quoted).+?"|\S+\s) 匹配 "Dogs.jpg "Yiska playing.jpg"" 中的 Dogs.jpg 和 "Yiska playing.jpg"

Ⅷ替换

替换模式中使用的正则表达式

字符 描述 模式 替换模式 输入字符串 结果字符串
$number 替换按组 number 匹配的子字符串 \b(\w+)(\s)(\w+)\b 32$1 "one two" "two one"
${name} 替换按命名组 name 匹配的子字符串 \b(?< word1>\w+)(\s)(?< word2>\w+)\b {word2} {word1} "one two" "two one"
$$ 替换字符"$" \b(\d+)\s?USD $$$1 "103 USD" "$103"
$& 替换整个匹配项的一个副本 ($(\d(.+\d+)?){1}) **$& "$1.30" "$1.30"
$` 替换匹配前的输入字符串的所有文本 B+ $` "AABBCC" "AAAACC"
$' 替换匹配后的输入字符串的所有文本 B+ $' "AABBCC" "AACCCC"
$+ 替换最后捕获的组 B+(C+) $+ "AABBCCDD" AACCDD
$_ 替换整个输入字符串 B+ $_ "AABBCC" "AAAABBCCCC"

Ⅸ杂项构造

构造 描述 示例
(?imnsx-imnsx) 在模式中间对诸如不区分大小写这样的选项进行设置或禁用 \bA(?i)b\w+\b 匹配 "ABA Able Act" 中的 "ABA" 和 "Able"
(?#注释) 内联注释。该注释在第一个右括号处终止 \bA(?# 匹配以 A 开头的单词 )\w+\b
# [行尾] 该注释以非转义的 # 开头,并继续到行的结尾 (?x)\bA\w+\b# 匹配以 A 开头的单词

②Regex 类

Regex 类用于使用一个正则表达式

方法 描述
public bool IsMatch( string input ) 指示 Regex 构造函数中指定的正则表达式是否在指定的输入字符串中找到匹配项
public bool IsMatch( string input, int startat ) 指示 Regex 构造函数中指定的正则表达式是否在指定的输入字符串中找到匹配项,从字符串中指定的位置开始查找
public static bool IsMatch( string input, string pattern ) 指示指定的正则表达式是否在指定的输入字符串中找到匹配项
public MatchCollection Matches( string input ) 在指定的输入字符串中搜索正则表达式的所有匹配项
public string Replace( string input, string replacement ) 在指定的输入字符串中,把所有匹配正则表达式模式的所有匹配的字符串替换为指定的替换字符串
public string[] Split( string input ) 把输入字符串分割为子字符串数组,根据在 Regex 构造函数中指定的正则表达式模式定义的位置进行分割

匹配以"C"开头的词组------\bC\S*

MatchCollection mc = Regex.Matches(s1, s2);

Matches:在指定的输入字符串中搜索正则表达式的所有匹配项

@"\bC\S*",@表示字符串常量;\b表示匹配一个单词的开始或结束的位置;C就是这个单词;\S表示匹配任何非空白字符(包括换行符);*表示匹配上一个元素零次或多次

csharp 复制代码
using System;
using System.Text.RegularExpressions;

namespace beyond.yy
{
    class YYXDW
    {
        static void Main(string[] args)
        {
            string str = "C#教程 C语言中文网 c.biancheng.net 正则表达式";
            ShowYY(str, @"\bC\S*");
            Console.ReadKey();
        }

        private static void ShowYY(string s1, string s2)
        {
            Console.WriteLine("正则表达式为: " + s2);
            Console.WriteLine("匹配以"C"开头的字符串: ");
            MatchCollection mc = Regex.Matches(s1, s2);
            foreach (Match m in mc)
            {
                Console.WriteLine(m);
            }
        }
    }
}

匹配以"m"开头,以"e"结尾的单词------\bm\S*e\b

csharp 复制代码
using System;
using System.Text.RegularExpressions;

namespace beyond.yy
{
    class YYXDW
    {
        static void Main(string[] args)
        {
            string str = "make maze and manage to measure it";
            ShowYY(str, @"\bm\S*e\b");
        }

        private static void ShowYY(string s1, string s2)
        {
            Console.WriteLine("正则表达式为: " + s2);
            Console.WriteLine("匹配以"m"开头,以"e"结尾的单词: ");
            MatchCollection mc = Regex.Matches(s1, s2);
            foreach (Match m in mc)
            {
                Console.WriteLine(m);
            }
        }
    }
}

去除字符串中多余的空格

csharp 复制代码
using System;
using System.Text.RegularExpressions;

namespace beyond.yy
{
    class YYXDW
    {
        static void Main(string[] args)
        {
            string input = "C   语言   中文网   ";
            string pattern = "\\s+";
            Regex rgx = new Regex(pattern);
            string result = rgx.Replace(input, "");
            Console.WriteLine("原始字符串: {0}", input);
            Console.WriteLine("替换后的字符串: {0}", result);
            Console.ReadKey();
        }
    }
}

41,异常处理

异常是在程序运行出错时引发的

以一个数字除以零,所有异常都派生自 System.Exception 类

异常处理则是处理运行时错误的过程,使用异常处理可以使程序在发生错误时保持正常运行

异常处理基于四个关键字构建,分别是 try、catch、finally 和 throw。

  • try:try 语句块中通常用来存放容易出现异常的代码,其后面紧跟一个或多个 catch 语句块;
  • catch:catch 语句块用来捕获 try 语句块中的出现的异常;
  • finally:finally 语句块用于执行特定的语句,不管异常是否被抛出都会执行;
  • throw:throw 用来抛出一个异常。

①try/catch语句

csharp 复制代码
try{
   // 引起异常的语句
}catch( ExceptionName e1 ){
   // 错误处理代码
}catch( ExceptionName e2 ){
   // 错误处理代码
}
...
catch( ExceptionName eN ){
   // 错误处理代码
}finally{
   // 要执行的语句
}

可以列出多个 catch 语句块来捕获不同类型的异常,以防止 try 语句块在不同的情况下产生多个异常

csharp 复制代码
using System;
using System.Text.RegularExpressions;

namespace beyond.yy
{
    class YYXDW
    {
        static void Main(string[] args)
        {
            try 
            {
                int a = 123;
                int b = 0;
                int x = a / b;

            } 
            catch (Exception ex) 
            {
                Console.WriteLine("捕获到异常:{0}", ex);
            } 
            finally 
            {
                Console.WriteLine("finally 语句块已执行");
            }
            Console.WriteLine("其他代码已运行...");
        }
    }
}

②异常类

异常类主要是从 System.Exception 类派生的,比如 System.ApplicationException 和 System.SystemException 两个异常类就是从 System.Exception 类派生的

  • System.ApplicationException 类支持由程序产生的异常,因此我们自定义的异常都应继承此类;
  • System.SystemException 类是所有系统预定义异常的基类。
异常类 描述
System.IO.IOException 处理 I/O 错误
System.IndexOutOfRangeException 处理当方法引用超出范围的数组索引时产生的错误
System.ArrayTypeMismatchException 处理当数组类型不匹配时产生的错误
System.NullReferenceException 处理引用一个空对象时产生的错误
System.DivideByZeroException 处理当除以零时产生的错误
System.InvalidCastException 处理在类型转换期间产生的错误
System.OutOfMemoryException 处理空闲内存不足产生的错误
System.StackOverflowException 处理栈溢出产生的错误

③自定义异常类

除了可以使用系统预定义的异常类外,我们还可以自行定义异常类,自定义的异常类都应继承 System.ApplicationException 类

csharp 复制代码
using System;
using System.Text.RegularExpressions;

namespace beyond.yy
{
    class YYDW
    {
        static void Main(string[] args)
        {
            TestYYException yy = new TestYYException();
            try
            {
                yy.validate(12);
            }
            catch (IHZException hz) 
            {
                Console.WriteLine("IHZException:{0}",hz);
            }
            Console.WriteLine("others code");
        }
    }
}
public class IHZException : ApplicationException
{
    public IHZException(string message) : base(message)
    {
    }
}
public class TestYYException
{
    public void validate(int age)
    {
        if (age < 18)
        {
            throw (new IHZException("Sorry, Age must be greater than 18"));
        }
    }
}

④抛出异常

如果异常是直接或间接派生自 System.Exception 类,则可以在 catch 语句块中使用 throw 语句抛出该异常,所谓抛出异常这里可以理解为重新引发该异常

42,文件读写

文件是存储在磁盘中的具有特定名称和目录路径的数据集合,当我们使用程序对文件进行读取或写入时,程序会将文件以数据流(简称流)的形式读入内存中

我们可以将流看作是通过通信路径传递的字节序列,流主要分为输入流和输出流,输入流主要用于从文件读取数据(读操作),输出流主要用于向文件中写入数据(写操作)。

①I/O 类

System.IO 命名空间中包含了各种用于文件操作的类,例如文件的创建、删除、读取、写入等等

I/O 类 描述
BinaryReader 从二进制流中读取原始数据
BinaryWriter 以二进制格式写入原始数据
BufferedStream 临时存储字节流
Directory 对目录进行复制、移动、重命名、创建和删除等操作
DirectoryInfo 用于对目录执行操作
DriveInfo 获取驱动器的信息
File 对文件进行操作
FileInfo 用于对文件执行操作
FileStream 用于文件中任何位置的读写
MemoryStream 用于随机访问存储在内存中的数据流
Path 对路径信息执行操作
StreamReader 用于从字节流中读取字符
StreamWriter 用于向一个流中写入字符
StringReader 用于从字符串缓冲区读取数据
StringWriter 用于向字符串缓冲区写入数据

②FileStream 类

FileStream 类在 System.IO 命名空间下,使用它可以读取、写入和关闭文件

csharp 复制代码
FileStream <object_name> = new FileStream(<file_name>, <FileMode Enumerator>, <FileAccess Enumerator>, <FileShare Enumerator>);
FileStream F = new FileStream("sample.txt", FileMode.Open, FileAccess.Read, FileShare.Read);
  • object_name:创建的对象名称;
  • file_name:文件的路径(包含文件名在内);
  • FileMode:枚举类型,用来设定文件的打开方式,可选值如下:
    • Append:打开一个已有的文件,并将光标放置在文件的末尾。如果文件不存在,则创建文件;
    • Create:创建一个新的文件,如果文件已存在,则将旧文件删除,然后创建新文件;
    • CreateNew:创建一个新的文件,如果文件已存在,则抛出异常;
    • Open:打开一个已有的文件,如果文件不存在,则抛出异常;
    • OpenOrCreate:打开一个已有的文件,如果文件不存在,则创建一个新的文件并打开;
    • Truncate:打开一个已有的文件,然后将文件清空(删除原有内容),如果文件不存在,则抛出异常。
  • FileAccess:枚举类型,用来设置文件的存取,可选值有 Read、ReadWrite 和 Write;
  • FileShare:枚举类型,用来设置文件的权限,可选值如下:
    • Inheritable:允许子进程继承文件句柄,Win32 不直接支持此功能;
    • None:在文件关闭前拒绝共享当前文件,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败;
    • Read:允许随后打开文件读取,如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求都将失败,需要注意的是,即使指定了此标志,仍需要附加权限才能够访问该文件;
    • ReadWrite:允许随后打开文件读取或写入,如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求都将失败,需要注意的是,即使指定了此标志,仍需要附加权限才能够访问该文件;
    • Write:允许随后打开文件写入,如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求都将失败,需要注意的是,即使指定了此标志,仍可能需要附加权限才能够访问该文件;
    • Delete:允许随后删除文件。

FileStream 类中的常用方法

方法 描述
Close() 关闭当前流并释放与之关联的所有资源(如套接字和文件句柄)
CopyTo(Stream) 从当前流中读取字节并将其写入到另一流中
Dispose() 释放由 Stream 使用的所有资源
Equals(Object) 判断指定对象是否等于当前对象
Finalize() 确保垃圾回收器回收 FileStream 时释放资源并执行其他清理操作
Flush() 清除此流的缓冲区,使得所有缓冲数据都写入到文件中
GetHashCode() 默认哈希函数
GetType() 获取当前实例的 Type
Lock(Int64, Int64) 防止其他进程读取或写入 FileStream
Read(Byte[], Int32, Int32) 从流中读取字节块并将该数据写入给定缓冲区中
ReadByte() 从文件中读取一个字节,并将读取位置提升一个字节
ToString() 返回表示当前对象的字符串
Unlock(Int64, Int64) 允许其他进程访问以前锁定的某个文件的全部或部分
Write(Byte[], Int32, Int32) 将字节块写入文件流
WriteByte(Byte) 将一个字节写入文件流中的当前位置

使用 FileStream 类读取指定的文件

csharp 复制代码
using System;
using System.Text.RegularExpressions;

namespace beyond.yy
{
    class YY
    {
        static void Main(string[] args)
        {
            FileStream file = new FileStream("beyondyy.txt",FileMode.OpenOrCreate,FileAccess.ReadWrite);

            for (int i = 0; i < 20; i++) 
            {
                file.WriteByte((byte)i);
            }
            file.Position = 0;//获取或设置文件流的当前位置(以字节为单位)

            for (int i = 0; i < 20; i++) 
            {
                Console.Write(file.ReadByte() + " ");
            }

            file.Close();
            Console.ReadKey();
        }
    }
}

③文本文件的读取写入

System.IO 命名空间下的 StreamReader 和 StreamWriter 类可以用于文本文件的数据读写

这些类继承自抽象基类 Stream,Stream 类提供了对文件流读写的功能。

Ⅰ StreamReader

StreamReader 类继承自抽象基类 TextReader,用来从文件中读取一系列字符

方法 描述
public override void Close() 关闭 StreamReader 对象和基础流,并释放任何与之相关的系统资源
public override int Peek() 返回下一个可用的字符,但不使用它
public override int Read() 从输入流中读取下一个字符,并把字符位置往前移一个字符

查阅完整的方法列表,可以访问 C# 的官网文档

csharp 复制代码
using System;

namespace beyond.yy
{
    class YY
    {
        static void Main(string[] args)
        {
            try
            {
                StreamReader file = new StreamReader("beyondyy.txt");

                string line;
                while ((line = file.ReadLine()) != null)
                {
                    Console.WriteLine(line);
                }
            }
            catch (Exception ex) 
            { 
                Console.WriteLine("Error");
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

Ⅱ StreamWriter

StreamWriter 类同样继承自抽象类 TextWriter,用来向文件中写入一系列字符

方法 描述
public override void Close() 关闭当前的 StreamWriter 对象和基础流
public override void Flush() 清理当前所有的缓冲区,使所有缓冲数据写入基础流
public virtual void Write(bool value) 将布尔值的文本表示形式写入文本流
public override void Write(char value) 将一个字符写入流
public virtual void Write(decimal value) 将一个小数值的文本表示形式写入文本流
public virtual void Write(double value) 将一个 8 字节浮点值的文本表示形式写入文本流
public virtual void Write(int value) 将一个 4 字节有符号整数的文本表示形式写入文本流
public override void Write(string value) 将一个字符串写入文本流
public virtual void WriteLine() 将行结束符写入文本流

向文件中写入指定内容

csharp 复制代码
using System;

namespace beyond.yy
{
    class YY
    {
        static void Main(string[] args)
        {
            string[] str = new string[]
            {
                "beyond",
                "huangjiaju",
                "yanyu",
                "huize"
            };

            StreamWriter file = new StreamWriter("0903text.txt");
            foreach (string s in str) 
            {
                file.WriteLine(s);
            }
            file.Close();


            string line = "";
            StreamReader readfile = new StreamReader("0903text.txt");
            while ((line = readfile.ReadLine())!=null) 
            {
                Console.WriteLine(line);
            }
            readfile.Close();
            Console.ReadKey();
        }
    }
}

④二进制文件读写

BinaryReader 和 BinaryWriter 类可以用于二进制文件的读写

Ⅰ BinaryReader 类

BinaryReader 类用于从文件读取二进制数据

完整的方法列表请查阅 C# 的官方文档

方法 描述
public override void Close() 关闭 BinaryReader 对象和基础流
public virtual int Read() 从基础流中读取字符,并根据所使用的编码和从流中读取的特定字符,将流的当前位置前移
public virtual bool ReadBoolean() 从当前流中读取一个布尔值,并将流的当前位置前移一个字节
public virtual byte ReadByte() 从当前流中读取下一个字节,并将流的当前位置前移一个字节
public virtual byte[] ReadBytes(int count) 从当前流中读取指定数目的字节到一个字节数组中,并将流的当前位置前移指定数目的字节
public virtual char ReadChar() 从当前流中读取下一个字节,并把流的当前位置按照所使用的编码和从流中读取的指定的字符往前移
public virtual char[] ReadChars(int count) 从当前流中读取指定数目的字符,并以字符数组的形式返回数据,并把流的当前位置按照所使用的编码和从流中读取的指定的字符往前移
public virtual double ReadDouble() 从当前流中读取一个 8 字节浮点值,并把流的当前位置前移八个字节
public virtual int ReadInt32() 从当前流中读取一个 4 字节有符号整数,并把流的当前位置前移四个字节
public virtual string ReadString() 从当前流中读取一个字符串,字符串以长度作为前缀,同时编码为一个七位的整数

Ⅲ BinaryWriter 类

BinaryWriter 类用于向文件写入二进制数据

完整的方法列表请查阅 C# 的官方文档

方法 描述
public override void Close() 关闭 BinaryWriter 对象和基础流
public virtual void Flush() 清理当前编写器的所有缓冲区,使得所有缓冲数据写入基础设备
public virtual long Seek(int offset,SeekOrigin origin) 设置当前流中的位置
public virtual void Write(bool value) 将一个字节的布尔值写入到当前流中,0 表示 false,1 表示 true
public virtual void Write(byte value) 将一个无符号字节写入到当前流中,并把流的位置前移一个字节
public virtual void Write(byte[] buffer) 将一个字节数组写入到基础流中
public virtual void Write(char ch) 将一个 Unicode 字符写入到当前流中,并把流的当前位置按照所使用的编码和要写入到流中的指定字符往前移
public virtual void Write(char[] chars) 将一个字符数组写入到当前流中,并把流的当前位置按照所使用的编码和要写入到流中的指定字符往前移
public virtual void Write(double value) 将一个 8 字节浮点值写入到当前流中,并把流位置前移八个字节
public virtual void Write(int value) 将一个 4 字节有符号整数写入到当前流中,并把流位置前移四个字节
public virtual void Write(string value) 将一个有长度前缀的字符串按 BinaryWriter 的当前编码写如到流中,并把流的当前位置按照所使用的编码和要写入到流中的指定字符往前移

二进制文件的读取和写入

csharp 复制代码
using System;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {

            BinaryWriter yybw;
            BinaryReader yybr;

            int i = 25;
            double d = 3.1415;
            bool b = true;
            string s = "beyondyanyu";

            try 
            {
                yybw = new BinaryWriter(new FileStream("yydata",FileMode.Create));//创建文件

            } 
            catch(Exception ex) 
            {
                Console.WriteLine(ex.Message + "\n 文件创建失败!!");
                return;
            }

            try
            {
                yybw.Write(i);//写入文件
                yybw.Write(d);
                yybw.Write(b);
                yybw.Write(s);
            }
            catch (IOException iex)
            {
                Console.WriteLine(iex.Message + "\n 文件写入失败!!");
                return;
            }
            yybw.Close();

            //读取文件
            try 
            {
                yybr = new BinaryReader(new FileStream("yydata", FileMode.Open));
            }
            catch (IOException iex)
            {
                Console.WriteLine(iex.Message + "\n 文件打开失败!!");
                return;
            }

            try 
            {
                i = yybr.ReadInt32();
                Console.WriteLine("Integer data: {0}",i);
                d = yybr.ReadDouble();
                Console.WriteLine("Doubel data: {0}",d);
                b = yybr.ReadBoolean();
                Console.WriteLine("Boolean data: {0}",b);
                s = yybr.ReadString();
                Console.WriteLine("String data: {0}",s);
            }
            catch (IOException iex) 
            {
                Console.WriteLine(iex.Message + "\n 文件读取失败!!!");
            }
            yybr.Close();
            Console.ReadKey();
        }
    }
}

43,目录操作

①DirectoryInfo 类

DirectoryInfo 类派生自 FileSystemInfo 类,其中提供了各种用于创建、移动、浏览目录和子目录的方法。需要注意的是,该类不能被继承

方法以及属性介绍,请查阅 C# 官方文档

属性 描述
Attributes 获取当前文件或目录的属性
CreationTime 获取当前文件或目录的创建时间
Exists 获取一个表示目录是否存在的布尔值
Extension 获取表示文件存在的字符串
FullName 获取目录或文件的完整路径
LastAccessTime 获取当前文件或目录最后被访问的时间
Name 获取该 DirectoryInfo 实例的名称
方法 描述
public void Create() 创建一个目录
public DirectoryInfo CreateSubdirectory(string path) 在指定的路径上创建子目录,指定的路径可以是相对于 DirectoryInfo 类的实例的路径
public override void Delete() 如果为空的,则删除该 DirectoryInfo
public DirectoryInfo[] GetDirectories() 返回当前目录的子目录
public FileInfo[] GetFiles() 从当前目录返回文件列表

②FileInfo 类

FileInfo 类派生自 FileSystemInfo 类,其中提供了用于创建、复制、删除、移动、打开文件的属性和方法。与 DirectoryInfo 类相同,FileInfo 类也不能被继承。

完整的方法以及属性介绍,请查阅 C# 官方文档

属性 描述
Attributes 获取当前文件的属性
CreationTime 获取当前文件的创建时间
Directory 获取文件所属目录的一个实例
Exists 获取一个表示文件是否存在的布尔值
Extension 获取表示文件存在的字符串
FullName 获取文件的完整路径
LastAccessTime 获取当前文件最后被访问的时间
LastWriteTime 获取文件最后被写入的时间
Length 获取当前文件的大小,以字节为单位
Name 获取文件的名称
方法 描述
public StreamWriter AppendText() 创建一个 StreamWriter,追加文本到由 FileInfo 的实例表示的文件中
public FileStream Create() 创建一个文件
public override void Delete() 永久删除一个文件
public void MoveTo(string destFileName) 移动一个指定的文件到一个新的位置,提供选项来指定新的文件名
public FileStream Open(FileMode mode) 以指定的模式打开一个文件
public FileStream Open(FileMode mode,FileAccess access) 以指定的模式,使用 read、write 或 read/write 访问,来打开一个文件
public FileStream Open(FileMode mode,FileAccess access,FileShare share) 以指定的模式,使用 read、write 或 read/write 访问,以及指定的分享选项,来打开一个文件
public FileStream OpenRead() 创建一个只读的 FileStream
public FileStream OpenWrite() 创建一个只写的 FileStream

使用 DirectoryInfo 类获取目录的信息

csharp 复制代码
using System;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            DirectoryInfo yydir = new DirectoryInfo(@"E:\normal_desk\desk\testC#\MyC#Study\MyC#Study\bin\Debug\net8.0"); //创建一个DirectoryInfo对象
            
            FileInfo[] yyf = yydir.GetFiles();//获取目录中的文件以及它们的名称大小
            foreach (FileInfo yy in yyf) 
            {
                Console.WriteLine("文件名称:{0},大小:{1}", yy.Name, yy.Length);
            }
            Console.ReadKey();
        }
    }
}

44,特性(Attribute)

特性(Attribute)是一种用于在程序运行时传递各种元素(例如类、方法、结构、枚举等)行为信息的声明性代码。使用特性可以将元数据(例如编译器指令、注释、描述、方法和类等信息)添加到程序中。

.Net Framework 提供了两种类型的特性,分别是预定义特性自定义特性

特性具有以下属性:

  • 使用特性可以向程序中添加元数据 ,元数据是指程序中各种元素的相关信息,所有 .NET 程序中都包含一组指定的元数据;
  • 可以将一个或多个特性应用于整个程序、模块或者较小的程序元素(例如类和属性)中;
  • 特性可以像方法和属性一样接受自变量;
  • 程序可使用反射来检查自己的元数据或其他程序中的元数据。

①定义特性

定义特性的语法格式如下所示

csharp 复制代码
[attribute(positional_parameters, name_parameter = value, ...)]
element

[ ]中用来定义特性的名称和值,positional_parameters 用来指定基本信息,name_parameter 用来指定可选信息

②预定义特性

.Net Framework 中提供了三个预定义的属性:

  • AttributeUsage;
  • Conditional;
  • Obsolete

Ⅰ AttributeUsage

预定义特性 AttributeUsage 用来描述如何使用自定义特性类,其中定义了可以应用特性的项目类型

csharp 复制代码
[AttributeUsage (
   validon,
   AllowMultiple = allowmultiple,
   Inherited = inherited
)]
  • 参数 validon 用来定义特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All;
  • 参数 allowmultiple(可选参数)用来为该特性的 AllowMultiple 属性(property)提供一个布尔值,默认值为 false(单用的),如果为 true,则该特性是多用的;
  • 参数 inherited(可选参数)用来为该特性的 Inherited 属性(property)提供一个布尔值,默认为 false(不被继承),如果为 true,则该特性可被派生类继承
csharp 复制代码
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

ⅡConditional

预定义特性 Conditional 用来标记一个方法,它的执行依赖于指定的预处理标识符 。根据该特性值的不同,在编译时会起到不同的效果,例如当值为 Debug 或 Trace 时,会在调试代码时显示变量的值

csharp 复制代码
[Conditional(
    conditionalSymbol
)]

[Conditional("DEBUG")]
csharp 复制代码
#define DEBUG
using System.Diagnostics;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void function1() 
        {
            YyClass.Message("function1");
            function2();
        }

        static void function2() 
        {
            YyClass.Message("function2");
        }


        static void Main(string[] args)
        {
            YyClass.Message("Main function");
            function1();
            Console.ReadKey();
        }
    }
    public class YyClass 
    {
        [Conditional("DEBUG")]
        public static void Message(string mess) 
        {
            Console.WriteLine(mess);
        }
    }
}

ⅢObsolete

预定义特性 Obsolete 用来标记不应被使用的程序 ,您可以使用它来通知编译器放弃某个目标元素。例如当您需要使用一个新方法来替代类中的某个旧方法时,就可以使用该特性将旧方法标记为 obsolete(过时的)并来输出一条消息,来提示我们应该使用新方法代替旧方法。

csharp 复制代码
[Obsolete (
   message
)]

[Obsolete (
   message,
   iserror
)]
  • 参数 message 是一个字符串,用来描述项目为什么过时以及应该使用什么替代;
  • 参数 iserror 是一个布尔值,默认值是 false(编译器会生成一个警告),如果设置为 true,那么编译器会把该项目的当作一个错误。
csharp 复制代码
#define DEBUG
using System.Diagnostics;

namespace beyond.yy
{
    class Beyondyanyu
    {
        [Obsolete("OldMethod 已弃用,请改用 NewMethod", true)]
        static void OldMethod() { Console.WriteLine("已弃用"); }
        static void NewMethod() { Console.WriteLine("最新版本"); }

        static void Main(string[] args)
        {
            OldMethod();//报错,版本已弃用
            NewMethod();//OK
            Console.ReadKey();
        }
    }
}

③自定义特性

.Net Framework 允许您创建自定义特性,自定义特性不仅可以用于存储声明性的信息 ,还可以在运行时被检索

创建并使用自定义特性可以分为四个步骤:

  • 声明自定义特性;
  • 构建自定义特性;
  • 在目标程序上应用自定义特性;
  • 通过反射访问特性。

最后一步涉及编写一个简单的程序来读取元数据以便查找各种符号。元数据是有关数据或用于描述其他数据信息的数据。该程序应在运行时使用反射来访问属性。

Ⅰ 声明自定义属性

自定义特性应该继承 System.Attribute 类

csharp 复制代码
// 一个自定义特性 BugFix 被赋给类及其成员
[AttributeUsage(
   AttributeTargets.Class |
   AttributeTargets.Constructor |
   AttributeTargets.Field |
   AttributeTargets.Method |
   AttributeTargets.Property,
   AllowMultiple = true)]

public class DeBugInfo : System.Attribute //声明了一个名为 DeBugInfo 的自定义属性

Ⅱ 构建自定义特性

构建一个名为 DeBugInfo 的自定义特性,该特性可以存储下面列举的调试信息:

  • bug 的代码编号;
  • 该 bug 的开发人员名字;
  • 上次审查代码的日期;
  • 一个存储了开发人员标记的字符串消息。

DeBugInfo 类中带有三个用于存储前三个信息的私有属性(property)和一个用于存储消息的公有属性(public)。所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位( positional)参数,而消息则是一个可选的命名(named)参数。

每个特性都至少有一个构造函数,而且定位( positional)参数需要通过构造函数传递。

csharp 复制代码
using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
public class YyDebugInfo : Attribute 
{
    private int bugNo;
    private string developer;
    private string lastReview;
    public string message;
    public YyDebugInfo(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; } }
}

Ⅲ 应用自定义特性

通过把特性放置在紧挨着它的目标上面来应用该特性

csharp 复制代码
using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
public class YyDebugInfo : Attribute 
{
    private int bugNo;
    private string developer;
    private string lastReview;
    public string message;
    public YyDebugInfo(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; } }
}

namespace beyond.yy
{
    [YyDebugInfo(56, "beyond.yy", "2025-01-01",message = "This is a test message")]
    [YyDebugInfo(22, "beyond.yy", "2025-10-10",message = "This is a test message2")]
    class YyRectangle 
    {
        protected double width, height;
        public YyRectangle(double w, double h) { width = w; height = h; }

        [YyDebugInfo(11, "beyond.yy", "2025-05-05",message = "This is a test message3 Area()")]
        public double Area() { return width * height; }

        [YyDebugInfo(12, "beyond.yy", "2025-06-06",message = "This is a test message4 Display()")]
        public void Display() { Console.WriteLine("Width = {0}, Height = {1}, Area = {2}", width, height, Area()); }
    }

    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            YyRectangle rec = new YyRectangle(12, 15);
            rec.Display();
        }
    }
}

45,反射(Reflection)

反射(Reflection)是指程序可以访问、检测和修改它本身状态或行为的一种能力,反射中提供了用来描述程序集、模块和类型的对象,可以使用反射动态地创建类型的实例,并将类型绑定到现有对象,或者从现有对象中获取类型,然后调用其方法或访问其字段和属性。 如果代码中使用了特性,也可以利用反射来访问它们。

①反射的用途

  • 在运行时查看视图属性信息;
  • 检查装配中的各种类型并实例化这些类型;
  • 在后期绑定到方法和属性;
  • 在运行时创建新类型,然后使用这些类型执行一些任务。

②查看元数据

使用反射查看特性 的信息

首先需要初始化 System.Reflection 类的 MemberInfo 对象,用来发现与类关联的属性

csharp 复制代码
System.Reflection.MemberInfo info = typeof(MyClass);
csharp 复制代码
using System.Diagnostics;
using System;

[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{
    public readonly string Url;
    private string topic;
    public string Topic
    {
        get { return topic; }
        set { topic = value; }
    }

    public HelpAttribute(string url) 
    {
        this.Url = url;
    }
}

[HelpAttribute("about YyClassInfo")]
class YyClass 
{

}

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            System.Reflection.MemberInfo info = typeof(YyClass);
            object[] attributes = info.GetCustomAttributes(true);
            for (int i = 0;i<attributes.Length;i++)
            {
                System.Console.WriteLine(attributes[i]);
            }
            Console.ReadKey();
        }
    }
}

使用反射(Reflection)来读取矩形类中的元数据

csharp 复制代码
using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
public class YyDebugInfo : Attribute
{
    private int bugNo;
    private string developer;
    private string lastReview;
    public string message;
    public YyDebugInfo(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; } }
}

namespace beyond.yy
{
    [YyDebugInfo(56, "beyond.yy", "2025-01-01", message = "This is a test message")]
    [YyDebugInfo(22, "beyond.yy", "2025-10-10", message = "This is a test message2")]
    class YyRectangle
    {
        protected double width, height;
        public YyRectangle(double w, double h) { width = w; height = h; }

        [YyDebugInfo(11, "beyond.yy", "2025-05-05", message = "This is a test message3 Area()")]
        public double Area() { return width * height; }

        [YyDebugInfo(12, "beyond.yy", "2025-06-06", message = "This is a test message4 Display()")]
        public void Display() { Console.WriteLine("Width = {0}, Height = {1}, Area = {2}", width, height, Area()); }
    }

    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            YyRectangle rec = new YyRectangle(12, 15);
            rec.Display();
            Type type = typeof(YyRectangle);

            // 遍历YyRectangle类上面的所有YyDebugInfo属性
            foreach (Object attribute in type.GetCustomAttributes(false))
            {
                YyDebugInfo debugInfo = attribute as YyDebugInfo;

                if (debugInfo != null)
                {
                    Console.WriteLine("BugNo: {0}, Developer: {1}, LastReview: {2}, Message: {3}", debugInfo.BugNo, debugInfo.Developer, debugInfo.LastReview, debugInfo.Message);
                }
            }

            foreach (MethodInfo method in type.GetMethods())
            {
                foreach (Attribute attribute in method.GetCustomAttributes(true))
                {
                    YyDebugInfo debugInfo = attribute as YyDebugInfo;
                    if (debugInfo != null)
                    {
                        Console.WriteLine("BugNo: {0}, Developer: {1}, LastReview: {2}, Message: {3}", debugInfo.BugNo, debugInfo.Developer, debugInfo.LastReview, debugInfo.Message);
                    }
                }
            }
            Console.ReadLine();
        }
    }
}

46,属性(Property)

属性(Property)是 (class)、结构体 (structure)和接口 (interface)的成员,类或结构体中的成员变量称为字段,属性是字段的扩展,使用访问器(accessors)可以读写私有字段的值。

属性没有确切的内存位置,但具有可读写或计算的访问器。

例如有一个名为 Student 的类,其中包含 age、name 和 code 三个私有字段,我们不能在类的范围以外直接访问这些字段,但是可以访问来这些私有字段的属性。

①访问器

属性访问器有两种,分别是 get 属性访问器和 set 属性访问器。其中 get 访问器用来返回属性的值,set 访问器用来为属性设置新值。在声明访问器时可以仅声明其中一个,也可以两个访问器同时声明

csharp 复制代码
// 声明 string 类型的 Code 属性
public string Code {
   get {
      return code;
   }
   set {
      code = value;
   }
}
// 声明 string 类型的 Name 属性
public string Name {
   get {
      return name;
   }
   set {
      name = value;
   }
}
// 声明 int 类型的 Age 属性
public int Age {
   get {
      return age;
   }
   set {
      age = value;
   }
}

示例演示属性的用法

csharp 复制代码
using System;
using System.Reflection;

namespace beyond.yy
{

    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            YyStudent student = new YyStudent();
            student.Name = "小明";
            student.Age = 20;
            student.Code = "001";

            Console.WriteLine("学生信息:{0}", student);

            //增加年龄
            student.Age += 1;
            Console.WriteLine("学生信息:{0}", student);
            Console.ReadKey();
        }
    }

    class YyStudent 
    {
        private string name;
        private int age;
        private string code;

        public string Name { get { return name; } set { name = value; } }
        public int Age { get { return age; } set { age = value; } }
        public string Code { get { return code; } set { code = value; } }
        public override string ToString()
        {
            return "编号 = " + Code + ", 姓名 = " + Name + ", 年龄 = " + Age;
        }
    }
}

②抽象属性

抽象类中可以拥有抽象属性,这些属性会在派生类中实现

csharp 复制代码
using System;
using System.Reflection;

namespace beyond.yy
{

    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            YyStudent student = new YyStudent();
            student.Name = "小明";
            student.Age = 20;
            student.Code = "001";

            Console.WriteLine("学生信息:{0}", student);

            //增加年龄
            student.Age += 1;
            Console.WriteLine("学生信息:{0}", student);
            Console.ReadKey();
        }
    }

    class YyStudent 
    {
        private string name;
        private int age;
        private string code;

        public string Name { get { return name; } set { name = value; } }
        public int Age { get { return age; } set { age = value; } }
        public string Code { get { return code; } set { code = value; } }
        public override string ToString()
        {
            return "编号 = " + Code + ", 姓名 = " + Name + ", 年龄 = " + Age;
        }
    }

    public abstract class YyPerson 
    {
        public abstract string Name { get; set; }
        public abstract int Age { get; set; }
    }
}

47,索引器(Indexer)

索引器(英文名:Indexer)是类中的一个特殊成员,它能够让对象以类似数组的形式来操作 ,使程序看起来更为直观,更容易编写

索引器与属性类似,在定义索引器时同样会用到 get 和 set 访问器,不同的是,访问属性不需要提供参数访问索引器则需要提供相应的参数

①定义索引器

属性的定义需要提供属性名称,而索引器则不需要具体名称,而是使用 this 关键字来定义

csharp 复制代码
索引器类型 this[int index]
{
    // get 访问器
    get
    {   
        // 返回 index 指定的值
    }

    // set 访问器
    set
    {
        // 设置 index 指定的值
    }
}
csharp 复制代码
using System;
using System.Reflection;

namespace beyond.yy
{

    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            Beyondyanyu myBeyondyanyu = new Beyondyanyu();
            myBeyondyanyu[0] = "yanyu";
            myBeyondyanyu[1] = "beyond";
            myBeyondyanyu[2] = "yy";
            myBeyondyanyu[3] = "huangjiaju";
            for (int i = 0; i < Beyondyanyu.size; i++) 
            {
                Console.WriteLine(myBeyondyanyu[i]);
            }
            Console.ReadKey();
        }

        static public int size = 10;
        private string[] namelist = new string[size];
        public Beyondyanyu()
        {
            for (int i = 0; i < size; i++)
            {
                namelist[i] = "null";
            }
        }

        public string this[int index]
        {
            get
            {
                string temp;
                if (index >= 0 && index <= size - 1)
                {
                    temp = namelist[index];
                }
                else
                {
                    temp = "";
                }
                return temp;
            }
            set 
            {
                if (index >= 0 && index <= size - 1)
                {
                    namelist[index] = value;
                }
            }
        }
    }
}

②索引器重载

索引器可以被重载,而且在声明索引器时也可以带有多个参数 ,每个参数可以是不同的类型

索引器中的索引不必是整数,也可以是其他类型,例如字符串类型。

csharp 复制代码
using System;
using System.Reflection;

namespace beyond.yy
{

    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            Beyondyanyu myBeyondyanyu = new Beyondyanyu();
            myBeyondyanyu[0] = "yanyu";
            myBeyondyanyu[1] = "beyond";
            myBeyondyanyu[2] = "yy";
            myBeyondyanyu[3] = "huangjiaju";

            //使用带有int参数的第一个索引器
            for (int i = 0; i < Beyondyanyu.size; i++) 
            {
                Console.WriteLine(myBeyondyanyu[i]);
            }

            Console.WriteLine("The index of yanyu is " + myBeyondyanyu["yanyu"]);//使用带有string参数的第二个索引器

            Console.ReadKey();
        }

        static public int size = 10;
        private string[] namelist = new string[size];
        public Beyondyanyu()
        {
            for (int i = 0; i < size; i++)
            {
                namelist[i] = "null";
            }
        }

        public string this[int index]
        {
            get
            {
                string temp;
                if (index >= 0 && index <= size - 1)
                {
                    temp = namelist[index];
                }
                else
                {
                    temp = "";
                }
                return temp;
            }
            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;
            }
        }
    }
}

48,委托(Delegate)

委托(Delegate)类似于 C 或 C++ 中的函数指针 ,是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用

委托特别适用于实现事件和回调方法,所有的委托都派生自 System.Delegate 类

在实例化委托时,可以将委托的实例与具有相同返回值类型的方法相关联,这样就可以通过委托来调用方法。另外,使用委托还可以将方法作为参数传递给其他方法

委托具有以下特点:

  • 委托类似于 C/C++ 中的函数指针,但委托是完全面向对象的。另外,C++ 中的指针会记住函数,而委托则是同时封装对象实例和方法;
  • 委托允许将方法作为参数进行传递;
  • 委托可用于定义回调方法;
  • 委托可以链接在一起,例如可以对一个事件调用多个方法;
  • 方法不必与委托类型完全匹配;
  • C# 2.0 版引入了匿名函数的概念,可以将代码块作为参数(而不是单独定义的方法)进行传递。C# 3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。匿名方法和 Lambda 表达式都可编译为委托类型,这些功能现在统称为匿名函数

①声明委托

声明委托需要使用 delegate 关键字

csharp 复制代码
delegate <return type> delegate-name(<parameter list>)

其中 return type 为返回值类型,delegate-name 为委托的名称,parameter list 为参数列表。

委托可以引用与委托具有相同签名的方法,也就是说委托在声明时即确定了委托可以引用的方法。

②实例化委托

委托一旦声明,想要使用就必须使用 new 关键字来创建委托的对象,同时将其与特定的方法关联。

csharp 复制代码
public delegate void printString(string s);                      // 声明一个委托
...
printString ps1 = new printString(WriteToScreen);          // 实例化委托对象并将其与 WriteToScreen 方法关联
printString ps2 = new printString(WriteToFile);            // 实例化委托对象并将其与 WriteToFile 方法关联

演示委托的声明、实例化和使用,该委托可用于引用带有一个整型参数的方法,并返回一个整型值

csharp 复制代码
using System;
using System.Reflection;

//定义委托
public delegate int YyDelegate(int x);
public delegate int YyDelegate_get();

namespace beyond.yy
{

    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            // 创建委托实例
            YyDelegate myDelegate_add = new YyDelegate(AddNum);
            YyDelegate myDelegate_sub = new YyDelegate(SubNum);
            YyDelegate myDelegate_mul = new YyDelegate(MulNum);
            YyDelegate myDelegate_div = new YyDelegate(DivNum);
            YyDelegate_get myDelegate_get = new YyDelegate_get(GetNum);

            //使用委托
            Console.WriteLine("num + 10 = " + myDelegate_add(10));//10+10=20
            Console.WriteLine("num - 10 = " + myDelegate_sub(10));//20-10=10
            Console.WriteLine("num * 10 = " + myDelegate_mul(10));//10*10=100
            Console.WriteLine("num / 10 = " + myDelegate_div(10));//100/10=10
            Console.WriteLine("num = " + myDelegate_get());

            Console.ReadKey();
        }

        static int num = 10;
        public static int AddNum(int x)
        {
            num += x;
            return num;
        }
        public static int SubNum(int x)
        {
            num -= x;
            return num;
        }
        public static int MulNum(int x)
        {
            num *= x;
            return num;
        }
        public static int DivNum(int x)
        {
            num /= x;
            return num;
        }
        public static int GetNum() { return num; }
    }
}

③多播委托(合并委托)

委托对象有一个非常有用的属性,那就是可以通过使用+运算符将多个对象分配给一个委托实例,同时还可以使用-运算符从委托中移除已分配的对象,当委托被调用时会依次调用列表中的委托。委托的这个属性被称为委托的多播,也可称为组播,利用委托的这个属性,可以创建一个调用委托时要调用的方法列表

仅可合并类型相同的委托

csharp 复制代码
using System;
using System.Reflection;

//定义委托
public delegate int YyDelegate(int x);
public delegate int YyDelegate_get();

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            // 创建委托实例
            YyDelegate myDelegate_add = new YyDelegate(AddNum);
            YyDelegate myDelegate_sub = new YyDelegate(SubNum);
            YyDelegate myDelegate_mul = new YyDelegate(MulNum);
            YyDelegate myDelegate_div = new YyDelegate(DivNum);
            YyDelegate_get myDelegate_get = new YyDelegate_get(GetNum);

            //多播委托(合并委托)
            YyDelegate test;
            test = myDelegate_add;
            test += myDelegate_sub;
            test += myDelegate_mul;
            test += myDelegate_div;

            test(5);
            // 调用委托
            Console.WriteLine("num = {0}", myDelegate_get());

            //使用委托
            //Console.WriteLine("num + 10 = " + myDelegate_add(10));//10+10=20
            //Console.WriteLine("num - 10 = " + myDelegate_sub(10));//20-10=10
            //Console.WriteLine("num * 10 = " + myDelegate_mul(10));//10*10=100
            //Console.WriteLine("num / 10 = " + myDelegate_div(10));//100/10=10
            //Console.WriteLine("num = " + myDelegate_get());

            Console.ReadKey();
        }

        static int num = 10;
        public static int AddNum(int x)
        {
            num += x;
            return num;
        }
        public static int SubNum(int x)
        {
            num -= x;
            return num;
        }
        public static int MulNum(int x)
        {
            num *= x;
            return num;
        }
        public static int DivNum(int x)
        {
            num /= x;
            return num;
        }
        public static int GetNum() { return num; }
    }
}

定义一个委托 printString,使用这个委托来调用两个方法,第一个把字符串打印到控制台,第二个把字符串打印到文件:

csharp 复制代码
using System;
using System.Reflection;

//委托声明
public delegate void YyDelegate_printstring(string str);

namespace beyond.yy
{

    class Beyondyanyu
    {
        static FileStream fs;
        static StreamWriter sw;

        //打印输出到控制台
        public static void WriteToScreen(string str)
        {
            Console.WriteLine("The String is:{0} " + str);
        }

        //打印输出到文件
        public static void WriteToFile(string str)
        {
            try
            {
                if (fs == null)
                {
                    fs = new FileStream("./log.txt", FileMode.Create, FileAccess.Write);
                    sw = new StreamWriter(fs);
                }
                sw.WriteLine(str);
                sw.Flush();
                sw.Close();
                fs.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
            }
        }
        //把委托作为参数,并使用它调用方法
        public static void sendString(YyDelegate_printstring del)
        {
            del("beyondyanyu!");
        }

        static void Main(string[] args)
        {
            YyDelegate_printstring del = new YyDelegate_printstring(WriteToScreen);
            YyDelegate_printstring del2 = new YyDelegate_printstring(WriteToFile);

            sendString(del);
            sendString(del2);

            Console.ReadKey();
        }
    }
}

49,事件(Event)

事件(Event)可以看作是用户的一系列操作,例如点击键盘的某个按键、单击/移动鼠标等,当事件发生时我们可以针对事件做出一系列的响应,例如退出程序、记录日志等等。C# 中线程之间的通信就是使用事件机制实现的。

事件需要在类中声明和触发,并通过委托与事件处理程序关联。

事件可以分为发布器和订阅器两个部分,其中发布器是一个包含事件和委托的对象,事件和委托之间的联系也定义在这个类中,发布器类的对象可以触发事件,并使用委托通知其他的对象;订阅器则是一个接收事件并提供事件处理程序的对象,发布器类中的委托调用订阅器类中的方法(事件处理程序)。

  • 发布器确定何时触发事件,订阅器确定对事件作出何种响应;
  • 一个事件可以拥有多个订阅器,同时订阅器也可以处理来自多个发布器的事件;
  • 没有订阅器的事件永远也不会触发;
  • 事件通常用于定义针对用户的操作,例如单击某个按钮;
  • 如果事件拥有多个订阅器,当事件被触发时会同步调用所有的事件处理程序;
  • 在 .NET 类库中,事件基于 EventHandler 委托和 EventArgs 基类。

若要在类中声明一个事件,首先需要为该事件声明一个委托类型,然后使用 event 关键字来声明事件本身

csharp 复制代码
public delegate void delegate_name(string status);

// 基于上面的委托定义事件
public event delegate_name event_name;
//定义了一个名为 delegate_name 和名为 event_name 的事件,当事件触发的时侯会调用委托
//委托名称:delegate_name
//事件名称:event_name
csharp 复制代码
using System;
using System.Reflection;

//委托声明
public delegate void YyDelegate_printstring(string str);

namespace beyond.yy
{
    class Beyondyanyu
    {
        /***********发布器类***********/
        public class PublisherDemo
        {
            private string value;
            public delegate void MyEntrust(string str);
            public event MyEntrust MyEvent;
            public void SetValue(string s)
            {
                value = s;
                MyEvent(value);     // 触发事件
            }
        }
        /***********订阅器类***********/
        public class SubscriberDemo
        {
            public void printf(string str)
            {
                Console.WriteLine(str);
            }
        }

        static void Main(string[] args)
        {
            PublisherDemo e = new PublisherDemo(); /* 实例发布器类*/
            SubscriberDemo v = new SubscriberDemo(); /* 实例订阅器类 */
            e.MyEvent += new PublisherDemo.MyEntrust(v.printf);
            e.SetValue("Beyondyanyu");

            Console.ReadKey();
        }
    }
}

50,集合(Collection)

集合类(Collection)是专门用于数据存储和检索的类,类中提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。大多数集合类都实现了相同的接口。

集合类的用途多种多样,例如可以动态的为元素分配内存、根据索引访问列表项等等,这些类创建 Object 类的对象集合,Object 类是 C# 中所有数据类型的基类。

在 System.Collections.Generic,System.Collections.Concurrent 和 System.Collections 命名空间下提供了许多集合类型,每种集合类型都有特定的用途,下面以 System.Collection 命名空间为例,该命名空间下提供的集合类型如下表所示

描述和用法
动态数组(ArrayList) 动态数组表示可被单独索引的对象的有序集合。 动态数组基本上与数组相似,唯一不同的是动态数组可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整自身的大小。 另外,动态数组也允许在列表中进行动态内存分配、增加、搜索、排序等等。
哈希表(Hashtable) 哈希表可以使用键来访问集合中的元素。 哈希表中的每一项都由一个键/值对组成,键用于访问集合中的指定项。
排序列表(SortedList) 排序列表是数组和哈希表的组合,可以使用键或索引来访问列表中的各项。 排序列表中包含一个可使用键或索引访问各项的列表,如果您使用索引访问各项,则它是一个动态数组,如果您使用键访问各项,则它就是一个哈希表。 另外,排序列表中的各项总是按键值进行排序的。
堆栈(Stack) 堆栈代表了一个后进先出的对象集合。 当您需要对各项进行后进先出的访问时,则可以使用堆栈。为堆栈中添加一项称为推入项目,从堆栈中移除一项称为弹出项目。
队列(Queue) 队列代表了一个先进先出的对象集合。 当您需要对各项进行先进先出的访问时,则可以使用队列。为队列中添加项目称为入队,为队列中移除项目称为出队。
点阵列(BitArray) 点阵列代表了一个使用 1 和 0 来表示的二进制数组。 当您需要存储比特位,但是事先不知道具体位数时,则可以使用点阵列。可以使用整型索引从点阵列集合中访问各项,索引从零开始。

51,动态数组(ArrayList)

动态数组(ArrayList)代表了可被单独索引的对象的有序集合。动态数组基本上可以代替数组,唯一与数组不同的是,动态数组可以使用索引在指定的位置添加和移除指定的项目,动态数组会自动重新调整自身的大小。另外,动态数组允许在列表中进行动态内存分配、增加、搜索、排序等操作。

①ArrayList类中的属性

属性 描述
Capacity 获取或设置动态数组中可以包含的元素个数
Count 获取动态数组中实际包含的元素个数
IsFixedSize 判断动态数组是否具有固定大小
IsReadOnly 判断动态数组是否只读
IsSynchronized 判断访问动态数组是否同步(线程安全)
Item[Int32] 获取或设置指定索引处的元素
SyncRoot 获取一个对象用于同步访问动态数组

②ArrayList类中的方法

方法名 描述
public virtual int Add(object value) 将对象添加到动态数组的末尾
public virtual void AddRange(ICollection c) 将 ICollection 的元素添加到动态数组的末尾
public virtual void Clear() 从动态数组中移除所有的元素
public virtual bool Contains(object item) 判断某个元素是否在动态数组中
public virtual ArrayList GetRange(int index, int count) 返回一个动态数组,表示源动态数组中元素的子集
public virtual int IndexOf(object) 搜索整个动态数组,并返回对象在动态数组中第一次出现的索引,索引从零开始
public virtual void Insert(int index, object value) 在动态数组的指定索引处插入一个元素
public virtual void InsertRange(int index, ICollection c) 在动态数组的指定索引处插入某个集合的元素
public virtual void Remove(object obj) 从动态数组中移除指定的对象
public virtual void RemoveAt(int index) 移除动态数组中指定索引处的元素
public virtual void RemoveRange(int index, int count) 从动态数组中移除某个范围的元素
public virtual void Reverse() 逆转动态数组中元素的顺序
public virtual void SetRange(int index, ICollection c) 复制某个集合的元素到动态数组中某个范围的元素上
public virtual void Sort() 对动态数组中的元素进行排序
public virtual void TrimToSize() 将容量设置为动态数组中元素的实际个数

关于 ArrayList 类中的完整属性和方法介绍,可以查阅 C# 官方文档

csharp 复制代码
using System;
using System.Collections;
using System.Reflection;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            ArrayList yy_list = new ArrayList();
            Console.WriteLine("以num1,num2,...的形式输入一个字符串:");
            string str = Console.ReadLine();
            string[] strArray = str.Split(',');
            foreach (string s in strArray)
            {
                yy_list.Add(s);
            }

            Console.WriteLine("Capacity of the ArrayList is: {0}", yy_list.Capacity);
            Console.WriteLine("Count of the ArrayList is: {0}", yy_list.Count);

            Console.Write("Contens of the ArrayList are: ");
            foreach (string i in yy_list) 
            {
                Console.Write(i + " ");
            }

            Console.WriteLine();
            Console.Write("Sorted Contens of the ArrayList are: ");
            yy_list.Sort();
            foreach (string i in yy_list)
            {
                Console.Write(i + " ");
            }

            Console.WriteLine();
            Console.ReadKey();
        }
    }
}

52,哈希表(Hashtable)

Hashtable(哈希表) 类表示根据键的哈希代码进行组织的键(key)/值(value)对的集合,可以使用键来访问集合中的元素。也就是说当需要使用键来访问指定元素时,可以选择使用哈希表

①哈希表类中的属性

属性 描述
Count 获取哈希表中包含的键值对的个数
IsFixedSize 获取一个值,用来表示哈希表是否具有固定大小
IsReadOnly 获取一个值,用来表示哈希表是否只读
Item 获取或设置与指定键关联的值
Keys 获取一个 ICollection,其中包含哈希表中的键
Values 获取一个 ICollection,其中包含哈希表中的值

②哈希表类中的方法

方法名 描述
public virtual void Add(object key, object value) 向哈希表中添加一个带有指定的键和值的元素
public virtual void Clear() 从哈希表中移除所有的元素
public virtual bool ContainsKey(object key) 判断哈希表是否包含指定的键
public virtual bool ContainsValue(object value) 判断哈希表是否包含指定的值
public virtual void Remove(object key) 从哈希表中移除带有指定的键的元素

关于 Hashtable 类中的完整属性和方法介绍,可以查阅 C# 官方文档

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            Hashtable Yyht = new Hashtable();
            Yyht.Add("001", "张三");
            Yyht.Add("002", "李四");
            Yyht.Add("003","王五");

            if (Yyht.ContainsKey("王强")) Console.WriteLine("张三在Hashtable中");
            else Yyht.Add("004","王强");

            //获取Hashtable所有键的集合
            ICollection ic = Yyht.Keys;
            foreach (string key in ic) 
            {
                Console.WriteLine(key + " " + Yyht[key]);
            }
            Console.ReadKey();
        }
    }
}

53,排序列表(SortedList)

SortedList 类用来表示键/值对的集合,这些键/值对按照键值进行排序,并且可以通过键或索引访问集合中的各个项。

可以将排序列表 看作是数组和哈希表的组合 ,其中包含了可以使用键或索引访问各项的列表

如果使用索引 访问各项,那么它就是一个动态数组(ArrayList),如果使用访问各项,那么它就是一个哈希表(Hashtable)。

另外,集合中的各项总是按键值进行排序

①排序列表中的属性

属性 描述
Capacity 获取或设置排序列表中可包含的元素个数
Count 获取排序列表中的元素个数
IsFixedSize 判断排序列表是否具有固定大小
IsReadOnly 判断排序列表是否只读
Item 获取或设置排序列表中指定键所关联的值
Keys 获取一个包含排序列表中所有键的集合
Values 获取一个包含排序列表中所有值的集合

②排序列表中的方法

方法名 描述
public virtual void Add(object key, object value) 向排序列表中添加一个带有指定的键和值的元素
public virtual void Clear() 从排序列表中移除所有的元素
public virtual bool ContainsKey(object key) 判断排序列表中是否包含指定的键
public virtual bool ContainsValue(object value) 判断排序列表中是否包含指定的值
public virtual object GetByIndex(int index) 获取排序列表中指定索引处的值
public virtual object GetKey(int index) 获取排序列表中指定索引处的键
public virtual IList GetKeyList() 获取排序列表中的键
public virtual IList GetValueList() 获取排序列表中的值
public virtual int IndexOfKey(object key) 返回排序列表中指定键的索引,索引从零开始
public virtual int IndexOfValue(object value) 返回排序列表中指定值第一次出现的索引,索引从零开始
public virtual void Remove(object key) 从排序列表中移除带有指定键的元素
public virtual void RemoveAt(int index) 移除排序列表中指定索引处的元素
public virtual void TrimToSize() 将排序列表的容量设置为排序列表中元素的实际个数

关于 SortedList 类中的完整属性和方法介绍,可以查阅 C# 官方文档

和哈希表(Hashtable)基本一致,但是输出是按key排序好的结果

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            SortedList Yysl = new SortedList();
            Yysl.Add("001", "张三");
            Yysl.Add("002", "李四");
            Yysl.Add("003","王五");

            if (Yysl.ContainsKey("王强")) Console.WriteLine("张三在sortedlist中");
            else Yysl.Add("004","王强");

            //获取Hashtable所有键的集合
            ICollection ic = Yysl.Keys;
            foreach (string key in ic) 
            {
                Console.WriteLine(key + " " + Yysl[key]);
            }
            Console.ReadKey();
        }
    }
}

54,堆栈(Stack)

堆栈(Stack)类表示一个后进先出的对象集合,当您需要对项目进行后进先出的访问时,则可以使用堆栈。向堆栈中添加元素称为推入元素,从堆栈中移除元素称为弹出元素。

①Stack类中的属性

属性 描述
Count 获取堆栈中包含的元素个数
IsSynchronized 判断是否同步对堆栈的访问(线程安全)
SyncRoot 获取可用于同步对堆栈访问的对象

②Stack类中的方法

方法名 描述
public virtual void Clear() 从堆栈中移除所有的元素
public virtual bool Contains(object obj) 判断某个元素是否在堆栈中
public virtual object Peek() 返回在堆栈顶部的对象,但不移除它
public virtual object Pop() 移除并返回在堆栈顶部的对象
public virtual void Push(object obj) 向堆栈顶部添加一个对象
public virtual object[] ToArray() 复制堆栈到一个新的数组中

关于 Stack 类中的完整属性和方法介绍,可以查阅 C# 官方文档

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            Stack st = new Stack();
            st.Push('A');
            st.Push('B');
            st.Push('C');
            st.Push('D');
            st.Push('E');

            Console.WriteLine("当前堆栈中的元素为:");
            foreach (char item in st)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine();


            st.Push('F');
            st.Push('G');
            st.Push('H');
            Console.WriteLine("已将F、G、H压入堆栈");

            Console.WriteLine("堆栈中下一个可以弹出的值为:\r\n{0}", st.Peek());
            Console.WriteLine("当前堆栈中的元素为:");
            foreach (char item in st)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine();


            Console.WriteLine("删除值下面的值:");
            Console.WriteLine(st.Pop() + " ");
            Console.WriteLine(st.Pop() + " ");
            Console.WriteLine(st.Pop() + " ");
            Console.WriteLine(st.Pop() + " " + "\r\n");


            Console.WriteLine("当前堆栈中的元素为:");
            foreach (char item in st)
            {
                Console.WriteLine(item);
            }
        }
    }
}

55,队列(Queue)

队列(Queue 类)与堆栈类似,它代表了一个先进先出的对象集合,当您需要对项目进行先进先出访问时,则可以使用队列。

向队列中添加元素称为入队(enqueue),从堆栈中移除元素称为出队(deque)

①Queue类中的属性

属性 描述
Count 获取队列中包含的元素个数
IsSynchronized 判断是否同步对队列的访问(线程安全)
SyncRoot 获取可用于同步对队列访问的对象

②Queue类中的方法

方法名 描述
public virtual void Clear() 从队列中移除所有的元素
public virtual bool Contains(object obj) 判断某个元素是否在队列中
public virtual object Dequeue() 移除并返回在队列开头的对象
public virtual void Enqueue(object obj) 向队列的末尾处添加一个对象
public virtual object[] ToArray() 复制队列到一个新的数组中
public virtual void TrimToSize() 将队列的容量设置为队列中元素的实际个数

关于 Queue 类中的完整属性和方法介绍,可以查阅 C# 官方文档

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            Queue myQueue = new Queue();
            myQueue.Enqueue('A');
            myQueue.Enqueue('B');
            myQueue.Enqueue('C');
            myQueue.Enqueue('D');

            Console.WriteLine("当前队列中的元素:");
            foreach (char c in myQueue)
            {
                Console.Write(c + " ");
            }
            Console.WriteLine("\r\n");
            Console.WriteLine("向队列中添加元素E、F、G\r\n");
            myQueue.Enqueue('E');
            myQueue.Enqueue('F');

            Console.WriteLine("当前队列中的元素:");
            foreach (char c in myQueue)
            {
                Console.Write(c + " ");
            }

            Console.WriteLine();
            Console.WriteLine("从队列中删除以下元素\r");
            char ch = (char)myQueue.Dequeue();
            Console.WriteLine(ch + " ");
            ch = (char)myQueue.Dequeue();
            Console.WriteLine(ch + " ");
            ch = (char)myQueue.Dequeue();
            Console.WriteLine(ch + " ");
            ch = (char)myQueue.Dequeue();
            Console.WriteLine(ch + "\n");

            Console.WriteLine("当前队列中的元素:");
            foreach (char c in myQueue)
            {
                Console.Write(c + " ");
            }
            Console.ReadKey();
        }
    }
}

56,点阵列(BitArray)

BitArray 类用来管理一个紧凑型的位值数组,数组中的值均为布尔类型,其中 true(1)表示此位为开启,false(0)表示此位为关闭

当需要存储 (英文名"bit"数据存储的最小单位,也可称为比特),但事先又不知道具体位数时 ,就可以使用点阵列

当需要访问点阵列中的元素时,可以使用整型索引从点阵列中访问指定元素,索引从零开始。

①BitArray 类中的属性

属性 描述
Count 获取点阵列中包含的元素个数
IsReadOnly 判断 点阵列是否只读
Item 获取或设置点阵列中指定位置的值
Length 获取或设置点阵列中的元素个数

②BitArray 类中的方法

方法名 描述
public BitArray And(BitArray value) 对当前的点阵列中的元素和指定点阵列中相对应的元素执行按位与操作
public bool Get(int index) 获取点阵列中指定位置的位值
public BitArray Not() 反转当前点阵列中所有位的值,即将 true 设置为 false,将 false 设置为 true
public BitArray Or(BitArray value) 对当前点阵列中的元素和指定点阵列中的相对应的元素执行按位或操作
public void Set(int index, bool value) 把点阵列中指定位置的位设置为指定的值
public void SetAll(bool value) 把点阵列中的所有位设置为指定的值
public BitArray Xor(BitArray value) 对当前点阵列中的元素和指定点阵列中的相对应的元素执行按位异或操作

关于 BitArray 类中的完整属性和方法介绍,可以查阅 C# 官方文档

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            //创建两个大小为8的点阵列
            BitArray bitArray1 = new BitArray(8);
            BitArray bitArray2 = new BitArray(8);
            byte[] a = { 60 };//0 0 1 1 1 1 0 0,倒着输出
            byte[] b = { 22 };//0 0 0 1 0 1 1 0,倒着输出

            //把值60和22赋值给bitArray1和bitArray2
            bitArray1 = new BitArray(a);
            bitArray2 = new BitArray(b);

            //bitArray1的内容
            Console.WriteLine("点阵列bitArray1:60");
            for (int i = 0; i < bitArray1.Count; i++)
            {
                Console.Write("{0,-6}", bitArray1[i]);
            }
            Console.WriteLine();

            //bitArray2的内容
            Console.WriteLine("点阵列bitArray2:22");
            for (int i = 0; i < bitArray2.Count; i++)
            {
                Console.Write("{0,-6}", bitArray2[i]);
            }
            Console.WriteLine();


            BitArray bitArray3 = new BitArray(8);
            bitArray3 = bitArray1.And(bitArray2);
            //bitArray3的内容
            Console.WriteLine("点阵列bitArray3:60与22的与运算结果");
            for (int i = 0; i < bitArray3.Count; i++)
            {
                Console.Write("{0,-6}", bitArray3[i]);
            }
            Console.WriteLine();

            bitArray3 = bitArray1.Or(bitArray2);
            //bitArray3的内容
            Console.WriteLine("点阵列bitArray3:60与22的或运算结果");
            for (int i = 0; i < bitArray3.Count; i++)
            {
                Console.Write("{0,-6}", bitArray3[i]);
            }
            Console.WriteLine();

            Console.ReadKey();
        }
    }
}

57,泛型(Generic)

泛型(Generic)是一种规范,它允许我们使用占位符来定义类和方法,编译器会在编译时将这些占位符替换为指定的类型,利用泛型的这一特性我们可以定义通用类(泛型类)或方法(泛型方法)。

定义通用类需要使用尖括号<>,这里的尖括号用于将类或方法声明为泛型。

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    //定义泛型类
    class GenericClas<T> 
    {
        //定义泛型方法
        public GenericClas(T msg) 
        {
            Console.WriteLine(msg);
        }
    }

    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            GenericClas<string> str_gen = new GenericClas<string>("BeyondYanyu");
            GenericClas<int> int_gen = new GenericClas<int>(100);
            GenericClas<double> double_gen = new GenericClas<double>(3.14);
            GenericClas<char> char_gen = new GenericClas<char>('A');
            Console.ReadKey();
        }
    }
}

①泛型的特性

可以将泛型看作是一种增强程序功能的技术,泛型类和泛型方法兼具可重用性、类型安全性和效率,这是非泛型类和非泛型方法无法实现的。泛型通常与集合以及作用于集合的方法一起使用,System.Collections.Generic 命名空间下就包含几个基于泛型的集合类。下面总结了一些关于泛型的特性:

  • 使用泛型类型可以最大限度地重用代码、保护类型的安全性以及提高性能;
  • 泛型最常见的用途是创建集合类;
  • .NET 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类,您可以使用这些类来代替 System.Collections 中的集合类;
  • 可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托;
  • 也可以对泛型类进行约束以访问特定数据类型的方法;
  • 在泛型数据类型中所用类型的信息可在运行时通过使用反射来获取。

②泛型方法

通过类型参数声明泛型方法

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void YySwap<T>(ref T a, ref T b)
        {
            T temp;
            temp = a;
            a = b;
            b = temp;
        }

        static void Main(string[] args)
        {
            int a, b;
            char c, d;
            a = 10;
            b = 20;
            c = 'A';
            d = 'B';

            //在交换之前显示原来的值
            Console.WriteLine("Before Swap: a = {0}, b = {1}, c = {2}, d = {3}", a, b, c, d);

            //调用泛型方法进行交换
            YySwap<int>(ref a, ref b);
            YySwap<char>(ref c, ref d);

            //在交换之后显示新值
            Console.WriteLine("After Swap: a = {0}, b = {1}, c = {2}, d = {3}", a, b, c, d);
            Console.ReadKey();
        }
    }
}

③泛型委托

还可以使用类型参数定义泛型委托

csharp 复制代码
delegate T NumberChanger<T>(T n);

示例演示泛型委托的使用:

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        delegate T YyChanger<T>(T input);
        static int num = 10;

        public static int AddNum(int input)
        {
            num += input;
            return num;
        }
        public static int MultiplyNum(int input)
        {
            num *= input;
            return num;
        }
        public static int getNum() { return num; }


        static void Main(string[] args)
        {
            //创建委托实例
            YyChanger<int> add = new YyChanger<int>(AddNum);
            YyChanger<int> multiply = new YyChanger<int>(MultiplyNum);

            Console.WriteLine("num原先值为:" + getNum());
            //使用委托对象调用方法
            add(22);
            Console.WriteLine("num加22后值为:"+getNum());
            //使用委托对象调用方法
            multiply(3);
            Console.WriteLine("num乘3后值为:" + getNum());
            Console.ReadKey();
        }
    }
}

58,匿名函数

可以将匿名函数简单的理解为没有名称只有函数主体的函数。匿名函数提供了一种将代码块作为委托参数传递的技术,它是一个"内联"语句或表达式,可在任何需要委托类型的地方使用

匿名函数可以用来初始化命名委托传递命名委托 作为方法参数。

无需在匿名函数中指定返回类型,返回值类型是从方法体内的 return 语句推断出来的。

匿名函数是通过使用 delegate 关键字创建的委托实例来声明的

csharp 复制代码
delegate void NumberChanger(int n);
...
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: {0}", x);
};

代码块Console.WriteLine("Anonymous Method: {0}", x);是匿名函数的主体。

委托可以通过匿名函数调用,也可以通过普通有名称的函数调用,只需要向委托对象中传递相应的方法参数即可。注意,匿名函数的主体后面需要使用;结尾。

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        delegate void YyNumberChanger(int n);
        static int num = 10;
        public static void AddNumber(int n) 
        {
            num += n;
            Console.WriteLine("命名函数:{0}", num);
        }
        public static void MultiplyNumber(int n) 
        {
            num *= n;
            Console.WriteLine("命名函数:{0}", num);
        }

        static void Main(string[] args)
        {
            //使用匿名函数创建委托实例
            YyNumberChanger changer = delegate(int n) {Console.WriteLine("匿名函数:{0}", num); };
            //使用匿名函数调用委托
            changer(5);

            //使用命名函数实例化委托
            changer = new YyNumberChanger(MultiplyNumber);
            //使用命名函数调用委托
            changer(2);

            //使用另一个命名函数实例化委托
            changer = new YyNumberChanger(AddNumber);
            //使用另一个命名函数调用委托
            changer(3);

            Console.ReadKey();
        }
    }
}

59,指针变量与unsafe

为了保持类型的安全性,默认情况下 C# 是不支持指针的 ,但是如果使用 unsafe 关键字来修饰类或类中的成员,这样的类或类中成员就会被视为不安全代码,C# 允许在不安全代码中使用指针变量

在公共语言运行时 (CLR) 中,不安全代码是指无法验证的代码,不安全代码不一定是危险的,只是 CLR 无法验证该代码的安全性。

因此 CLR 仅会执行信任程序集中包含的不安全代码。

①指针变量

指针是一个变量,但是它的值是另一个变量的内存地址,在使用指针之前我们同样需要先声明指针

csharp 复制代码
type* var_name;
示例 说明
int* p p 是指向整数的指针
double* p p 是指向双精度数的指针
float* p p 是指向浮点数的指针
int** p p 是指向整数的指针的指针
int*[] p p 是指向整数的指针的一维数组
char* p p 是指向字符的指针
void* p p 是指向未知类型的指针

与声明变量相同,同样可以在一行代码中同时声明多个指针

csharp 复制代码
int* p1, p2, p3;  // 定义 p1、p2、p3 三个整数指针

指针类型不能从对象中继承,并且装箱和拆箱也不支持指针,但是不同的指针类型以及指针与整型之间可以进行转换。

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static unsafe void Main(string[] args)
        {
            double f = 3.1415926;
            double* p = &f;
            Console.WriteLine("数据的内容为:{0}",f);
            Console.WriteLine("数据在内存中的地址为:{0}",(int)p);

            Console.ReadKey();
        }
    }
}

②使用指针检索数据的值

可以使用 ToString() 来获取指针变量所指向的数据的值

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static unsafe void Main(string[] args)
        {
            double f = 3.1415926;
            double* p = &f;
            Console.WriteLine("变量f的值为:{0}",f);
            Console.WriteLine("指针p指向的值为:{0}",p->ToString());
            Console.WriteLine("指针p的值为:{0}",(int)p);

            Console.ReadKey();
        }
    }
}

③将指针作为参数传递给函数

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        public unsafe void swap(int* p, int* q) 
        {
            int temp = *p;
            *p = *q;
            *q = temp;
        }

        static unsafe void Main(string[] args)
        {
            Beyondyanyu yy = new Beyondyanyu();
            int var1 = 10;
            int var2 = 20;
            int* x = &var1;
            int* y = &var2;

            Console.WriteLine("调用swap函数之前:var1 = {0}, var2 = {1}", var1, var2);
            yy.swap(x, y);

            Console.WriteLine("调用swap函数之后:var1 = {0}, var2 = {1}", var1, var2);
            Console.ReadLine();
        }
    }
}

④使用指针访问数组元素

数组和指向该数组且与数组名称相同的指针是不同的数据类型,例如int* pint[] p就是不同的数据类型

可以增加指针变量 p 的值,因为它在内存中不是固定的,但数组地址在内存中是固定的,因此不能增加数组 p 的值

如果需要使用指针变量访问数组数据,可以像我们在 C 或 C++中所做的那样,使用 fixed 关键字来固定指针

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static unsafe void Main(string[] args)
        {
            int[] list = { 10, 20, 30, 50, 40 };
            fixed(int* p = list)

            //显示指针中数组的地址
            for (int i = 0; i < list.Length; i++)
            {
                Console.WriteLine("list[{0}]的内存地址为:{1}", i, (int)(p + i));
                Console.WriteLine("list[{0}]的值为:{1}", i, *(p + i));
            }
            Console.ReadKey();
        }
    }
}

⑤编译不安全代码

为了编译不安全代码,在编译时必须使用unsafe命令

例如编译包含不安全代码的 demo.cs 程序的命令:

csharp 复制代码
csc /unsafe demo.cs
或
csc -unsafe demo.cs

如果使用的是 Visual Studio,那么需要在项目属性中启用不安全代码,具体步骤如下:

  • 通过双击资源管理器(Solution Explorer)中的属性(properties)节点,打开项目属性(project properties);
  • 点击 Build 标签页;
  • 选择选项"Allow unsafe code"。

60,多线程

多线程就是多个线程同时工作的过程,可以将线程看作是程序的执行路径,每个线程都定义了一个独特的控制流,用来完成特定的任务

使用多线程可以节省 CPU 资源,同时提高应用程序的执行效率,现代操作系统对并发编程的实现就用到了多线程

到目前为止我们编写的示例程序都是单线程的应用程序,这样的应用程序一次只能执行一个任务。

①线程生命周期

线程生命周期开始于创建 System.Threading.Thread 类对象 的时候,当线程被终止或完成执行时生命周期终止

  • 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况;
  • 就绪状态:当线程准备好运行并等待 CPU 周期时的状况;
  • 不可运行状态:下面的几种情况下线程是不可运行的:
    • 已经调用 Sleep 方法;
    • 已经调用 Wait 方法;
    • 通过 I/O 操作阻塞。
  • 死亡状态:当线程已完成执行或已中止时的状况。

②主线程

System.Threading.Thread 类用于处理线程,它允许在多线程应用程序中创建和访问各个线程。

在多线程中执行的第一个线程称为主线程当 C# 程序开始执行时,将自动创建主线程,而使用 Thread 类创建的线程则称为子线程,可以使用 Thread 类的 CurrentThread 属性访问线程。

示例程序演示主线程的执行

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {
        static void Main(string[] args)
        {
            Thread yy = Thread.CurrentThread;//拿到当前的进程
            yy.Name = "Main Thread";//为其赋一个名称
            Console.WriteLine("当前为:{0}", yy.Name);
            Console.ReadLine();
        }
    }
}

③Thread类中的属性和方法

属性 描述
CurrentContext 获取线程正在执行的上下文
CurrentCulture 获取或设置当前线程的区域性
CurrentPrincipal 获取或设置线程的当前负责人(对基于角色的安全性而言)
CurrentThread 获取当前正在运行的线程
CurrentUICulture 获取或设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源
ExecutionContext 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息
IsAlive 获取当前线程的执行状态
IsBackground 获取或设置一个值,该值表示某个线程是否为后台线程
IsThreadPoolThread 获取线程是否属于托管线程池
ManagedThreadId 获取当前托管线程的唯一标识符
Name 获取或设置线程的名称
Priority 获取或设置线程的调度优先级
ThreadState 获取当前线程的状态
方法名 描述
public void Abort() 在调用此方法的线程上引发 ThreadAbortException,以终止此线程
public static LocalDataStoreSlot AllocateDataSlot() 在所有的线程上分配未命名的数据槽,为了获得更好的性能,请改用以 ThreadStaticAttribute 特性标记的字段
public static LocalDataStoreSlot AllocateNamedDataSlot(string name) 在所有线程上分配已命名的数据槽,为了获得更好的性能,请改用以 ThreadStaticAttribute 特性标记的字段
public static void BeginCriticalRegion() 通知主机执行将要进入一个代码区域,在该代码区域内线程中止或未经处理的异常的影响可能会危害应用程序域中的其他任务
public static void BeginThreadAffinity() 通知主机托管代码将要执行依赖于当前物理操作系统线程的标识指令
public static void EndCriticalRegion() 通知主机执行将要进入一个代码区域,在该代码区域内线程中止或未经处理的异常仅影响当前任务
public static void EndThreadAffinity() 通知主机托管代码已执行完依赖于当前物理操作系统线程的标识指令
public static void FreeNamedDataSlot(string name) 为进程中的所有线程消除名称与数据槽之间的关联。为了获得更好的性能,请改用以 ThreadStaticAttribute 特性标记的字段
public static Object GetData(LocalDataStoreSlot slot) 检索当前线程中指定的值。为了获得更好的性能,请改用以 ThreadStaticAttribute 特性标记的字段
public static AppDomain GetDomain() 返回当前线程运行的域
public static AppDomain GetDomainID() 返回应用程序域的唯一标识符
public static LocalDataStoreSlot GetNamedDataSlot(string name) 查找已命名的数据槽。为了获得更好的性能,请改用以 ThreadStaticAttribute 特性标记的字段
public void Interrupt() 中断处于 WaitSleepJoin 状态的线程
public void Join() 在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。此方法有不同的重载形式
public static void MemoryBarrier() 按如下方式同步内存访问:执行当前线程的处理器在对指令重新排序时不能采用先执行 MemoryBarrier 调用之后的内存存取,再执行 MemoryBarrier 调用之前的内存存取的方式
public static void ResetAbort() 取消为当前线程请求的 Abort
public static void SetData(LocalDataStoreSlot slot, Object data) 在当前正在运行的线程上的指定槽中为此线程的当前域设置数据。为了获得更好的性能,请改用以 ThreadStaticAttribute 特性标记的字段
public void Start() 开始一个线程
public static void Sleep(int millisecondsTimeout) 让线程暂停一段时间
public static void SpinWait(int iterations) 让线程等待一段时间,时间长短由 iterations 参数定义
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) 读取字段值。无论处理器的数目或处理器缓存状态如何,该值都是由计算机处理器写入的最新值
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) 立即向字段中写入一个值,并使该值对计算机中的所有处理器都可见
public static bool Yield() 终止当前正在调用的线程并执行另一个准备运行的线程(由操作系统选择将要执行的另一个线程)

④创建线程

通过扩展 Thread 类创建线程,然后使用扩展的 Thread 类调用 Start() 方法开始执行子线程

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {

        public static void CallToChildThread() { Console.WriteLine("Child Thread is runing..."); }

        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("Creat child thread in main function...");
            Thread childThread = new Thread(childref);
            childThread.Start();

            Console.ReadKey();
        }
    }
}

⑤管理线程

Thread 类提供了各种管理线程的方法,比如可以使用 sleep() 方法使线程在特定时间段内暂停

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {

        public static void CallToChildThread() 
        {
            Console.WriteLine("Child Thread is runing...");
            int sleep = 5000;
            Console.WriteLine("Child Thread will sleep for " + sleep + " milliseconds.");
            Thread.Sleep(sleep);
            Console.WriteLine("Child Thread is awake now.");
        }

        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("Creat child thread in main function...");
            Thread childThread = new Thread(childref);
            childThread.Start();

            Console.ReadKey();
        }
    }
}

⑥销毁线程

Thread 类中提供了 Abort() 方法用于销毁线程,Abort() 方法会抛出一个 threadabortexception 异常来中止线程,这个异常不能被捕获!

csharp 复制代码
using System.Collections;

namespace beyond.yy
{
    class Beyondyanyu
    {

        public static void CallToChildThread() 
        {
            try
            {
                Console.WriteLine("Child thread is running...");
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine("Child thread is running...{0}", i);
                }
                Console.WriteLine("Child thread is finished.");
            }
            catch (ThreadAbortException ex)
            {
                Console.WriteLine("Child thread is aborted.");
            }
            finally 
            {
                Console.WriteLine("Child thread is not captured.");
            }

        }

        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine("Creat child thread in main function...");
            Thread childThread = new Thread(childref);
            childThread.Start();

            // main thread sleep 2s
            Thread.Sleep(2000);

            //abort child thread
            Console.WriteLine("Abort child thread in main function...");
            childThread.Abort();//会报错,因为Abort() 方法会抛出一个 threadabortexception 异常来中止线程,这个异常不能被捕获

            Console.ReadKey();
        }
    }
}
相关推荐
爱吃小胖橘3 小时前
Unity网络开发--超文本传输协议Http(1)
开发语言·网络·网络协议·http·c#·游戏引擎
IT小农工4 小时前
Word 为每一页设置不同页边距(VBA 宏)
开发语言·c#·word
sali-tec4 小时前
C# 基于halcon的视觉工作流-章42-手动识别文本
开发语言·人工智能·算法·计算机视觉·c#·ocr
咕白m6256 小时前
C# 合并多个PDF文档:高效解决方案
c#·.net
c#上位机8 小时前
wpf之MVVM中只读属性更新界面
c#·wpf·mvvm
技术支持者python,php11 小时前
winform本地上位机-ModbusRTC1.上位机控制台与数据监控(数据监控架构思维与图表系列)
c#
kalvin_y_liu13 小时前
DeploySharp开源发布:让C#部署深度学习模型更加简单
深度学习·开源·c#
c#上位机14 小时前
wpf之GroupBox
c#·wpf
CodeCraft Studio14 小时前
国产化PDF处理控件Spire.PDF教程:C#中轻松修改 PDF 文档内容
前端·pdf·c#·.net·spire.pdf·编辑pdf·修改pdf