扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

C#:基本语法看这一篇就够了

01/16 13:31
833
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论
本文来自w3c教程的C#教程的学习笔记,对其中的示例有所删减与变更,建议以以下链接为准。C# 简介_w3cschool

一、C# 与 C++的区别

C# 和 C++ 在语法上有许多相似之处,因为两者都是 C 语言家族的成员,继承了许多 C 语言的语法特性。但它们也有不少差异,主要体现在内存管理、面向对象编程的支持、以及语言设计哲学等方面。有关C/C++文档见以下链接:C/C++:基本语法看这一篇就够了_h,.n:)!i-CSDN博客

1. 基本语法结构

特性 C# C++
程序入口点 static void Main(string[] args) int main()
语句结束 语句以分号 ; 结束 语句以分号 ; 结束
大括号 使用大括号 {} 来表示代码块 使用大括号 {} 来表示代码块
变量声明 int x = 5; int x = 5;

2. 面向对象编程

特性 C# C++
类的声明 class MyClass { } class MyClass { };
类的继承 class Derived : Base { } class Derived : public Base { };
访问修饰符 public, private, protected, internal public, private, protected
构造函数 public MyClass() {} MyClass() {}
析构函数 使用 IDisposable 接口来实现资源释放,自动垃圾回收, 以及~MyClass() {} ~MyClass() {}
接口 interface IMyInterface { } 没有直接的接口概念,使用抽象类来实现类似接口的功能
属性 public int MyProperty { get; set; } 没有直接的属性概念,需要通过方法来实现属性功能

3. 内存管理

特性 C# C++
内存管理 自动垃圾回收 (GC) 管理内存 手动内存管理,使用 newdeletemallocfree
指针 不直接支持指针,只有通过 unsafe 代码块才支持 支持指针,允许直接访问内存
引用 支持引用类型,变量和对象引用的赋值是按引用传递的 支持引用类型,使用 & 符号声明引用变量

4. 异常处理

特性 C# C++
异常处理 try, catch, finally try, catch, throw
异常声明 异常不需要在方法声明中声明 异常可以声明为 throw() 来表示不抛出异常
自定义异常类 可以继承 Exception 类,定义自定义异常类 可以继承 std::exception 类,定义自定义异常类

5. 泛型与模板

特性 C# C++
泛型 使用 generic 类型:List<T> 使用模板:template <typename T> class MyClass { };
类型安全 泛型类型在运行时类型安全 模板类型在编译时类型安全,但没有运行时类型检查

6. 内联函数与委托/回调

特性 C# C++
内联函数 使用 inline 不常见,更多使用方法封装和事件 使用 inline 关键字来提示编译器内联函数
委托与事件 委托用于封装方法引用,事件用于发布/订阅模式 使用函数指针或 std::function 来实现回调功能

7. 标准库/类库

特性 C# C++
标准库 .NET 提供丰富的类库,如 System, System.Linq 标准库提供多种功能,如 STL(标准模板库)、iostream
字符串类型 string 类型为引用类型,自动管理内存 std::string 为值类型,需要手动管理内存

8. 多线程与并发

特性 C# C++
多线程 使用 System.Threading 来管理线程和并发任务 使用 std::thread 来创建线程,并通过 std::mutex 等管理同步
异步编程 使用 asyncawait 来简化异步编程 C++11 引入了异步编程,但没有专门的关键字,通常使用 std::async

9. 语言特点

特性 C# C++
语言设计 面向对象为主,强调开发效率和安全性 支持面向对象、面向过程和泛型编程,强调对性能的控制
多态实现 通过虚方法和接口实现多态 通过虚函数和继承实现多态
类型系统 强类型语言,类型检查严格 强类型语言,类型检查严格

二、开发环境

C# 的开发环境有很多种,Visual Studio、Visual Studio Code等,这里采用Visual Studio。

Visual Studio 是由微软(Microsoft)开发的集成开发环境(IDE),主要用于开发 Windows 和跨平台应用程序,支持多种编程语言,包括 C#、C++、Python、JavaScript、F# 等。它是开发 Windows 应用程序、Web 应用程序、移动应用程序以及游戏等的强大工具之一,具有丰富的功能和广泛的使用场景。

很强,也很复杂,以至于感觉有点难用,所以得讲,避免大家被劝退,这里简单走一下流程。

1、下载与安装

下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux (microsoft.com)

Visual Studio安装可以根据自己需要的组件进行安装,如果还不知道自己需要什么组件,先采用默认安装。后面可以双击安装程序,点击修改,继续组件的安装。

2、新建应用

这个是窗体应用,就是cmd那样的窗体,新建完成后会加载窗体环境与配置运行环境,文件很多。如果是一些复杂的应用,加载的东西更多。不同的应用,加载的环境有些区别,有些应用,对于小白来说,不太友好。窗体应用算是比较友好的了。

3、解决方案

Visual Studio 中,"解决方案"(Solution)是一个容器,用来组织一个或多个相关的项目。解决方案本质上是 Visual Studio 中管理和部署项目的方式,它帮助开发者组织代码、资源和依赖项,并控制多个项目之间的关系。

(1)解决方案的基本概念

解决方案(Solution):解决方案是 Visual Studio 中的顶级容器,包含了一个或多个项目。一个解决方案可以包含多个不同类型的项目,例如一个项目是 C# 应用程序,另一个是数据库项目,或者 Web 应用程序等。解决方案通过 .sln 文件来表示,文件名后缀为 .sln,该文件包含了解决方案内各个项目的元数据和设置。

项目(Project):每个解决方案可以包含多个项目。项目是实现特定功能的代码单元,它包含源代码、资源文件、配置文件等内容。每个项目有自己的构建配置和输出目标,如 DLL、EXE 或其他类型的可执行文件。项目通过 .csproj 文件来表示(C# 项目是 .csproj 格式),该文件管理了项目的所有资源和配置。

解决方案资源管理器(Solution Explorer):它是 Visual Studio 的一个工具窗口,用来展示和管理当前打开的解决方案和项目结构。在这个窗口中,你可以查看和操作项目中的文件、引用的库、配置文件等。在这个窗口中,解决方案(Solution)作为最上层的节点展示,下面是项目(Project)和项目中的具体文件、文件夹等内容。

项目类型:项目可以是多种类型的应用程序,例如控制台应用程序(Console App)、Web 应用程序、类库(Library)等。每个项目的目标是实现特定的功能。

(2)对于上述界面

解决方案"ConsoleApp1" (1 个项目/共 1 个):这是一个名为 ConsoleApp1 的解决方案,它包含一个项目,并且该项目的类型是 C# 控制台应用程序。解决方案中当前只有一个项目(ConsoleApp1),并且它包含的代码文件和资源,显示的是 C# 代码。

ConsoleApp1:这是解决方案中的一个项目,表示一个 C# 控制台应用程序。

C#:这个标签说明该项目是一个 C# 类型的项目,即使用 C# 语言编写的。

PropertiesProperties 是每个 C# 项目中都有的文件夹,包含项目的设置和配置文件,如 AssemblyInfo.cs 文件,它定义了程序集的元数据。

引用 (References)References 文件夹包含了项目依赖的所有外部库(如 DLL 文件)和其他项目。通过引用,项目可以使用外部库或其他项目中的代码。

App.config:这是 C# 项目中的配置文件,用于存储应用程序的配置信息,例如数据库连接字符串、应用程序设置等。

Program.csProgram.cs 是 C# 控制台应用程序的默认入口点。这个文件包含了 Main 方法,这是应用程序执行的起始点。

4、入口文件

这个界面就是我们编程的界面,把程序编入static void Main(string[] args)函数里面即可。

5、加载库

右键方案》添加》引用:浏览库文件添加。

6、运行

三、语法基础

1、数据类型

类型类别 类型示例 描述 范围/默认值 使用方式/示例代码
值类型 (Value Types) bool, byte, char, decimal, double, float, int, long, sbyte, short, uint, ulong, ushort 值类型直接包含数据,存储在栈上。每次赋值都会复制值。 bool: False, int: 0, char: '', float: 0.0F, double: 0.0D 等 int a = 10;float f = 3.14F;char c = 'A';
引用类型 (Reference Types) object, dynamic, string, class, interface, delegate 引用类型存储的是内存地址而非数据本身,指向堆上的对象。多个变量可以引用相同的内存位置。 由具体对象决定,如 string 默认空字符串 ""object 默认 null string str = "Hello";object obj = 100;dynamic d = 10.5;
指针类型 (Pointer Types) char*, int*, float*, 等 指针类型存储内存地址,允许直接操作内存。只能在 unsafe 代码块中使用。 在 C# 中需在 unsafe 环境下使用,无默认值 unsafe { int* ptr = &a; }

(1)指针使用方式示例

// 指针使用示例,还需要设置Visual Code,让其运行不安全代码unsafe{                int a = 10;                int* ptr = &a;  // 获取 a 的内存地址                Console.WriteLine(*ptr);  // 通过指针访问 a 的值                Console.ReadLine();}

2、类型转换

转换类型 描述 示例代码
隐式类型转换 (Implicit Conversion) C# 默认的类型转换方式,编译器会自动进行转换,通常是从较小类型到较大类型,或从派生类到基类。 int i = 10;long l = i; // int 转换为 long
显式类型转换 (Explicit Conversion) 用户通过强制转换运算符显式地进行转换,通常用于不同类型之间的转换,需要注意类型兼容性。 double d = 5673.74;int i = (int)d; // 强制转换为 int
ToBoolean 将类型转换为布尔型。 bool b = Convert.ToBoolean("true");
ToByte 将类型转换为字节类型。 byte b = Convert.ToByte(123.45);
ToChar 将类型转换为单个 Unicode 字符类型。 char c = Convert.ToChar(65); // ASCII 'A'
ToDateTime 将类型(整数或字符串类型)转换为日期时间类型。 DateTime dt = Convert.ToDateTime("2021-12-31");
ToDecimal 将浮点型或整数类型转换为十进制类型。 decimal dec = Convert.ToDecimal(123.45);
ToDouble 将类型转换为双精度浮点型。 double d = Convert.ToDouble(123);
ToInt16 将类型转换为 16 位整数类型。 short s = Convert.ToInt16(123);
ToInt32 将类型转换为 32 位整数类型。 int i = Convert.ToInt32(123.45);
ToInt64 将类型转换为 64 位整数类型。 long l = Convert.ToInt64(123.45);
ToSByte 将类型转换为有符号字节类型。 sbyte sb = Convert.ToSByte(127);
ToSingle 将类型转换为单精度浮点数类型。 float f = Convert.ToSingle(123.45);
ToString 将类型转换为字符串类型。 string str = Convert.ToString(123);
ToType 将类型转换为指定的目标类型。 object obj = 123;int i = (int)obj;
ToUInt16 将类型转换为 16 位无符号整数类型。 ushort us = Convert.ToUInt16(123);
ToUInt32 将类型转换为 32 位无符号整数类型。 uint ui = Convert.ToUInt32(123.45);
ToUInt64 将类型转换为 64 位无符号整数类型。 ulong ul = Convert.ToUInt64(123.45);

3、变量

概念/类型 描述 举例
变量定义 在 C# 中,变量是供程序操作的存储区,必须声明其数据类型。 int i, j, k;
数据类型 每个变量都有一个特定的类型,决定了内存大小和布局。 int, double, char, float
变量初始化 变量可以在声明时进行初始化,通过赋值给变量。 int i = 100;, double pi = 3.14;
整数类型 用于存储整数值。包括有符号和无符号整数类型。 int, long, byte, sbyte, char
浮点类型 用于存储小数(浮点数)值。 float, double
十进制类型 用于高精度存储十进制数,通常用于财务和货币计算。 decimal
布尔类型 用于存储逻辑值,通常表示真(true)或假(false)。 bool
空类型 用于表示可能为空的值。 Nullable<int>, int?
Lvalue 左值(Lvalue):可以出现在赋值语句的左边,表示可修改的内存位置。 int g = 20; (g 是 Lvalue)
Rvalue 右值(Rvalue):可以出现在赋值语句的右边,表示常量或表达式的结果。 20 = 20;(无效,因为 20 是 Rvalue)

4、常量

概念 描述 示例
常量 常量是固定值,在程序执行期间不能改变。常量可以是任何基本数据类型,如整数、浮点数、字符、字符串等。 const int x = 10;
整数常量 整数常量可以是十进制、八进制、十六进制,带有后缀的无符号和长整型常量。 85, 0213, 0x4b, 30u, 30l
浮点常量 浮点常量由整数部分、小数部分和指数部分组成,支持小数形式和指数形式。 3.14159, 314159E-5L, 510E, 210f
字符常量 字符常量用单引号括起来,可以是普通字符、转义序列或通用字符。 'x', 't', 'n', 'u02C0'
字符串常量 字符串常量用双引号 "" 括起来,也可以使用 @"" 形式表示原始字符串。 "hello, dear", "hello, ndear", @"hello dear"
转义序列 用于表示特殊字符,如换行符、制表符等。常用于字符常量和字符串常量中。 n, t, , u02C0
常量声明 常量通过 const 关键字声明,声明后其值不可更改。 const double pi = 3.14159;
常量实例 示例代码演示常量的定义和使用。通过常量计算圆的面积。 const double pi = 3.14159; double area = pi * r * r;

5、运算符

这里是对 C# 中运算符的总结,包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符以及杂项运算符:

(1)算术运算符

运算符 描述 实例
+ 把两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 把两个操作数相乘 A * B 将得到 200
/ 分子除以分母 B / A 将得到 2
% 取模运算符,整除后的余数 B % A 将得到 0
++ 自增运算符,整数值增加 1 A++ 将得到 11
-- 自减运算符,整数值减少 1 A-- 将得到 9

(2)关系运算符

运算符 描述 实例
== 检查两个操作数的值是否相等 (A == B) 不为真
!= 检查两个操作数的值是否不相等 (A != B) 为真
> 检查左操作数的值是否大于右操作数的值 (A > B) 不为真
< 检查左操作数的值是否小于右操作数的值 (A < B) 为真
>= 检查左操作数的值是否大于或等于右操作数 (A >= B) 不为真
<= 检查左操作数的值是否小于或等于右操作数 (A <= B) 为真

(3)逻辑运算符

运算符 描述 实例
&& 逻辑与运算符,如果两个操作数都为真则结果为真 (A && B) 为假
` `
! 逻辑非运算符,用来逆转操作数的逻辑状态 !(A && B) 为真

(4)位运算符

运算符 描述 实例
& 位与运算符,如果同时存在于两个操作数中 (A & B) 将得到 12
` ` 位或运算符,如果存在于任一操作数中
^ 位异或运算符,如果存在于其中一个操作数中但不同时存在 (A ^ B) 将得到 49
~ 补码运算符,翻转位效果 (~A) 将得到 -61
<< 位左移运算符,将左操作数的值左移指定的位数 (A << 2) 将得到 240
>> 位右移运算符,将左操作数的值右移指定的位数 (A >> 2) 将得到 15

(5)赋值运算符

运算符 描述 实例
= 简单的赋值运算符,把右边操作数的值赋给左边操作数 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
` =` 按位或且赋值运算符

(6)杂项运算符

运算符 描述 实例
sizeof() 返回数据类型的大小 sizeof(int) 将返回 4
typeof() 返回类的类型 typeof(StreamReader)
& 返回变量的地址 &a 将得到变量的实际地址
* 变量的指针 *a 将指向一个变量
? : 条件表达式 (a == 1) ? 20 : 30
is 判断对象是否为某一类型 Ford is Car 检查 Ford 是否是 Car 类型的对象
as 强制转换,即使转换失败也不会抛出异常 obj as StringReader

(7)运算符优先级

C# 运算符的优先级决定了在一个表达式中运算符的执行顺序。高优先级的运算符会先执行。以下是按优先级从高到低排列的运算符:

优先级 运算符 结合性
1 () [] -> . ++ -- 从左到右
2 + - ! ~ ++ -- (type) * & sizeof 从右到左
3 * / % 从左到右
4 + - 从左到右
... ... ...

此优先级列表会影响运算顺序,在没有括号的情况下,具有高优先级的运算符会首先被计算。

6、 判断

语句类型 描述 示例
if 语句 由一个布尔表达式后跟一个或多个语句组成,执行条件为真时的语句。 if (a >= 60) { Console.WriteLine("及格"); }
if...else 语句 一个 if 语句后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。 if (a >= 60) { Console.WriteLine("及格"); } else { Console.WriteLine("不及格"); }
嵌套 if 语句 在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 if (a > 90) { Console.WriteLine("优秀"); } else if (a >= 60) { Console.WriteLine("及格"); }
switch 语句 允许测试一个变量等于多个值时的情况,通常用于多重条件判断。 switch (a) { case 1: Console.WriteLine("一"); break; case 2: Console.WriteLine("二"); break; }
嵌套 switch 语句 在一个 switch 语句内使用另一个 switch 语句,处理复杂的多重条件。 switch (a) { case 1: switch (b) { case 1: Console.WriteLine("一"); break; } break; }
三目运算符 (? :) 通过 Exp1 ? Exp2 : Exp3 表达式替代 if...else 语句,Exp1 为真时返回 Exp2,否则返回 Exp3。 result = (a >= 60) ? "及格" : "不及格";
条件运算符替代 if ? : 运算符只能用于表达式,不能执行函数操作,但适用于简单的判断逻辑,常用于返回值的判断。 a >= 60 ? true : false;

7、循环

(1)循环类型

循环类型 描述 示例
while 循环 当给定条件为真时,重复执行语句或语句组。在执行循环主体之前会测试条件。 while (a < 10) { Console.WriteLine(a); a++; }
for 循环 多次执行一个语句序列,简化循环变量的管理。通常用于已知循环次数的情况。 for (int i = 0; i < 10; i++) { Console.WriteLine(i); }
foreach 循环 用于遍历集合类型(如数组、列表等),简化了循环访问集合元素的方式。 foreach (var item in array) { Console.WriteLine(item); }
do...while 循环 在循环体结束后测试条件,保证循环体至少执行一次。 do { Console.WriteLine(a); a++; } while (a < 10);
嵌套循环 在一个循环内部再嵌套另一个或多个循环,常用于处理多维数据。 for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { Console.WriteLine(i + " " + j); } }

(2)循环控制

控制语句 描述 示例
break 终止当前循环或 switch 语句,程序流会继续执行 loopswitch 后的下一条语句。 while (true) { if (a == 10) break; Console.WriteLine(a); a++; }
continue 跳过循环主体的剩余部分,立即回到循环条件的判断阶段,继续执行下一个循环。 for (int i = 0; i < 10; i++) { if (i % 2 == 0) continue; Console.WriteLine(i); }
无限循环 如果条件永远为真,循环将持续无限执行。通常可以使用 for(;;) 结构实现。 for (; ;) { Console.WriteLine("Hey! I am Trapped"); }

8、封装

修饰符 描述 访问范围 示例
Public 公开访问修饰符,允许类的成员被所有类和对象访问。 任何类或对象都可以访问。 public int x;
Private 私有访问修饰符,仅允许类内部的成员函数访问该成员。其他类或对象无法访问。 只能在定义该成员的类内部访问。 private int x;
Protected 保护访问修饰符,允许子类访问基类的成员,但类外的其他对象无法访问。 当前类及其派生类可以访问。 protected int x;
Internal 内部访问修饰符,允许同一个程序集内的其他类或对象访问。 同一程序集内的类和对象可以访问。 internal int x;
Protected internal 受保护内部访问修饰符,允许同一程序集内的类、派生类及其成员访问。 同一程序集内的类、派生类以及当前类可以访问。 protected internal int x;

(1)Public示例

using System;namespace RectangleApplication{    class Rectangle    {        public double length;   // 公共成员变量        public double width;        public double GetArea()  // 公共方法        {            return length * width;        }        public void Display()        {            Console.WriteLine("Length: " + length);            Console.WriteLine("Width: " + width);            Console.WriteLine("Area: " + GetArea());        }    }    class ExecuteRectangle    {        static void Main(string[] args)        {            Rectangle r = new Rectangle();            r.length = 5;   // 直接访问公有成员            r.width = 10;            r.Display();        }    }}

9、方法

概念 描述 示例代码
方法定义 定义方法的语法:<Access Specifier> <Return Type> <Method Name>(Parameter List) { Method Body } public int FindMax(int num1, int num2) { return (num1 > num2) ? num1 : num2; }
方法调用 使用方法名调用方法,方法可以带参数。 ret = n.FindMax(a, b); Console.WriteLine("最大值是: {0}", ret);
递归方法调用 方法可以自我调用,这被称为递归。 public int factorial(int num) { if (num == 1) return 1; else return factorial(num - 1) * num; }
值参数传递 传递的是值的副本。方法修改参数时,不会影响实际参数的值。 public void swap(int x, int y) { int temp = x; x = y; y = temp; }n.swap(a, b); // a 和 b 的值不变
引用参数传递 (ref) 传递的是参数的引用,方法可以修改参数的值,影响实际参数。 public void swap(ref int x, ref int y) { int temp = x; x = y; y = temp; }n.swap(ref a, ref b); // a 和 b 的值会交换
输出参数传递 (out) 用于从方法中返回多个值,参数无需初始化。在方法内赋值后,调用者获取新的值。 public void getValue(out int x) { x = 5; }n.getValue(out a); // a 被赋值为 5
方法返回值 方法通过 return 语句返回一个值。 public int Add(int x, int y) { return x + y; }
实例:交换值(值传递) 演示了值传递时,交换操作对原始值没有影响。 Console.WriteLine("交换前: a={0}, b={1}", a, b);n.swap(a, b);Console.WriteLine("交换后: a={0}, b={1}", a, b); // 结果显示 a 和 b 值没有变化
实例:交换值(引用传递) 演示了引用传递时,交换操作会影响原始值。 Console.WriteLine("交换前: a={0}, b={1}", a, b);n.swap(ref a, ref b);Console.WriteLine("交换后: a={0}, b={1}", a, b); // 结果显示 a 和 b 值交换
实例:获取多个值 演示了输出参数可以从方法中返回多个值。 public void getValues(out int x, out int y) { x = 7; y = 8; }n.getValues(out a, out b);Console.WriteLine("a={0}, b={1}", a, b);

10、可空类型

概念 描述 语法与示例 结果示例
可空类型(Nullable Types) 可空类型允许值类型(如 intbooldouble)可以接受一个 null 值,表示该值未定义。 <data_type>? <variable_name> = null; int? num1 = null;int? num2 = 45;Console.WriteLine(num1, num2);输出null, 45
可空类型声明 ? 声明可空类型,允许为该类型赋值 null 或者其正常值范围内的数值。 int? num1 = null;double? num3 = new double?(); num1 = nullnum3 = null
空值输出 可空类型变量若未赋值,输出为空值 null bool? boolval = new bool?();Console.WriteLine(boolval); 输出null
空值合并运算符(??) ?? 运算符用于提供一个默认值。如果可空类型的值为 null,则返回指定的默认值,否则返回可空类型的值。 num3 = num1 ?? 5.34;num3 = num2 ?? 5.34; 输出num3 = 5.34(当 num1null 时使用默认值 5.34)num3 = 3.14157(当 num2 不为 null 时使用 num2 值)

11、数组

概念 描述 语法与示例 结果示例
数组声明 数组是一个固定大小的顺序集合,用来存储相同类型的元素。数组变量可以通过索引访问。 datatype[] arrayName;double[] balance; 声明一个 double[] 类型的数组变量 balance
数组初始化 声明数组后需要使用 new 关键字初始化数组,并指定数组的大小或赋初值。 double[] balance = new double[10];int[] marks = { 99, 98, 92, 97, 95 }; 初始化数组 balance,大小为 10。 初始化数组 marks,并赋值 { 99, 98, 92, 97, 95 }
赋值给数组 使用索引为数组中的特定元素赋值。可以在声明数组时同时初始化数组元素。 balance[0] = 4500.0;int[] marks = new int[5] { 99, 98, 92, 97, 95 }; 赋值 balance[0] = 4500.0; 和初始化 marks 数组。
访问数组元素 数组元素通过索引来访问。索引从 0 开始。数组元素通过 array[index] 的方式访问。 double salary = balance[9]; 访问 balance[9] 元素的值并赋值给 salary
使用 foreach 循环 使用 foreach 循环遍历数组元素。比 for 循环更简洁。 foreach (int j in n) { Console.WriteLine("Element[{0}] = {1}", i, j); } 输出数组 n 中的每个元素值。
多维数组 C# 支持多维数组,最简单的是二维数组。多维数组中的元素通过多个索引访问。 int[,] matrix = new int[3, 4]; 声明并初始化一个 3 行 4 列的二维数组 matrix
交错数组 C# 支持交错数组,即数组的数组,类似于二维数组但每行的长度不固定。 int[][] jaggedArray = new int[3][];jaggedArray[0] = new int[2]; 声明一个交错数组 jaggedArray,其中每个子数组的长度可以不同。
传递数组给函数 数组可以作为参数传递给函数,传递的是数组的引用。 void DisplayArray(int[] arr) { }DisplayArray(marks); marks 数组传递给 DisplayArray 函数。
参数数组 通过 params 关键字传递未知数量的参数,允许传递不同长度的参数数组。 void DisplayNumbers(params int[] numbers) { } 使用 params 传递多个数值给函数 DisplayNumbers
Array 类 Array 类是所有数组的基类,提供了数组的各种方法和属性。 Array.Sort(array);Array.Length 使用 Array.Sort() 对数组进行排序,使用 array.Length 获取数组的长度。

12、 字符串

概念/方法 描述 示例代码
创建 String 对象 创建 String 对象的几种方式:
1. 通过给 String 变量指定一个字符串 直接给变量赋值字符串。 string fname = "Rowan";
2. 通过使用 String 类构造函数 使用字符数组构造一个新的 String 对象。 char[] letters = { 'H', 'e', 'l', 'l', 'o' }; string greetings = new string(letters);
3. 通过字符串串联运算符(+) 使用 + 运算符将字符串连接成一个新的 String 对象。 string fullname = fname + lname;
4. 通过方法返回字符串 调用方法并返回字符串。 string message = String.Join(" ", sarray);
5. 通过格式化方法 使用 String.Format 方法将值转换为字符串表示形式。 string chat = String.Format("Message sent at {0:t} on {0:D}", waiting);
String 类的属性 String 类的常用属性。
1. Chars 获取当前 String 对象中指定位置的字符。 char ch = myString[0];
2. Length 获取 String 对象中的字符数。 int length = myString.Length;
String 类的常用方法 String 类的常用方法。
1. Compare 比较两个字符串并返回它们在排序顺序中的位置。 int result = String.Compare(str1, str2);
2. Concat 连接两个或多个字符串。 string fullName = String.Concat(firstName, lastName);
3. Contains 判断当前字符串是否包含指定的子字符串。 bool contains = str.Contains("test");
4. Copy 创建一个新的 String 对象,值与指定字符串相同。 string newStr = String.Copy(str);
5. CopyTo 从指定位置开始将当前字符串的字符复制到字符数组。 myString.CopyTo(0, destArray, 0, 5);
6. EndsWith 判断当前字符串是否以指定的子字符串结尾。 bool endsWith = str.EndsWith("test");
7. Equals 判断两个字符串是否相等。 bool equals = str1.Equals(str2);
8. Format 格式化字符串,将指定的格式项替换为对象的字符串表示。 string formattedStr = String.Format("Price: {0:C}", price);
9. IndexOf 返回指定字符或子字符串第一次出现的索引。 int index = str.IndexOf("test");
10. Insert 在指定索引位置插入字符串。 string result = str.Insert(5, "ABC");
11. IsNullOrEmpty 判断字符串是否为 null 或空字符串。 bool isEmpty = String.IsNullOrEmpty(str);
12. Join 将字符串数组中的所有元素连接成一个单一字符串,并用指定分隔符分隔。 string result = String.Join(", ", array);
13. LastIndexOf 返回指定字符或子字符串最后一次出现的索引。 int lastIndex = str.LastIndexOf("test");
14. Remove 从指定位置开始移除字符。 string result = str.Remove(5);
15. Replace 替换指定字符或子字符串。 string result = str.Replace("old", "new");
16. Split 按指定字符分割字符串并返回子字符串数组。 string[] substrings = str.Split(',');
17. StartsWith 判断字符串是否以指定的子字符串开始。 bool startsWith = str.StartsWith("test");
18. ToCharArray 将字符串转换为字符数组。 char[] charArray = str.ToCharArray();
19. ToLower 将字符串转换为小写。 string lowerStr = str.ToLower();
20. ToUpper 将字符串转换为大写。 string upperStr = str.ToUpper();
21. Trim 移除字符串的前导和尾随空白字符。 string trimmedStr = str.Trim();

13、 结构

序号 主题 描述
1 定义结构 使用 struct 关键字定义结构,例如 struct Books
2 结构成员 结构可以包含字段、方法、属性、索引器、事件等。
3 构造函数 结构可以定义构造函数,但不能定义析构函数。结构没有默认构造函数。
4 值类型与引用类型 结构是值类型,赋值给另一个结构时会复制其数据,而类是引用类型。
5 结构的继承 结构不能继承类或其他结构,也不能作为其他结构或类的基类。
6 接口实现 结构可以实现一个或多个接口。
7 成员权限 结构成员不能指定为 abstractvirtualprotected
8 默认实例化 结构可以通过 new 运算符创建实例,也可以在未使用 new 的情况下实例化。
9 字段初始化 当不使用 new 时,结构的字段只有在初始化后才能使用。
10 结构与类的对比 结构和类的主要不同点包括:结构是值类型,结构不支持继承,结构不能声明默认构造函数等。

(1)对比类与结构

特性 结构(Struct) 类(Class)
类型 值类型 引用类型
继承 不支持继承 支持继承
默认构造函数 自动提供但不可更改 可以定义自定义构造函数
字段初始化 可以不使用 new 关键字实例化 必须使用 new 关键字实例化
大小 存储在栈上(一般较小) 存储在堆上(一般较大)
垃圾回收 不受垃圾回收控制 由垃圾回收器管理

14、枚举

序号 主题 描述
1 定义枚举 使用 enum 关键字声明枚举类型,例如:enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
2 枚举的值 枚举成员的默认值从 0 开始,每个后续成员的值比前一个大 1。可以通过显式指定值来改变默认值。
3 枚举成员的值 每个枚举成员实际上是一个整数值,默认从 0 开始。如果需要,可以手动赋予枚举成员不同的整数值。
4 枚举类型 枚举是值类型,它具有自己的数据值,并且不能被继承或传递继承。
5 枚举与整数转换 枚举成员可以通过显式转换为整数值,整数也可以被转换回对应的枚举类型。

15、 类

概念 描述 示例代码
类的定义 使用 class 关键字定义类,类的成员包括变量和方法。 class Box { public double length; public double breadth; public double height; }
实例化类 使用 new 关键字创建类的实例(对象)。 Box Box1 = new Box(); Box Box2 = new Box();
成员变量 类的属性,定义类的状态。 public double length;
成员方法 类的行为或操作,用于定义类的功能。 public double getVolume() { return length * breadth * height; }
封装 通过访问控制符将类的成员限制访问,常将变量设为 private,并通过 public 方法访问。 private double length;public void setLength(double len) { length = len; }
构造函数 特殊方法,用于在创建对象时初始化对象的成员。构造函数名称与类名相同。可以是默认构造函数或参数化构造函数。 public Line() { Console.WriteLine("对象已创建"); }public Line(double len) { length = len; }
析构函数 特殊方法,用于对象超出作用域时释放资源。析构函数的名称是类名加波浪符(~)。 ~Line() { Console.WriteLine("对象已删除"); }
静态成员 属于类而非实例的成员,所有实例共享一个副本。静态成员使用 static 关键字。 public static int num;public static int getNum() { return num; }
静态方法 静态方法只能访问静态变量,不能访问实例成员。 public static int getNum() { return num; }
访问控制符 控制类成员的访问权限,常见的有 publicprivateprotectedinternal public double length;private double length;
成员的访问 使用点运算符 (.) 来访问对象的成员。 Box1.length = 5.0;Console.WriteLine(Box1.getVolume());
类与对象的关系 类是一个模板,定义了对象的结构和行为,对象是类的实例。 Box Box1 = new Box();

16、继承

概念 描述 示例代码
继承概述 继承是面向对象程序设计中的重要概念,允许一个类继承另一个类的成员,使得代码复用和维护变得更容易。 派生类继承了基类的成员,可以重用基类的代码。
基类与派生类 基类是被继承的类,派生类是继承基类的类。派生类可访问基类的公有和保护成员,不能访问基类的私有成员。 基类 Shape 和派生类 Rectangle
属于(IS-A)关系 继承实现了“属于”关系。例如,狗属于哺乳动物哺乳动物属于动物,所以狗属于动物 Shape 是一个基类,Rectangle 是派生类。
基类的成员 基类的成员(变量和方法)会被派生类继承,派生类可以重用基类的代码并扩展功能。 基类成员:public void setWidth(int w)public void setHeight(int h)
构造函数与初始化 派生类无法继承基类的构造函数,但可以通过构造函数初始化基类成员。派生类对象创建前,基类对象先被创建。 public Rectangle(double l, double w) : base(l, w)
代码示例:继承实现 创建基类和派生类,派生类扩展了基类的功能。 class Shape { ... }class Rectangle : Shape { ... }
多重继承 C# 不支持多重继承,但可以通过接口来实现类似的功能。 使用接口 PaintCost 实现多重继承:class Rectangle : Shape, PaintCost { ... }
多重继承示例 通过接口模拟多重继承,实现多个类的功能组合。 public interface PaintCost { int getCost(int area); }class Rectangle : Shape, PaintCost { ... }
派生类扩展功能 派生类不仅继承基类的功能,还可以添加更多成员或覆盖基类的方法来扩展功能。 Rectangle 中添加方法 getArea()getCost(),并通过 Display() 输出数据。
基类初始化与成员 基类在派生类的构造函数中进行初始化,派生类在初始化时获得基类的成员。 public Tabletop(double l, double w) : base(l, w)base.Display()

17、多态性

概念 描述 示例代码
多态性概述 多态性意味着有多重形式,它允许通过同一接口调用不同的功能。在面向对象编程中,多态性通常表现为"一个接口,多个功能"。 - 静态多态性:函数重载和运算符重载。 - 动态多态性:通过抽象类和虚方法来实现运行时的多态。
静态多态性 静态多态性(也称为早期绑定)是通过编译时确定函数的响应。静态多态性通过函数重载和运算符重载实现。 - 函数重载示例:同一个函数名根据参数类型或数量的不同执行不同的功能。
函数重载 函数重载是指在同一个类中,允许有多个相同函数名,但它们的参数列表不同(如类型不同或个数不同)。 void print(int i) { ... }

void print(double f) { ... }

void print(string s) { ... }

动态多态性 动态多态性(也称为晚期绑定)是指在运行时根据对象的实际类型来决定调用哪个方法。通过抽象类和虚方法来实现。 - 使用抽象类和虚方法实现不同派生类的不同实现。例如,基类定义一个虚方法,派生类覆盖它。
抽象类与抽象方法 抽象类不能被实例化,它包含抽象方法,派生类必须重写这些方法。抽象方法在基类中没有实现,而是由派生类来实现。 abstract class Shape { public abstract int area(); }

class Rectangle : Shape { public override int area() { ... } }

虚方法 虚方法有默认实现,派生类可以选择是否重写它。虚方法支持运行时多态,允许基类与派生类的行为不同。 class Shape { public virtual int area() { ... } }

class Rectangle : Shape { public override int area() { ... } }

抽象方法与虚方法区别 - 抽象方法没有实现,必须在派生类中实现,派生类不能实例化。 - 虚方法有默认实现,可以在派生类中覆盖,也可以不覆盖。 - 抽象方法:必须在派生类中重写,且派生类必须是抽象类。 - 虚方法:可以选择覆盖或不覆盖。

18、运算符重载

运算符 描述 是否可以重载
+ 二元运算符,用于执行加法操作。 可重载
- 二元运算符,用于执行减法操作。 可重载
* 二元运算符,用于执行乘法操作。 可重载
/ 二元运算符,用于执行除法操作。 可重载
% 二元运算符,用于执行求余操作。 可重载
! 一元运算符,用于取反操作。 可重载
~ 一元运算符,用于按位取反操作。 可重载
++ 一元运算符,用于执行自增操作。 可重载
-- 一元运算符,用于执行自减操作。 可重载
== 比较运算符,用于检查两个对象是否相等。 可重载
!= 比较运算符,用于检查两个对象是否不相等。 可重载
< 比较运算符,用于检查左边的对象是否小于右边的对象。 可重载
> 比较运算符,用于检查左边的对象是否大于右边的对象。 可重载
<= 比较运算符,用于检查左边的对象是否小于或等于右边的对象。 可重载
>= 比较运算符,用于检查左边的对象是否大于或等于右边的对象。 可重载
&& 逻辑运算符,用于执行逻辑与操作。 不可重载
+= 赋值运算符,用于执行加法赋值操作。 不可重载
-= 赋值运算符,用于执行减法赋值操作。 不可重载
*= 赋值运算符,用于执行乘法赋值操作。 不可重载
/= 赋值运算符,用于执行除法赋值操作。 不可重载
= 赋值运算符,用于执行简单赋值操作。 不可重载
. 用于访问对象的成员(字段、方法等)。 不可重载
?: 三元运算符,用于根据条件表达式返回不同的值。 不可重载
-> 用于访问结构体的成员。 不可重载
new 用于创建对象实例。 不可重载
is 用于检查对象是否为指定类型。 不可重载
sizeof 用于获取数据类型的大小。 不可重载
typeof 用于获取类型的实例。 不可重载

(1)可重载示例

// 在下面的例子中,我们重载了 + 运算符,使得 Box 类的对象可以通过加法运算符相加。using System;namespace OperatorOverloadExample{    class Box    {        public double Length { get; set; }        public double Width { get; set; }        public double Height { get; set; }        // 重载 + 运算符        public static Box operator +(Box b1, Box b2)        {            Box result = new Box();            result.Length = b1.Length + b2.Length;            result.Width = b1.Width + b2.Width;            result.Height = b1.Height + b2.Height;            return result;        }        public override string ToString()        {            return $"Length: {Length}, Width: {Width}, Height: {Height}";        }    }    class Program    {        static void Main()        {            Box box1 = new Box { Length = 5, Width = 3, Height = 2 };            Box box2 = new Box { Length = 4, Width = 2, Height = 1 };            Box box3 = box1 + box2;  // 使用重载的 + 运算符            Console.WriteLine("Box1: " + box1);            Console.WriteLine("Box2: " + box2);            Console.WriteLine("Box3 (Box1 + Box2): " + box3);        }    }}

(2)不可重载示例

// C# 中的逻辑运算符,如 && 和 || 是不可重载的,不能自定义它们的行为。以下是一个试图重载 && 运算符的例子,它会导致编译错误。using System;namespace OperatorOverloadExample{    class Box    {        public double Length { get; set; }        // 编译时错误:无法重载 && 运算符        public static bool operator &&(Box b1, Box b2)        {            return b1.Length > 5 && b2.Length > 5;        }    }    class Program    {        static void Main()        {            Box box1 = new Box { Length = 6 };            Box box2 = new Box { Length = 7 };            bool result = box1 && box2;  // 编译错误            Console.WriteLine(result);        }    }}

19、接口

特性 接口 抽象类
定义方式 使用 interface 关键字声明 使用 abstract class 关键字声明
成员定义 只包含成员声明,没有实现。成员可以是方法、属性、事件。 包含方法声明和实现。可以包含字段、属性、构造函数等。
构造函数 不能包含构造函数 可以包含构造函数
字段 不能包含字段 可以包含字段
访问修饰符 成员默认是 public,不能使用 privateprotected 等修饰符 可以使用不同的访问修饰符,如 privateprotectedpublic
成员实现要求 实现接口时,必须实现接口中声明的所有方法和属性 只需要实现抽象方法,其他方法可以有实现。
多重继承 支持多重继承,一个类可以实现多个接口 不支持多重继承,只能继承一个抽象类或其他类
继承对象 类继承接口时,必须实现接口中的所有方法 类继承抽象类时,必须实现抽象方法
适用场景 用于定义类的契约或协议,类通过接口规范行为 用于定义类的通用行为并提供部分实现

20、命名空间

特性 描述
定义 使用 namespace 关键字定义,后跟命名空间的名称。例如:namespace namespace_name { }
作用 提供一种机制将一组相关名称(如类、函数)分组,以避免与其他命名空间中的同名元素冲突。
访问命名空间中的成员 通过将命名空间名称放在前面访问成员,例如:namespace_name.item_name
嵌套命名空间 一个命名空间可以包含另一个命名空间,使用点(.)运算符访问嵌套命名空间中的成员。例如:first_space.second_space.member_name
using 关键字 using 关键字允许在程序中使用命名空间中的成员时,不需要每次都写出完整的命名空间路径。例如:using first_space;
完全限定名称 如果没有使用 using 关键字,可以使用完全限定名称访问命名空间中的成员,例如:System.Console.WriteLine("Hello");
访问多个命名空间 可以使用多个 using 语句引入不同的命名空间。例如:using first_space; using second_space;
成员访问 直接访问成员时,使用命名空间作为前缀,如:first_space.namespace_cl。通过实例化类后,调用其方法:fc.func()
命名空间成员的限制 命名空间不包含实现,只有类、结构、接口和枚举等成员的声明。实现由类或其他类型负责。

21、预处理指令

处理器指令 描述
#define 定义一个符号常量,后续可在条件编译中使用。例如,#define PI 定义一个符号 PI
#undef 取消已定义的符号。例如,#undef PI 取消对符号 PI 的定义。
#if 用于测试一个符号是否为真,条件成立时编译器编译 #if 和下一个指令之间的代码。例如,#if DEBUG 测试 DEBUG 是否已定义。
#else #if 配合使用,在条件不成立时执行的代码部分。例如,#else 语句会在 #if 条件未满足时执行。
#elif #if 配合使用,表示如果 #if 条件不满足时,使用新的条件进行判断。例如,#elif DEBUG
#endif 结束一个条件编译块。必须在 #if#elif 语句之后使用。
#line 修改编译器的行号,或指定输出错误和警告时的文件名。例如,#line 100 可以修改当前行号为 100。
#error 生成一个编译时错误,允许开发者在代码中指定错误。
#warning 生成一个编译时警告,允许开发者在代码中指定警告。
#region 在 Visual Studio 中指定一个可折叠的代码区域,使代码更易于管理和查看。
#endregion 结束一个 #region 块,标识该代码区域的结束。

22、正则表达式

类别 描述 示例模式 匹配
字符转义 在正则表达式中使用反斜杠来转义字符。 a, b, t 匹配警告符、退格符、制表符等特定字符。
字符类 用于匹配字符集中的任意一个字符。 [a-z] 匹配任何小写字母。
定位点 零宽度断言,指示匹配应位于特定位置。 ^, $ 匹配字符串的开头或结尾。
分组构造 用来创建子表达式,通常用于捕获子字符串。 (abc) 匹配 "abc" 并捕获该部分。
限定符 用于指定一个元素的出现次数。 *, +, ? 匹配字符零次或多次,至少一次,或零次一次。
反向引用构造 用于引用之前捕获的子表达式。 1, k<name> 匹配捕获组的内容。
备用构造 用于启用“或”条件匹配。 `a b`
替换 替换匹配到的字符串。 $1, $& 替换捕获组内容或整个匹配内容。
杂项构造 其他类型的构造,包括注释、模式选项等。 (?x) 在模式中启用或禁用选项。
Regex 类方法 用于在代码中实现正则表达式匹配、替换、分割等操作。 IsMatch(), Replace() 检查字符串匹配、替换字符串、分割字符串等操作。

23、异常处理

(1)处理机制

关键词 描述
try 标识一段可能抛出异常的代码。后跟一个或多个 catch 块。
catch 捕获异常并处理,通常会使用异常类型作为条件。
finally 无论是否发生异常,都会执行的代码块,通常用于清理资源。
throw 用于显式抛出异常。可以在 catch 块内重新抛出当前异常。

(2)异常类

异常类 描述
System.IO.IOException 处理 I/O 错误。
System.IndexOutOfRangeException 处理数组索引越界错误。
System.ArrayTypeMismatchException 处理数组类型不匹配错误。
System.NullReferenceException 处理空对象引用错误。
System.DivideByZeroException 处理除以零错误。
System.InvalidCastException 处理类型转换错误。
System.OutOfMemoryException 处理内存不足错误。
System.StackOverflowException 处理栈溢出错误。

24、文件的输入输出

(1)文件相关类

I/O 类 描述
BinaryReader 从二进制流读取原始数据。
BinaryWriter 以二进制格式写入原始数据。
BufferedStream 提供字节流的临时存储。
Directory 用于操作目录结构。
DirectoryInfo 用于对目录执行操作。
DriveInfo 提供驱动器的信息。
File 用于处理文件(例如:创建、删除、打开文件)。
FileInfo 用于对文件执行操作(例如:获取文件信息、修改文件)。
FileStream 用于文件中任何位置的读写。
MemoryStream 用于随机访问存储在内存中的数据流。
Path 对路径信息执行操作。
StreamReader 用于从字节流中读取字符。
StreamWriter 用于向一个流中写入字符。
StringReader 用于读取字符串缓冲区。
StringWriter 用于写入字符串缓冲区。

(2)FileStream类

参数 描述
FileMode 定义了打开文件的方式(例如:CreateOpen 等)。
FileAccess 定义对文件的访问类型(例如:ReadWriteReadWrite)。
FileShare 定义文件访问的共享模式(例如:NoneReadReadWrite)。

(3)FileMode类

枚举值 描述
Append 打开文件并将光标放在文件末尾,若文件不存在则创建。
Create 创建一个新文件,若文件已存在,则覆盖文件。
CreateNew 创建新文件,若文件已存在则抛出异常。
Open 打开现有文件,若文件不存在则抛出异常。
OpenOrCreate 打开现有文件,若文件不存在则创建新文件。
Truncate 打开现有文件并截断为零字节大小。若文件不存在则抛出异常。

(4)FileAccess类

枚举值 描述
Read 仅允许读取文件。
Write 仅允许写入文件。
ReadWrite 允许同时读取和写入文件。

(5)FileShare类

枚举值 描述
None 不允许任何进程共享文件。
Read 允许其他进程以只读方式访问文件。
Write 允许其他进程以写方式访问文件。
ReadWrite 允许其他进程以读写方式访问文件。
Delete 允许其他进程删除文件。

25、特性

特性名称 描述 语法示例
规定特性 用于为程序元素(如类、方法等)提供元数据。包括位置参数和命名参数。 [attribute(positional_parameters, name_parameter = value, ...)]
AttributeUsage 描述如何使用自定义特性,指定特性可以应用的目标。 `[AttributeUsage(AttributeTargets.Class
Conditional 用于标记条件方法,只有在指定的条件下才会执行。 [Conditional("DEBUG")]
Obsolete 标记已过时的方法或类,编译器会生成警告或错误提示。 [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
自定义特性声明 自定义特性类必须派生自System.Attribute `[AttributeUsage(AttributeTargets.Class
自定义特性构建 通过构造函数传递必需的参数,同时可以设置可选参数来扩展特性的信息。 public class DeBugInfo : System.Attribute { public DeBugInfo(int bugNo, string developer, string lastReview) { ... } }
自定义特性应用 使用自定义特性时,必须将其放置在目标元素的前面。 [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")] class Rectangle { ... }

26、反射

内容 描述
定义 反射是程序访问、检测和修改自身状态或行为的能力。
组成 程序集包含模块,模块包含类型,类型包含成员。反射提供了封装程序集、模块和类型的对象。
作用 - 动态创建类型实例 - 绑定类型到现有对象 - 获取类型 - 调用类型方法,访问字段和属性
命名空间 System.Reflection
优点 - 提高程序灵活性和扩展性 - 降低耦合性,提高自适应能力 - 允许程序创建和控制任何类的对象,无需硬编码目标类
缺点 - 性能问题:反射比直接代码慢 - 反射模糊程序内部逻辑,增加维护难度 - 反射代码较复杂,比直接代码难以理解
用途 - 查看属性信息 - 审查集合中的类型,实例化这些类型 - 延迟绑定方法和属性 - 运行时创建新类型并执行任务
查看元数据 使用System.Reflection.MemberInfo对象,初始化并发现类相关属性(attribute)。
示例 自定义属性示例: 通过反射读取类MyClass的自定义属性HelpAttribute。 示例代码展示了如何通过GetCustomAttributes()方法访问和输出附加的属性信息。
实例化对象 通过反射读取和输出类Rectangle及其成员的方法和属性的元数据,输出调试信息。例如,DeBugInfo自定义属性可用于跟踪类成员及方法的调试信息。

27、属性

概念 描述
属性 (Property) 属性是类、结构和接口的命名成员,用来扩展域 (Field),通过访问器 (accessor) 让私有域的值可被读写或操作。
域 (Field) 域是类或结构中的成员变量或方法。
访问器 (Accessors) 访问器包含有助于获取(读取或计算)或设置(写入)属性的可执行语句,通常包括 getset 访问器,或仅一个访问器。
属性实例 属性通过定义 getset 访问器来访问私有域。例如:public string Code { get { return code; } set { code = value; } }
抽象属性 (Abstract Properties) 抽象类可以拥有抽象属性,这些属性需要在派生类中实现。例如,public abstract string Name { get; set; }
简化版抽象属性 (C# 6.0) 使用简化的语法定义属性和自动实现的属性。例如:public string Code { get; set; } = "N.A";
属性用法示例 在实例中,创建一个 Student 对象并通过属性访问和设置值,最终输出学生信息。例如:Student Info: Code = 001, Name = Zara, Age = 9
抽象类示例 抽象类定义抽象属性,派生类实现这些属性,并使用访问器来设置和获取值。
C# 6.0 新特性 使用简化的语法和自动属性初始化器,简化代码并提高可读性。

28、索引器

概念 描述
索引器 (Indexer) 索引器允许对象像数组一样被索引,通过 this 关键字定义,在类中允许使用数组访问运算符 [ ] 来访问对象实例的特定部分。
索引器语法 一维索引器的语法为: element-type this[int index] { get { } set { } }。其中 getset 用于获取或设置指定索引的值。
索引器用途 索引器类似于属性,但不需要名称,使用 this 关键字来访问。它通过 getset 访问器来定义,允许获取或设置对象实例中的特定值。
索引器示例 this[int index] 用于访问 namelist 数组中的元素。例如:names[0] = "Zara" 设置值,names[0] 获取值。
索引器重载 索引器可以被重载,允许使用不同类型的参数。例如,使用 int 类型和 string 类型的索引器,可以分别通过索引访问和按名称查找元素。
重载索引器示例 在同一类中定义两个索引器:一个使用 int 类型来设置和获取 namelist 数组中的元素,另一个使用 string 类型来查找数组中指定名称的元素的索引。例如:public int this[string name] { get { } }
索引器应用 索引器使得对象能够表现得像数组一样,通过数组的方式访问类中的成员数据。通过重载索引器,类可以处理不同类型的访问方式,提供灵活的操作接口。
示例结果 执行代码时,输出索引器访问的内容。例如:Zara, Riz, Nuha, ...2 表示 "Nuha" 的索引位置。

29、委托

概念 描述
委托 (Delegate) 委托是一个引用类型变量,存储对某个方法的引用,允许动态地在运行时改变引用。它类似于 C 或 C++ 中的函数指针,常用于事件和回调方法的实现。
声明委托 委托声明决定了可由该委托引用的方法。语法:delegate <return type> <delegate-name> <parameter list>,例如:public delegate int MyDelegate(string s);
实例化委托 声明委托类型后,使用 new 关键字实例化委托,并将其与特定的方法关联。示例:printString ps1 = new printString(WriteToScreen);
委托实例化示例 示例代码中,委托 NumberChanger 引用了两个方法 AddNumMultNum,并通过委托对象调用这些方法。结果展示了 num 的值发生了变化。
委托的多播 委托对象可以使用 + 运算符合并,调用时会依次执行所有合并的委托。可以使用 - 运算符从合并的委托中移除某个委托。多播委托实现了一个方法调用列表。
多播示例 示例中,使用 nc += nc2 合并两个委托,调用时依次执行 AddNumMultNum,输出 num 的变化。结果显示 num 从 10 变为 75。
委托用途 委托可用于在运行时动态调用方法,常用于事件和回调机制。示例代码展示了如何使用 printString 委托分别将字符串打印到控制台和写入文件。
委托作为参数 委托可以作为参数传递给其他方法,在方法中使用该委托调用指定的目标方法。示例代码中,sendString 方法接受委托 printString,并调用传递给它的具体方法(打印到控制台或写入文件)。
委托示例输出 执行委托时,输出控制台信息:“The String is: Hello World”。

30、事件

概念 描述
事件 (Event) 事件是程序对某些用户操作或系统生成的通知的响应。常见的事件包括按键、鼠标点击或移动。事件用于进程间通信,并要求应用程序在事件发生时做出响应。
事件与委托 事件通过委托来处理。委托用于将事件处理程序与事件关联,通常使用发布-订阅模型(Publisher-Subscriber),其中发布器类发布事件,订阅器类响应事件。
发布器 (Publisher) 发布器类包含事件和委托定义,并负责触发事件,通知订阅器类。
订阅器 (Subscriber) 订阅器类响应事件,提供事件处理程序来处理事件。
声明事件 在类中声明事件之前,首先声明事件的委托类型。事件使用 event 关键字声明,示例:public event BoilerLogHandler BoilerEventLog;
实例 1:事件的声明与使用 示例中,EventTest 类定义了 NumManipulationHandler 委托和 ChangeNum 事件。在 SetValue 方法中触发事件,如果值发生变化则调用 OnNumChanged 方法触发事件。
实例 2:锅炉事件日志 通过事件发布器类 DelegateBoilerEvent 和事件 BoilerEventLog,结合委托 BoilerLogHandler 处理锅炉的状态。事件处理程序会记录温度和压力信息,并将日志保存到文件和输出到控制台。
事件触发 事件通过发布器调用触发方法 OnBoilerEventLog,检查事件是否已被订阅,如果订阅了事件,则调用所有订阅的处理方法(如日志记录器)。
事件订阅 订阅器类通过 += 运算符订阅事件,例如 boilerEvent.BoilerEventLog += new DelegateBoilerEvent.BoilerLogHandler(Logger);,该方法被调用时会执行相应的处理。
事件输出示例 执行事件时,输出以下信息:Logging Info: Temperature 100 Pressure 12 Message: O.K,并且日志写入文件 boiler.txt

31、集合

类名 描述和用法
动态数组 (ArrayList) 代表一个可以单独索引的有序集合,类似于数组,但大小动态调整,支持在任意位置添加、移除项目,提供动态内存分配、搜索和排序功能。
哈希表 (Hashtable) 使用键来访问集合中的元素,键值对存储,每个元素都有一个键用于访问集合中的项目。
排序列表 (SortedList) 结合了数组和哈希表的特性,支持通过键或索引访问项目。列表按键值排序,当使用索引访问时类似动态数组,当使用键访问时类似哈希表。
堆栈 (Stack) 代表一个后进先出的对象集合,支持推入(添加元素)和弹出(移除元素)操作。
队列 (Queue) 代表一个先进先出的对象集合,支持入队(添加元素)和出队(移除元素)操作。
点阵列 (BitArray) 使用 1 和 0 表示的二进制数组,用于存储位数据,适用于位数未知的情况,支持通过整型索引访问每一位,索引从零开始。

32、泛型

概念/特性 描述
泛型(Generic) 允许延迟指定类或方法中的数据类型,直到实际使用时。它使得类或方法可以与任何数据类型一起工作,提高代码重用性和类型安全性。
泛型类实例 通过数据类型的替代参数编写类的规范,在编译时根据类型生成代码。例如,MyGenericArray<T> 类使用泛型 T 来处理不同的数据类型。
泛型的特性 1. 最大化代码重用。 2. 提高类型安全性。 3. 提升性能。 4. 创建泛型集合类。 5. 创建自定义泛型接口、类、方法、事件和委托。 6. 可以对泛型类进行约束以访问特定数据类型的方法。
泛型方法实例 泛型方法允许使用类型参数声明方法,例如 Swap<T> 方法可以交换任意类型的两个变量的值。
泛型委托 泛型委托允许使用类型参数定义委托。例如,NumberChanger<T> 委托可以用来定义处理不同类型数据的操作。实例中展示了使用泛型委托来实现对变量的操作。
示例代码输出 - 泛型数组:输出整型数组 0 5 10 15 20 和字符数组 a b c d e。 - 泛型方法:交换整数和字符的值,输出交换前后的结果。 - 泛型委托:输出 Value of Num: 35Value of Num: 175

33、匿名方法

(1)将委托加入对比

概念/特性 描述
委托(Delegate) 委托用于引用具有相同签名的方法,允许通过委托对象调用被引用的方法。
匿名方法(Anonymous Methods) 匿名方法没有名称,只有方法体。它提供了一种传递代码块作为委托参数的技术,返回类型是通过方法主体的 return 语句推断出来的。
匿名方法语法 通过 delegate 关键字创建委托实例并指定代码块。例如:NumberChanger nc = delegate(int x){ Console.WriteLine("Anonymous Method: {0}", x); };
委托调用方式 委托可以通过匿名方法调用,也可以通过命名方法调用。例如,委托实例化后,可以使用 nc(10) 来调用匿名方法或命名方法。
示例代码 1. 使用匿名方法创建委托实例并调用:nc(10) 输出 Anonymous Method: 10。 2. 使用命名方法实例化委托并调用:nc(5) 输出 Named Method: 15nc(2) 输出 Named Method: 30

34、不安全代码

概念/特性 描述
unsafe修饰符 unsafe 修饰符标记的代码块允许使用指针变量,表示使用了指针的非托管代码。
指针变量 指针是存储另一个变量地址的变量。声明格式:type *var-name;。例如,int* p; 表示指向整数的指针。
指针声明示例 - int* p;:p 是指向整数的指针。 - double* p;:p 是指向双精度数的指针。 - int** p;:p 是指向整数指针的指针。
指针声明规则 多个指针声明时,星号 * 只与基础类型一起声明,不能为每个指针单独加星号,例如:int* p1, p2, p3; 是正确的。
指针使用示例 - int var = 20; - int* p = &var; 示例输出:Data is: 20, Address is: 99215364
使用ToString检索数据值 使用 ToString() 方法可以检索指针引用位置的数据。例如:p->ToString() 会输出指针所指向的数据。示例输出:Data is: 20, Data is: 20, Address is: 77128984
传递指针作为方法参数 可以向方法传递指针作为参数。例如:交换两个整数的指针。示例输出:Before Swap: var1: 10, var2: 20, After Swap: var1: 20, var2: 10
使用指针访问数组元素 使用指针访问数组时,需使用 fixed 关键字固定指针地址。例如:fixed(int *ptr = list),然后通过指针访问数组元素。输出显示数组地址和值。
编译不安全代码 编译包含不安全代码时需使用 /unsafe 标志。例如:csc /unsafe prog1.cs在 Visual Studio 中,需要在项目属性中启用不安全代码。

35、多线程

类别 描述
线程定义 线程是程序的执行路径,具有独特的控制流。线程可以帮助应用程序执行复杂、耗时的操作。线程是轻量级进程,在现代操作系统中用于并行编程,节省 CPU 周期,提高应用程序效率。
线程生命周期 1. 未启动状态:线程实例已创建,但未调用 Start 方法。 2. 就绪状态:线程准备运行,等待 CPU 周期。 3. 不可运行状态:例如调用 Sleep、Wait 或 I/O 操作阻塞。 4. 死亡状态:线程执行完毕或已中止。
主线程 进程中第一个执行的线程,自动创建并执行。通过 Thread.CurrentThread 属性可访问主线程。
常用属性 1. CurrentThread:获取当前线程。 2. IsAlive:指示线程是否处于活动状态。 3. IsBackground:指示线程是否为后台线程。 4. ThreadState:获取线程的状态。
常用方法 1. Abort():终止线程。 2. Join():阻塞调用线程,直到某个线程结束。 3. Sleep():让线程暂停一段时间。 4. Start():启动线程执行。 5. Interrupt():中断线程。
创建线程 通过继承 Thread 类并调用 Start() 方法创建线程。例如:Thread childThread = new Thread(childref);
管理线程 1. Sleep():暂停线程一段时间。 2. Interrupt():中断等待状态的线程。
销毁线程 通过 Abort() 方法中止线程,抛出 ThreadAbortException 异常。此异常无法捕获,控制会转移到 finally 块。

36、字典

功能 描述
定义 Dictionary 是一个从一组键(Key)到一组值(Value)的映射结构,包含在System.Collections.Generic命名空间中。
键的要求 键必须是唯一的,且不能为空引用(null)。如果值为引用类型,则可以为空值。
键和值类型 Key 和 Value 可以是任何类型(如 string, int, 自定义类等)。
常用方法与属性
创建和初始化 Dictionary<int, string> myDictionary = new Dictionary<int, string>();
添加元素 myDictionary.Add(1, "C#");
通过Key查找元素 if(myDictionary.ContainsKey(1)) { Console.WriteLine("Key:{0}, Value:{1}", "1", myDictionary[1]); }
通过KeyValuePair遍历元素 foreach(KeyValuePair<int, string> kvp in myDictionary) { Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value); }
仅遍历键 Dictionary<int, string>.KeyCollection keyCol = myDictionary.Keys; foreach (int key in keyCol) { Console.WriteLine("Key = {0}", key); }
仅遍历值 Dictionary<int, string>.ValueCollection valueCol = myDictionary.Values; foreach (string value in valueCol) { Console.WriteLine("Value = {0}", value); }
移除指定键值 myDictionary.Remove(1); if(!myDictionary.ContainsKey(1)) { Console.WriteLine("不存在 Key : 1"); }
常见属性和方法
Comparer 获取用于确定字典中键是否相等的 IEqualityComparer
Count 获取字典中键/值对的数量。
Item 获取或设置与指定键相关联的值。
Keys 获取字典中所有键的集合。
Values 获取字典中所有值的集合。
Add 将指定的键和值添加到字典中。
Clear 从字典中移除所有键和值。
ContainsKey 检查字典是否包含指定的键。
ContainsValue 检查字典是否包含指定的值。
GetEnumerator 返回用于遍历字典的枚举器。
GetType 获取字典实例的类型。
Remove 从字典中移除指定键的值。
ToString 返回字典的字符串表示。
TryGetValue 获取与指定键关联的值。

四、实操

1、Hello World

/*  * 该文件是一个简单的C#控制台应用程序,主要用于演示如何在控制台输出“Hello World”。 * 程序使用了System命名空间下的基本功能来进行输出操作,并等待用户输入以结束程序。 *  * 文件包含以下部分: * - 引用必要的命名空间 * - 程序入口Main方法 * - 控制台输出功能 */// 导入C#中常用的系统库,用于提供基本的功能using System; // 提供基本的类和基类支持,例如Console类using System.Collections.Generic; // 提供泛型集合的接口和类using System.Linq; // 提供对集合进行查询操作的功能(LINQ)using System.Text; // 提供对文本处理和编码转换的功能using System.Threading.Tasks; // 提供对异步编程和任务并行的支持// 定义命名空间,用于组织代码和避免名称冲突namespace ConsoleApp1 // 定义项目的命名空间,命名为ConsoleApp1{    // 定义一个类,类是C#中代码的基本结构单元    class Program // 定义Program类,通常是程序的入口点    {        // 定义Main方法,是程序执行的入口        static void Main(string[] args) // Main方法,程序从这里开始运行        {            // 使用Console类输出一行文本到控制台            Console.WriteLine("Hello World"); // 输出"Hello World"到控制台            // 等待用户按下回车键,以便查看控制台输出            Console.ReadLine(); // 阻止程序立即关闭,等待用户输入        }    }}

(1)入口函数

Main()是默认的入口函数,可以被修改,但我不告诉你怎么修改。

我想说的是他的特殊性,通常来说,一个静态的函数,都是需要现行调用的,不能直接运行。但入口函数不一样,运行程序会被直接调用,不需要显性调用。

入口函数有且只有一个。

2、模块化

(1)创建文件夹与cs文件

双击 .sln 文件(解决方案文件)来打开解决方案或项目 》 右键解决方案名称添加文件夹 》 右键文件夹添加类

(2)修改cs文件名,类名可自定义

(3)命名空间

命名空间也可以自定义,如可以跟其他文件的命名空间名称一样,或者写其他字符。

规范1:所有文件中的命名空间都跟项目名称一致,这样在导入不同模块时,是需要使用同个命名空间即可,适合小型项目。

规范2:命名空间.目录路径, 这种方式适合大型项目,在引用时,需要using 命名空间.目录路径,这样就可以导入该空间下的类或方法了。

(4)引用定义好的类

在Hello.cs文件中,写入:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ConsoleApp1{    class HelloWorld    {        public void PrintMessage()        {            Console.WriteLine("Hello World");        }    }}

在Program.cs中写入:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using ConsoleApp1; // 使用命名空间namespace ConsoleApp1 {    class Program    {        static void Main(string[] args)         {            // 创建 HelloWorld 类的实例            HelloWorld helloWorld = new HelloWorld();            // 调用 PrintMessage 方法            helloWorld.PrintMessage();            Console.ReadLine();         }    }}

可正常运行:

3、实现MC服务器

(1)下载第三方应用程序

Download - 胡工科技官网 (hsltechnology.cn)

(2)加载应用程序的dll库

解压安装包》找到以下库

使用VS添加库,详见本文:开发环境》加载库

(3)查看接口文档

MelsecMcServer 类 (hslcommunication.cn)

(4)编程

实现功能:启动MC服务器,往D0寄存器写入“AA”,100ms后往D0寄存器写入“00”,接着读取D10数据,5秒后重复该动作。

新增McServer.cs,写入以下代码:

using System;using System.Text;using System.Timers;using HslCommunication.Profinet.Melsec;using TimerThreading = System.Threading.Timeout;namespace ConsoleApp1{    class McServer    {        private readonly string writeAddress;        private readonly string writeValue;        private readonly string readAddress;        private MelsecMcServer mcServer;        private Timer writeTimer;        private int elapsedTime;  // 用来记录已延迟的时间        private int delayTime;  // 延迟时间        private int existTime;  // 指令存在时间        public McServer(string writeAddress, string writeValue, string readAddress)        {            this.writeAddress = writeAddress;            this.writeValue = writeValue;            this.readAddress = readAddress;            this.elapsedTime = 0; // 初始化已延迟时间        }        // 启动服务器时,增加了 exist_time 和 delayTime 参数        public void StartServer(int port, int existTime, int delayTime)        {            try            {                // 初始化并启动 MC 服务器                mcServer = new MelsecMcServer                {                    ActiveTimeSpan = TimeSpan.FromHours(24) // 运行1小时                    // ActiveTimeSpan = TimerThreading.InfiniteTimeSpan  //永久运行,可能不支持                };                mcServer.ServerStart(port);                Console.WriteLine($"MC 3E Frame Server started on port {port}.");                // 保存延迟时间                this.delayTime = delayTime;                this.existTime = existTime;                // 初始化定时器,使用 exist_time 控制定时器触发的间隔                writeTimer = new Timer(delayTime);                writeTimer.Elapsed += WriteTimer_Elapsed; // 每次定时器触发时执行写入操作                writeTimer.AutoReset = true;                writeTimer.Start();                Console.WriteLine("Write timer started.");            }            catch (Exception ex)            {                Console.WriteLine($"Failed to start server: {ex.Message}");            }        }        // 停止服务器        public void StopServer()        {            try            {                writeTimer.Stop();                mcServer.ServerClose();                Console.WriteLine("Server and timer stopped.");            }            catch (Exception ex)            {                Console.WriteLine($"Error stopping server: {ex.Message}");            }        }        // 写入并延迟修改寄存器的操作        private void WriteTimer_Elapsed(object sender, ElapsedEventArgs e)        {            try            {                // 每次定时器触发,增加已延迟时间                elapsedTime += (int)writeTimer.Interval;                // 写入AA到寄存器                mcServer.Write(writeAddress, Encoding.ASCII.GetBytes(writeValue));                Console.WriteLine($"Wrote '{writeValue}' to {writeAddress}");                // 延时                System.Threading.Thread.Sleep(existTime);                // 延迟时间(delayTime)后修改寄存器为00                mcServer.Write(writeAddress, Encoding.ASCII.GetBytes("00"));                Console.WriteLine($"Changed {writeAddress} to '00'");                // 读取寄存器值并打印调试信息                var readResult = mcServer.Read(readAddress, (ushort)writeValue.Length);                if (readResult.IsSuccess)                {                    string readValue = Encoding.ASCII.GetString(readResult.Content);                    Console.WriteLine($"Successfully read '{readValue}' from {readAddress}");                }                else                {                    Console.WriteLine($"Failed to read from {readAddress}: {readResult.Message}");                }            }            catch (Exception ex)            {                Console.WriteLine($"Error in WriteTimer_Elapsed: {ex.Message}");            }        }    }}

在Program.cs文件中写入

using System;using HslCommunication.Profinet.Melsec;using System.Timers;namespace ConsoleApp1{    class Program    {        static McServer mcServer; // MC 服务器实例        static void Main(string[] args)        {            // 打印欢迎信息            HelloWorld helloWorld = new HelloWorld();            helloWorld.PrintMessage();            // 初始化并启动 3E 帧 MC 服务器            mcServer = new McServer("D0", "AA", "D10");            mcServer.StartServer(9600, 100, 5000);            Console.WriteLine("Press Enter to exit...");            // 等待用户按下 Enter 键退出            Console.ReadLine();            // 停止服务器和定时器            mcServer.StopServer();        }    }}

(5)运行

(6)命名空间那些事儿

对于内置模块,可以不使用using关键字引入,可直接通过完整路径使用,如上述代码System.Threading.Thread.Sleep(1000);

对于同个命名空间,可以不使用using关键字引入,可直接在该命名空间下使用属于该命名空间的类和方法,无论他在哪里,如上述代码 static McServer mcServer;

对于命名空间别名,用法如using TimerThreading = System.Threading.Timeout; 即TimerThreading为System.Threading.Timeout的别名,用于解决不同模块的Timeout的同名冲突。

(7)构造函数

class McServer的构造函数是public McServer,即如果你想给这个class McServer类传参,需要构造函数public McServer,它的名字必须和类名相同,且没有返回类型(连 void 也没有)。

(8)static关键字

static定义了一个静态变量,因为Main的方法是static,只能访问同样是 static 的类成员。原因是 static 方法属于类,而非某个对象实例,因此它不能访问非静态成员(需要实例来调用)。我们需要将 mcServer 声明为 static,以便与 Main 方法在同一静态上下文中使用。如果为非静态实例,将会出现以下报错:

4、继承

问大家一个问题,爸爸的爸爸叫什么?当你很快速的回答这个问题后,你就发现,你有了继承的意识。

新增McServer_inherit.cs文件写入:

namespace ConsoleApp1{    class McServer_Base : McServer  // 基本继承    {        public McServer_Base(string writeAddress, string writeValue, string readAddress)            : base(writeAddress, writeValue, readAddress)        {        }    }    class McServer_Override : McServer  // 方法重写    {        public McServer_Override(string writeAddress, string writeValue, string readAddress)            : base(writeAddress, writeValue, readAddress)        {        }        public override void StartServer(int port, int existTime, int delayTime)        {            System.Console.WriteLine("Override StartServer");            base.StartServer(port, existTime, delayTime);        }    }    class McServer_New : McServer  // 方法隐藏    {        public McServer_New(string writeAddress, string writeValue, string readAddress)            : base(writeAddress, writeValue, readAddress)        {        }        public new void StartServer(int port, int existTime, int delayTime)        {            System.Console.WriteLine("New StartServer");            base.StartServer(port, existTime, delayTime);        }    }    class McServer_Constructor : McServer  // 构造函数继承    {        public McServer_Constructor(string writeAddress, string writeValue, string readAddress)            : base(writeAddress, writeValue, readAddress)        {            System.Console.WriteLine("McServer_Constructor initialized");        }    }    abstract class AbstractServer    {        public abstract void StartServer(int port, int existTime, int delayTime);        public abstract void StopServer();    }    class McServer_Abstract : AbstractServer  // 抽象类继承    {        private McServer mcServer;        public McServer_Abstract(string writeAddress, string writeValue, string readAddress)        {            mcServer = new McServer(writeAddress, writeValue, readAddress);        }        public override void StartServer(int port, int existTime, int delayTime)        {            mcServer.StartServer(port, existTime, delayTime);        }        public override void StopServer()        {            mcServer.StopServer();        }    }    interface IServer    {        void StartServer(int port, int existTime, int delayTime);        void StopServer();    }    class McServer_Interface : IServer  // 接口继承    {        private McServer mcServer;        public McServer_Interface(string writeAddress, string writeValue, string readAddress)        {            mcServer = new McServer(writeAddress, writeValue, readAddress);        }        public void StartServer(int port, int existTime, int delayTime)        {            mcServer.StartServer(port, existTime, delayTime);        }        public void StopServer()        {            mcServer.StopServer();        }    }    class McServer_Parent: McServer    {        public McServer_Parent(string writeAddress, string writeValue, string readAddress): base(writeAddress, writeValue, readAddress) // 调用父类的构造函数        {            System.Console.WriteLine("McServer_Parent constructor called.");        }        public override void StartServer(int port, int existTime, int delayTime)        {            System.Console.WriteLine("Starting server in McServer_Parent");            base.StartServer(port, existTime, delayTime);        }    }    class McServer_MultiLevel : McServer_Parent  // 多层继承    {        public McServer_MultiLevel(string writeAddress, string writeValue, string readAddress)    : base(writeAddress, writeValue, readAddress) // 调用父类的构造函数        {            System.Console.WriteLine("McServer_MultiLevel constructor called.");        }    }    sealed class McServer_Sealed  // 防止继承    {        private readonly McServer mcServer;        public McServer_Sealed(string writeAddress, string writeValue, string readAddress)        {            mcServer = new McServer(writeAddress, writeValue, readAddress);        }        public void StartServer(int port, int existTime, int delayTime)        {            mcServer.StartServer(port, existTime, delayTime);        }        public void StopServer()        {            mcServer.StopServer();        }    }}

在Program.cs文件中依次调用

using System;using HslCommunication.Profinet.Melsec;using System.Timers;namespace ConsoleApp1{    class Program    {        // static McServer mcServer; // MC 服务器实例        // static McServer_Base mcServer;  // 0基本继承        // static McServer_Override mcServer;  // 1方法重写        // static McServer_New mcServer;  // 2方法隐藏        // static McServer_Constructor mcServer;  // 3构造函数继承        // static McServer_Abstract mcServer;  // 4抽象类继承        // static McServer_Interface mcServer;  // 5接口继承        // static McServer_MultiLevel mcServer;  // 6多层继承        static McServer_Sealed mcServer;  // 7防止继承        static void Main(string[] args)        {            // 打印欢迎信息            HelloWorld helloWorld = new HelloWorld();            helloWorld.PrintMessage();            // 初始化并启动 3E 帧 MC 服务器            //mcServer = new McServer("D0", "AA", "D10");            //mcServer = new McServer_Base("D0", "AA", "D10");  // 0基本继承            //mcServer = new McServer_Override("D0", "AA", "D10");  // 1方法重写            //mcServer = new McServer_New("D0", "AA", "D10");  // 2方法隐藏            //mcServer = new McServer_Constructor("D0", "AA", "D10");  // 3构造函数继承            //mcServer = new McServer_Abstract("D0", "AA", "D10");  // 4抽象类继承            //mcServer = new McServer_Interface("D0", "AA", "D10");  // 5接口继承            //mcServer = new McServer_MultiLevel("D0", "AA", "D10");  // 6多层继承            mcServer = new McServer_Sealed("D0", "AA", "D10");  // 7防止继承            mcServer.StartServer(9600, 100, 5000);            Console.WriteLine("Press Enter to exit...");            // 等待用户按下 Enter 键退出            Console.ReadLine();            // 停止服务器和定时器            mcServer.StopServer();        }    }}

需要改造一下父类方法才能重写方法,在McServer.cs文件中:

public void StartServer(int port, int existTime, int delayTime)

改为

public virtual void StartServer(int port, int existTime, int delayTime)

(1)继承的注意事项

当父类有构造函数时,需要继承构造函数,如:

        public McServer_Base(string writeAddress, string writeValue, string readAddress)            : base(writeAddress, writeValue, readAddress)        {        }

当父类方法被重写时,如果还想继续使用父类方法,需要显性调用,如:

base.StartServer(port, existTime, delayTime);

当父类方法被重写,而且多个子类都在继续使用父类方法,那么会被调用多次,但父类方法会被实例化一次,如多层继承的:

base.StartServer(port, existTime, delayTime);

方法隐藏跟重写方法有些相似。方法隐藏是指子类定义了与父类同名的方法,但没有使用 override 关键字,而是使用 new 关键字显式地隐藏父类中的方法。在这种情况下,子类的方法会隐藏父类的方法,而不是重写父类的方法。起到一个效果:即使父类的方法不是 virtual,子类依然可以使用 new 关键字来隐藏父类的方法。这种情况下,隐藏的是父类的方法,而不是重写父类的方法。

构造函数继承即在继承父类构造函数后,可添加一些行为。

对于抽象类继承,可以强制子类实现抽象方法: 抽象类通过定义抽象方法,强制继承该类的子类必须提供这些方法的实现。这样可以确保所有子类都遵循相同的接口规范。可以统一接口: 通过抽象类,父类提供了一种统一的接口,子类可以根据自己的需求实现这些接口。这样可以让代码结构更加清晰,并且容易扩展。可以提供共用的代码: 抽象类除了抽象方法,还可以包含普通的已实现方法。在这种情况下,子类继承抽象类不仅可以实现抽象方法,还可以直接使用父类中已经实现的方法,从而减少代码重复。这得用代码来解释:

// 抽象类,定义了必须由子类实现的抽象方法abstract class AbstractServer{    // 抽象方法,子类必须重写此方法并提供具体实现    public abstract void StartServer(int port, int existTime, int delayTime);    public abstract void StopServer();}// 具体的子类,继承自抽象类并实现其抽象方法class McServer_Abstract : AbstractServer  // 抽象类继承{    private McServer mcServer;    // 构造函数:子类在构造时实例化 McServer 对象    public McServer_Abstract(string writeAddress, string writeValue, string readAddress)    {        mcServer = new McServer(writeAddress, writeValue, readAddress);    }    // 重写抽象方法 StartServer,提供具体实现    public override void StartServer(int port, int existTime, int delayTime)    {        mcServer.StartServer(port, existTime, delayTime); // 调用 McServer 类的方法    }    // 重写抽象方法 StopServer,提供具体实现    public override void StopServer()    {        mcServer.StopServer(); // 调用 McServer 类的方法    }}

接口继承和抽象类继承实在太像了,直接上对比:

特性 接口(Interface) 抽象类(Abstract Class)
继承方式 可以被多个类实现(支持多重继承) 只能单继承一个抽象类
方法实现 只能声明方法,不能提供实现,除非是 C# 8.0 后的默认实现 可以声明抽象方法,也可以提供方法实现
成员限制 只能包含方法签名、属性、事件、索引器等声明 可以包含方法、字段、属性、事件、构造函数等
访问修饰符 所有成员默认为 public,不能指定其他访问修饰符 可以使用任何访问修饰符(private、protected、public)
构造函数 不能有构造函数 可以有构造函数
字段 不能包含字段 可以包含字段
适用场景 用于定义行为规范,支持不同类的统一接口,适合松耦合设计 用于共享部分实现和公共基础设施,适合有共性的类

C#没有直接实现多重继承的方式,但可由接口继承来实现。PS:多重继承 是指一个类可以同时继承多个父类的特性和行为。这意味着子类可以从多个父类继承方法、属性和其他成员,允许类从多个来源组合其功能。这种继承方式是面向对象编程中的一种常见特性,但并非所有编程语言都支持多重继承。

防止继承表明这个类,不会被其他类继承。

5、实现TCPclient

新增TCP.cs

using System;using System.Net.Sockets;using System.Text;using System.Threading;namespace ConsoleApp1{    public class TcpClientWrapper : IDisposable    {        private TcpClient client;        private NetworkStream stream;        private bool disposed = false; // 用于标记是否已释放资源        private readonly ManualResetEvent connectDone = new ManualResetEvent(false); // 用于实现连接超时        private readonly int connectTimeout = 5000; // 连接超时时间(毫秒)        // 初始化TCP客户端并连接到服务器。        public TcpClientWrapper(string serverIP, int serverPort)        {            try            {                client = new TcpClient                {                    ReceiveTimeout = 30000, // 接收超时时间(30秒)                    SendTimeout = 30000,   // 发送超时时间(30秒)                    NoDelay = true         // 禁用Nagle算法,减少延迟                };                Console.WriteLine("正在连接到服务器...");                var asyncResult = client.BeginConnect(serverIP, serverPort, null, null);                if (!asyncResult.AsyncWaitHandle.WaitOne(connectTimeout))                {                    throw new TimeoutException("连接服务器超时!");                }                client.EndConnect(asyncResult); // 完成连接                stream = client.GetStream();                Console.WriteLine("已连接到服务器,长连接已建立!");            }            catch (SocketException ex)            {                Console.WriteLine($"连接服务器失败: {ex.Message}");                Dispose();                throw;            }            catch (TimeoutException ex)            {                Console.WriteLine($"连接超时: {ex.Message}");                Dispose();                throw;            }            catch (Exception ex)            {                Console.WriteLine($"未知错误: {ex.Message}");                Dispose();                throw;            }        }        // 发送消息到服务器。        public void Send(string message)        {            if (disposed)            {                throw new ObjectDisposedException(nameof(TcpClientWrapper));            }            if (stream == null || !client.Connected)            {                throw new InvalidOperationException("尚未连接到服务器。");            }            try            {                byte[] data = Encoding.UTF8.GetBytes(message);                stream.Write(data, 0, data.Length);                Console.WriteLine($"发送消息: {message}");            }            catch (Exception ex)            {                Console.WriteLine($"发送消息失败: {ex.Message}");            }        }        // 从服务器接收消息。        public string Receive()        {            if (disposed)            {                throw new ObjectDisposedException(nameof(TcpClientWrapper));            }            if (stream == null || !client.Connected)            {                throw new InvalidOperationException("尚未连接到服务器。");            }            try            {                byte[] buffer = new byte[1024];                int bytesRead = stream.Read(buffer, 0, buffer.Length);                string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);                Console.WriteLine($"接收到消息: {response}");                return response;            }            catch (Exception ex)            {                Console.WriteLine($"接收消息失败: {ex.Message}");                return string.Empty;            }        }        // 关闭连接并释放资源。        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this); // 告诉GC不需要再调用终结器        }        // 释放资源的核心方法        protected virtual void Dispose(bool disposing)        {            if (!disposed)            {                if (disposing)                {                    // 释放托管资源                    if (stream != null)                    {                        stream.Close();                        stream = null;                    }                    if (client != null)                    {                        client.Close();                        client = null;                    }                }                // 如果有非托管资源,可以在这里释放                disposed = true;                Console.WriteLine("已关闭连接并释放资源。");            }        }        // 析构函数,用于释放资源        ~TcpClientWrapper()        {            Dispose(false);        }    }}

更新Program文件:

using System;using HslCommunication.Profinet.Melsec;using System.Timers;namespace ConsoleApp1{    class Program    {        static void Main(string[] args)        {            string serverIP = "192.168.66.188";            int serverPort = 5000;            try            {                using (TcpClientWrapper tcpClient = new TcpClientWrapper(serverIP, serverPort))                {                    while (true)                    {                        Console.Write("请输入发送消息 (输入 'exit' 退出): ");                        string message = Console.ReadLine();                        if (message?.ToLower() == "exit")                        {                            break;                        }                        tcpClient.Send(message);                        string response = tcpClient.Receive();                        Console.WriteLine($"服务器响应: {response}");                    }                }            }            catch (TimeoutException ex)            {                Console.WriteLine($"连接超时: {ex.Message}");            }            catch (Exception ex)            {                Console.WriteLine($"发生异常: {ex.Message}");            }        }    }}

(1)using方法

using方法除了引入命名空间,还是可以用于自动释放资源。如:

using (TcpClientWrapper tcpClient = new TcpClientWrapper(serverIP, serverPort))

此时,using 语句的主要目的是:资源管理,即自动调用实现了 IDisposable 接口的类的 Dispose 方法。

这里需要注意:

Dispose 方法的名称是固定的,可以在这个方法里面加入其他资源释放方法。

所引用的类,需要继承IDisposable,即必须实现 IDisposable 接口。

(2)析构函数

析构函数,是 C# 中的一种特殊方法,用于对象销毁时执行清理操作。如:

 ~TcpClientWrapper()

析构函数在垃圾回收器 (Garbage Collector, GC) 释放对象时被调用,用于释放非托管资源或执行一些清理工作。

(3)内存释放机制对比

特点 析构函数 IDisposable 接口
调用方式 自动调用:由垃圾回收器在对象被销毁时自动触发。 手动调用:需要显式调用 Dispose 方法,或者使用 using 语句。
执行时间确定性 不确定性:无法保证析构函数的执行时间,因为垃圾回收的时机不可控。 确定性:Dispose 方法立即执行,适合需要确定释放资源的场景。
性能成本 可能增加垃圾回收器的负担,频繁调用可能对性能产生负面影响。 性能友好,资源可以及时释放,不会额外增加垃圾回收的负担。
推荐性 适合作为资源清理的最后保障机制,不建议单独依赖。 微软推荐使用 IDisposable 接口来清理资源,符合 .NET 开发最佳实践。

(4)同名方法其实是不同方法

以下是名称相同的方法,但是因签名不一样而不同的方法:

        // 关闭连接并释放资源。        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this); // 告诉GC不需要再调用终结器        }        // 释放资源的核心方法        protected virtual void Dispose(bool disposing)        {            if (!disposed)            {                if (disposing)                {                    // 释放托管资源                    if (stream != null)                    {                        stream.Close();                        stream = null;                    }                    if (client != null)                    {                        client.Close();                        client = null;                    }                }                // 如果有非托管资源,可以在这里释放                disposed = true;                Console.WriteLine("已关闭连接并释放资源。");            }        }

在 C# 中,方法的合法性由方法签名(方法名+参数列表)决定。public void Dispose() 方法没有参数。protected virtual void Dispose(bool disposing) 方法有一个 bool 类型的参数。这两者是不同的方法,因此在同一个类中可以同时存在。

(5)托管资源与非托管资源

特性 托管资源 非托管资源
管理方式 由垃圾回收器自动管理 需要显式管理,通常通过 Dispose 或析构函数处理
资源类型 常见例子:字符串 (string)、数组 (int[])、集合 (List<>Dictionary<>) 常见例子:文件 (FileStream)、数据库连接 (SqlConnection)、图形对象 (Bitmap)、套接字 (Socket)
释放方式 无需显式释放 显式调用释放方法(Dispose),如 stream.Dispose()
风险 无需担心资源泄漏 未释放可能导致内存泄漏或句柄耗尽,影响系统稳定性

(6)释放系统资源的操作方式

                    // 释放托管资源                    if (stream != null)                    {                        stream.Close();                        stream = null;                    }                    if (client != null)                    {                        client.Close();                        client = null;                    }

stream.Close() 用于关闭 NetworkStream 对象,释放与其关联的系统资源,如缓冲区或文件描述符。client.Close() 用于关闭 TcpClient 对象,断开底层 TCP 连接,释放与之关联的系统资源(如套接字句柄)。置为 null,是为了告诉垃圾回收器(GC),该对象已经不再需要,可以回收其内存,方便垃圾回收器清理无用的对象。

相关推荐