C#基础知识

文章目录

0 C#介绍

  1. 定义与背景

    • C#(发音为C - sharp)是微软公司开发的一种高级编程语言。它是专门为构建在微软的.NET平台上运行的各种应用程序而设计的。在2000年左右推出,目的是结合当时编程语言的优点,如C++的强大功能和Java的简单性与安全性,来满足开发人员对高效、安全、面向对象编程的需求。
  2. 语法结构

    • 基础语法规则
      • C#的代码以语句为基本单位,语句以分号“;”结尾。例如,Console.WriteLine("Hello");这一语句用于在控制台输出“Hello”,最后的分号是必须的。
      • 代码块通过花括号“{ }”来界定范围。比如在定义一个方法或者一个控制结构(如if - else语句)时,花括号内包含的是属于该结构的代码部分。
    • 变量与数据类型
      • C#是强类型语言,这意味着每个变量都必须有明确的类型声明。常见的数据类型有整数类型(如int,用于表示整数,如int num = 5;)、浮点类型(如floatdouble,用于表示带有小数的数字)、字符类型(char,如char letter = 'A';)和字符串类型(string,如string name = "John";)。
      • 变量的命名需要遵循一定的规则,通常以字母或下划线开头,后面可以跟字母、数字或下划线,并且区分大小写。
  3. 面向对象特性

    • 类与对象
      • 类是C#中面向对象编程的核心概念。它是一种数据结构,用于封装数据成员(字段)和方法成员。例如,定义一个简单的“Person”类:
      class Person
      {
          public string Name;
          public int Age;
          public void SayHello()
          {
              Console.WriteLine($"我叫{Name},今年{Age}岁。");
          }
      }
      
      • 对象是类的实例。可以通过new关键字来创建对象。例如,Person p = new Person(); p.Name = "Alice"; p.Age = 20; p.SayHello();,这里创建了一个“Person”类的对象p,设置了它的姓名和年龄属性,并调用了SayHello方法。
    • 继承与多态
      • 继承允许一个类(派生类)继承另一个类(基类)的属性和方法。例如,定义一个“Student”类继承自“Person”类:
      class Student : Person
      {
          public string School;
          public void Study()
          {
              Console.WriteLine($"{Name}{School}学习。");
          }
      }
      
      • 多态是指不同类的对象对同一消息做出不同响应的能力。在C#中,可以通过方法重写来实现。例如,在基类中有一个虚方法,在派生类中重写这个方法,当通过基类引用调用这个方法时,会根据对象的实际类型(是基类对象还是派生类对象)来执行相应的方法。
  4. 应用场景

    • 桌面应用开发
      • C#与Windows Forms或Windows Presentation Foundation(WPF)结合可以开发出功能丰富的桌面应用程序。Windows Forms提供了一种简单的方式来创建具有传统Windows风格的用户界面,而WPF则侧重于提供更灵活、更具视觉效果的界面设计,通过XAML(可扩展应用程序标记语言)和C#代码的结合来构建界面。
    • Web应用开发
      • 与ASP.NET技术配合,C#用于开发Web应用程序。ASP.NET提供了多种开发模式,如Web Forms和MVC(Model - View - Controller)。在MVC模式下,C#主要用于编写控制器(Controller)中的业务逻辑,处理用户请求并返回数据给视图(View)进行展示。
    • 游戏开发
      • 在游戏开发领域,C#与Unity游戏引擎配合紧密。Unity允许开发者使用C#编写游戏脚本,包括游戏角色的行为控制、场景切换、用户交互等各个方面的内容,能够开发出跨平台的2D和3D游戏。

1 基本语法

1.1. 变量、类型和方法名称

  • C#是区分大小写的语言。这意味着变量名、类型名和方法名等标识符的大小写不同会被视为不同的实体。
  • 例如,定义一个变量int myVariable;,如果在其他地方尝试访问myvariable(小写的v),编译器会认为这是一个未定义的标识符,从而产生错误。同样,对于类型,如stringString(在C#中,stringSystem.String类型的别名,它们是等价的,但要注意大小写一致),如果在代码中混淆使用,也会导致错误。对于方法名也是如此,MyMethod()mymethod()是不同的方法名。

1.2. 关键字

  • C#的关键字是固定的,并且也是区分大小写的。例如,if是一个关键字,用于条件判断,如果写成IF,编译器会将其视为普通的标识符,而不是条件判断关键字,从而导致语法错误。其他关键字如forwhileclassnamespace等都遵循这个规则。

1.3. 字符串内容与字符内容

  • 在字符串(string类型)和字符(char类型)内容中,大小写是有意义的。例如,字符串"Hello""hello"是不同的内容。在比较字符串是否相等或者进行字符串操作时,大小写会影响结果。可以使用string的方法,如Equals方法(str1.Equals(str2, StringComparison.OrdinalIgnoreCase)可以忽略大小写比较两个字符串是否相等)或者ToUpper(将字符串转换为大写)和ToLower(将字符串转换为小写)方法来处理大小写敏感的问题。对于字符也是一样,字符'A''a'是不同的。

1.4 命名

在C#中,变量命名需要遵循一定的规则和约定,以确保代码的可读性、可维护性以及符合编程语言的语法要求。以下是详细的C#变量命名规则:

语法规则

必须以字母、下划线(_)或 @ 符号开头
  • 变量名的第一个字符可以是字母(包括英文字母和Unicode字符)、下划线或@符号。例如:
    int _count; // 以下划线开头
    string @name; // 以@符号开头,常用于与关键字冲突的情况
    char letter; // 以字母开头
    
后续字符可以是字母、数字、下划线
  • 变量名的第一个字符之后,可以包含字母、数字和下划线。例如:
    int myVariable1;
    string user_name;
    
区分大小写
  • C#是区分大小写的语言,因此myVariableMyVariable是不同的变量名。例如:
    int myVariable = 10;
    int MyVariable = 20; // 这是一个不同的变量
    
不能使用C#关键字作为变量名(除非使用 @ 符号)
  • C#有许多保留关键字,如intstringiffor等,不能直接用作变量名。但如果确实需要使用关键字作为变量名,可以在关键字前加上@符号。例如:
    int @int = 5; // 使用@符号来使用关键字作为变量名
    
不能包含空格或其他特殊字符(除了下划线和 @ 符号)
  • 变量名中不能包含空格、标点符号等特殊字符(下划线和@符号除外)。例如,my variable是不合法的变量名,而my_variable是合法的。

命名约定

遵循驼峰命名法
  • 小驼峰命名法(camelCase):对于局部变量、方法参数等,通常使用小驼峰命名法,即第一个单词的首字母小写,后续单词的首字母大写。例如:
    int myVariable;
    void CalculateArea(int baseLength, int height);
    
  • 大驼峰命名法(PascalCase):对于类名、属性名等,通常使用大驼峰命名法,即每个单词的首字母都大写。例如:
    class MyClass
    {
        public int MyProperty { get; set; }
    }
    
命名要有意义
  • 变量名应该能够清晰地表达其用途和含义。避免使用过于简单或无意义的变量名,如abx等,除非在非常简单的示例或临时变量中。例如,使用studentName而不是sn来表示学生的姓名。
避免使用缩写(除非是广泛认可的缩写)
  • 尽量使用完整的单词来命名变量,避免使用难以理解的缩写。但对于一些广泛认可的缩写,如id(identifier)、num(number)等,可以使用。例如,使用customerId而不是cid
常量命名使用全大写,单词间用下划线分隔
  • 对于常量(使用const关键字声明的变量),通常使用全大写字母,单词之间用下划线分隔。例如:
    const int MAX_VALUE = 100;
    

遵循这些变量命名规则和约定可以使代码更易于阅读、理解和维护,同时也有助于团队协作和代码的一致性。

1.5 foreach

在C#中,foreach 语句是一种用于遍历集合或数组中每个元素的循环结构,它提供了一种简洁、安全且易于使用的方式来访问集合中的元素。以下是关于 foreach 语句的详细介绍:

基本语法

foreach (var element in collection)
{
    // 处理 element 的代码
}
  • var:用于声明一个变量,该变量的类型会根据集合中元素的类型自动推断。也可以显式指定变量的类型。
  • element:表示集合中的当前元素,在每次循环迭代时,它会依次被赋值为集合中的每个元素。
  • collection:表示要遍历的集合或数组,可以是任何实现了 IEnumerableIEnumerable<T> 接口的对象。

使用示例

遍历数组
using System;

class Program
{
    static void Main()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };

        foreach (int number in numbers)
        {
            Console.WriteLine(number);
        }
    }
}

在这个示例中,foreach 语句遍历了一个整数数组 numbers,并将数组中的每个元素依次赋值给变量 number,然后将其输出到控制台。

遍历列表
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> names = new List<string> { "Alice", "Bob", "Charlie" };

        foreach (string name in names)
        {
            Console.WriteLine(name);
        }
    }
}

这里,foreach 语句遍历了一个字符串列表 names,并输出列表中的每个元素。

遍历字典
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<string, int> scores = new Dictionary<string, int>
        {
            { "Alice", 85 },
            { "Bob", 90 },
            { "Charlie", 78 }
        };

        foreach (KeyValuePair<string, int> pair in scores)
        {
            Console.WriteLine($"Name: {pair.Key}, Score: {pair.Value}");
        }
    }
}

对于字典,foreach 语句遍历的是 KeyValuePair<TKey, TValue> 对象,通过 KeyValue 属性可以分别访问键和值。

注意事项

  • 只读访问:在 foreach 循环中,element 是只读的,不能直接对其进行赋值操作。如果需要修改集合中的元素,通常需要使用 for 循环。
int[] numbers = { 1, 2, 3 };
foreach (int number in numbers)
{
    // 下面这行代码会编译错误,因为 number 是只读的
    // number = number * 2; 
}
  • 集合修改:在 foreach 循环遍历集合的过程中,一般不允许修改集合的结构(如添加、删除元素),否则会抛出 InvalidOperationException 异常。
List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int number in numbers)
{
    // 下面这行代码会抛出异常
    // numbers.Add(number * 2); 
}
  • 异常处理:如果在 foreach 循环中发生异常,循环会立即终止,并且异常会被抛出。可以使用 try-catch 块来捕获和处理异常。

2 VS2022操作

在C#中建立新的类可以通过以下几种常见的方式,下面会结合示例详细介绍。

1. 在同一项目文件中直接定义新类

  • 步骤
    1. 打开Visual Studio(这里以Visual Studio 2022为例),创建一个新的C#项目(如控制台应用程序)。
    2. 在解决方案资源管理器中,找到要添加类的项目,展开项目节点。
    3. 通常,项目会有一个默认的代码文件(如Program.cs),你可以直接在这个文件中或者新建一个代码文件(如MyClass.cs)来定义新类。
  • 示例代码(在MyClass.cs文件中定义一个简单的类)
// 定义一个新的类,类名为 MyClass
public class MyClass
{
    // 类的成员变量
    private string name;

    // 类的构造函数
    public MyClass(string inputName)
    {
        name = inputName;
    }

    // 类的方法
    public void PrintName()
    {
        Console.WriteLine("Name: " + name);
    }
}
  • 使用这个类的示例(在Program.cs中)
using System;

class Program
{
    static void Main()
    {
        // 创建 MyClass 类的对象
        MyClass myObject = new MyClass("John");
        // 调用 MyClass 类的方法
        myObject.PrintName();
    }
}

2. 使用Visual Studio的添加新项功能来创建类

  • 步骤
    1. 在解决方案资源管理器中,右键单击项目名称,选择“添加” -> “新建项”。
    2. 在弹出的“添加新项”对话框中,选择“类”模板。
    3. 在“名称”文本框中输入类的名称(如NewClass.cs),然后点击“添加”按钮。
    4. Visual Studio会自动生成一个新的类文件,其中包含一个基本的类定义,你可以在这个文件中编辑类的成员和方法。
  • 自动生成的类文件示例(NewClass.cs
using System;

namespace YourNamespace // 这里的命名空间会根据项目设置自动生成
{
    public class NewClass
    {
        // 这里可以添加类的成员变量和方法
    }
}

3. 定义嵌套类(在一个类内部定义另一个类)

  • 示例代码
public class OuterClass
{
    // 外部类的成员变量
    private int outerValue;

    // 外部类的构造函数
    public OuterClass(int value)
    {
        outerValue = value;
    }

    // 定义一个嵌套类
    public class InnerClass
    {
        // 嵌套类的方法
        public void PrintOuterValue(OuterClass outer)
        {
            Console.WriteLine("Outer value: " + outer.outerValue);
        }
    }
}

// 使用嵌套类的示例
class Program
{
    static void Main()
    {
        OuterClass outerObject = new OuterClass(10);
        // 创建嵌套类的对象
        OuterClass.InnerClass innerObject = new OuterClass.InnerClass();
        // 调用嵌套类的方法
        innerObject.PrintOuterValue(outerObject);
    }
}

4. 定义抽象类

  • 示例代码
// 定义一个抽象类
public abstract class Shape
{
    // 抽象方法,没有具体实现,需要在派生类中实现
    public abstract double CalculateArea();
}

// 派生类继承自抽象类
public class Circle : Shape
{
    private double radius;

    public Circle(double r)
    {
        radius = r;
    }

    // 实现抽象方法
    public override double CalculateArea()
    {
        return Math.PI * radius * radius;
    }
}

// 使用抽象类和派生类的示例
class Program
{
    static void Main()
    {
        Circle circle = new Circle(5);
        double area = circle.CalculateArea();
        Console.WriteLine("Circle area: " + area);
    }
}

以上就是在C#中建立新类的几种常见方式和相关示例,你可以根据具体的需求选择合适的方式来定义类。

在WinForm中改变窗体的字体,可通过设计器和代码两种方式实现,以下是具体的操作方法:

Winform改变字体

1. 使用设计器改变窗体字体

步骤:
  • 打开Visual Studio并加载你的WinForm项目。
  • 在解决方案资源管理器中,双击要更改字体的窗体文件(通常是 Form1.cs),打开窗体设计器。
  • 在窗体设计器中,右键单击窗体空白处,选择“属性”,或者直接按 F4 键打开属性窗口。
  • 在属性窗口中,找到 Font 属性,点击其右侧的省略号(...)按钮,打开“字体”对话框。
  • 在“字体”对话框中,选择你想要的字体、字号和样式(如加粗、倾斜等),然后点击“确定”按钮。

2. 使用代码改变窗体字体

步骤:
  • 打开窗体的代码文件(如 Form1.cs)。
  • 在窗体的构造函数或其他合适的方法中,使用以下代码来更改窗体的字体:
using System;
using System.Drawing;
using System.Windows.Forms;

namespace WinFormFontChangeExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // 创建一个新的字体对象
            Font newFont = new Font("微软雅黑", 12, FontStyle.Regular);

            // 将新字体应用到窗体
            this.Font = newFont;
        }
    }
}
代码解释:
  • Font 类用于表示字体,通过 new Font("微软雅黑", 12, FontStyle.Regular) 创建了一个新的字体对象,其中 "微软雅黑" 是字体名称,12 是字号,FontStyle.Regular 表示常规样式(不加粗、不倾斜)。
  • this.Font = newFont; 将新创建的字体对象应用到当前窗体。

3. 批量改变窗体上所有控件的字体

如果需要同时改变窗体上所有控件的字体,可以使用递归方法遍历窗体上的所有控件,并设置它们的字体:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WinFormFontChangeExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // 创建一个新的字体对象
            Font newFont = new Font("微软雅黑", 12, FontStyle.Regular);

            // 调用方法改变窗体及所有控件的字体
            ChangeFont(this, newFont);
        }

        private void ChangeFont(Control control, Font newFont)
        {
            // 设置当前控件的字体
            control.Font = newFont;

            // 遍历当前控件的所有子控件
            foreach (Control childControl in control.Controls)
            {
                // 递归调用ChangeFont方法,改变子控件的字体
                ChangeFont(childControl, newFont);
            }
        }
    }
}
代码解释:
  • ChangeFont 方法接受一个 Control 类型的参数和一个 Font 类型的参数,用于递归地改变控件及其子控件的字体。
  • Form1 的构造函数中,调用 ChangeFont 方法并传入当前窗体和新字体对象,从而实现批量改变字体的效果。

通过以上方法,你可以在WinForm中方便地改变窗体及其控件的字体。

Label添加

在Visual Studio 2022中,“标签(Label)”通常是指WinForm设计器中的Label控件,如果你想问的是在WinForm设计器中如何添加Label控件,或者是如何调出显示属性等相关窗口,以下是具体介绍:

调出Label控件进行添加

  1. 打开WinForm设计器

    • 首先确保你已经创建了一个Windows Forms应用程序项目。在解决方案资源管理器中,双击.cs窗体文件(例如Form1.cs),打开WinForm设计器界面。
  2. 找到工具箱

    • 通常,工具箱会默认停靠在Visual Studio界面的左侧。如果没有显示,可以通过“视图”菜单,选择“工具箱”来将其显示出来。
  3. 添加Label控件

    • 在工具箱中,展开“所有Windows窗体”或“公共控件”分组(具体名称可能因版本和设置略有不同),找到Label控件。
    • 点击并按住Label控件,然后将其拖动到WinForm设计器的窗体上你想要放置的位置,松开鼠标即可添加一个Label控件到窗体上。

调出Label控件的属性窗口

  1. 选中Label控件

    • 在WinForm设计器中,点击你之前添加的Label控件,使其处于选中状态(控件周围会出现蓝色的边框)。
  2. 打开属性窗口

    • 一般情况下,属性窗口会默认停靠在Visual Studio界面的右侧。如果没有显示,可以通过以下两种方式调出:
      • 使用快捷键F4,可以快速打开或切换到属性窗口。
      • 通过“视图”菜单,选择“属性窗口”来显示属性窗口。
  3. 查看和修改Label控件的属性

    • 在属性窗口中,可以看到Label控件的各种属性,如Text(用于设置显示的文本内容)、Font(用于设置字体)、ForeColor(用于设置文本颜色)等。你可以根据需要修改这些属性的值。

调出Label控件的事件窗口(如果需要处理事件)

  1. 选中Label控件

    • 同样在WinForm设计器中,点击选中Label控件。
  2. 打开事件窗口

    • 可以通过以下两种方式打开事件窗口:
      • 使用快捷键Shift + F4
      • 通过“视图”菜单,选择“事件窗口”。
  3. 处理Label控件的事件

    • 在事件窗口中,可以看到Label控件支持的各种事件,如Click(点击事件)、MouseEnter(鼠标进入事件)等。双击你想要处理的事件名称,Visual Studio会自动在代码文件中生成相应的事件处理方法的框架,你可以在其中编写具体的代码逻辑。

3 函数

3.1 输出Console.Write

在C#中,Console.WriteLineConsole.Write都是用于向控制台输出信息的方法,但它们之间有一些重要的区别。

1. 输出后是否换行

  • Console.WriteLine
    • Console.WriteLine方法在输出完指定的信息后,会自动在输出的末尾添加一个换行符,将光标移动到下一行的开头。
    • 示例代码:
Console.WriteLine("第一行");
Console.WriteLine("第二行");
  • 输出结果:
第一行
第二行
  • Console.Write
    • Console.Write方法仅输出指定的信息,不会在输出的末尾添加换行符,光标会停留在输出内容的末尾。
    • 示例代码:
Console.Write("第一行");
Console.Write("第二行");
  • 输出结果:
第一行第二行

2. 重载方法的功能

  • Console.WriteLine
    • Console.WriteLine有多个重载方法,除了可以输出字符串,还可以直接输出各种基本数据类型(如整数、浮点数等),并且会自动调用这些数据类型的ToString方法将其转换为字符串输出,同时在末尾添加换行符。
    • 示例代码:
int number = 123;
Console.WriteLine(number);
  • 输出结果:
123
  • Console.Write
    • Console.Write同样有多个重载方法,可以输出各种基本数据类型,它会将这些数据类型转换为字符串输出,但不会添加换行符。
    • 示例代码:
int number1 = 123;
int number2 = 456;
Console.Write(number1);
Console.Write(number2);
  • 输出结果:
123456

3.2 Console.ReadKey

在C#中,Console.ReadKey 是一个用于从控制台读取用户按下的下一个字符或功能键的方法,它属于 System.Console 类。如果后面没有代码,则自动退出调试。

以下是关于 Console.ReadKey 的详细介绍:

方法重载

Console.ReadKey 有两个重载形式:

  1. public static ConsoleKeyInfo ReadKey ();

    • 这个重载会读取用户按下的下一个字符或功能键,并将其作为 ConsoleKeyInfo 对象返回,同时显示按下的字符(如果是可显示字符)。
  2. public static ConsoleKeyInfo ReadKey (bool intercept);

    • 此重载方法接受一个布尔类型的参数 intercept。当 intercepttrue 时,用户按下的键不会显示在控制台中;当 interceptfalse 时,用户按下的键会显示在控制台中。

返回值

Console.ReadKey 方法返回一个 ConsoleKeyInfo 类型的对象,该对象包含以下重要属性:

  • Key:一个 ConsoleKey 枚举值,表示用户按下的键,如 ConsoleKey.EnterConsoleKey.Escape 等。
  • KeyChar:一个 char 类型的值,表示用户按下的键对应的字符(如果该键有对应的字符)。对于功能键(如 F1F2 等),KeyChar 可能是 \u0000
  • Modifiers:一个 ConsoleModifiers 枚举值,表示用户按下的修饰键(如 ShiftCtrlAlt 等)的组合。

示例代码

以下是几个使用 Console.ReadKey 的示例:

基本用法
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("请按任意键继续...");
        ConsoleKeyInfo keyInfo = Console.ReadKey();
        Console.WriteLine(); // 换行
        Console.WriteLine($"你按下的键是:{keyInfo.Key}");
        Console.WriteLine($"对应的字符是:{keyInfo.KeyChar}");
    }
}

在这个示例中,程序会等待用户按下一个键,然后输出用户按下的键的信息。

使用 intercept 参数
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("请输入密码(输入时不显示字符):");
        string password = "";
        ConsoleKeyInfo keyInfo;

        do
        {
            keyInfo = Console.ReadKey(true); // 不显示输入的字符
            if (keyInfo.Key != ConsoleKey.Enter)
            {
                password += keyInfo.KeyChar;
            }
        } while (keyInfo.Key != ConsoleKey.Enter);

        Console.WriteLine(); // 换行
        Console.WriteLine($"你输入的密码是:{password}");
    }
}

在这个示例中,通过将 intercept 参数设置为 true,实现了在用户输入密码时不显示字符的效果。

根据用户输入的键执行不同操作
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("请选择操作:");
        Console.WriteLine("1. 显示信息");
        Console.WriteLine("2. 退出");

        ConsoleKeyInfo keyInfo = Console.ReadKey();

        switch (keyInfo.KeyChar)
        {
            case '1':
                Console.WriteLine(); // 换行
                Console.WriteLine("你选择了显示信息。");
                break;
            case '2':
                Console.WriteLine(); // 换行
                Console.WriteLine("程序退出。");
                return;
            default:
                Console.WriteLine(); // 换行
                Console.WriteLine("无效的选择。");
                break;
        }
    }
}

在这个示例中,根据用户按下的键执行不同的操作。

注意事项

  • Console.ReadKey 方法会阻塞当前线程,直到用户按下一个键。
  • 在某些情况下,Console.ReadKey 可能会受到控制台的输入模式、缓冲区设置等因素的影响。
  • 在使用 Console.ReadKey 时,要注意处理不同的键和修饰键组合,以确保程序的健壮性。

3.3 ///

在C#中,/// 是一种特殊的注释语法,用于生成XML文档注释。这些注释可以被Visual Studio等开发工具解析,用于生成代码的文档信息,帮助开发者更好地理解代码的功能和使用方法。以下是关于 /// 注释的详细介绍:

基本功能

当在代码中使用 /// 开始一行注释时,编译器会将这些注释作为XML格式的文档信息进行处理。这些注释通常用于描述类、方法、属性、字段等代码元素的用途、参数、返回值等信息。

使用场景和示例

类的文档注释
/// <summary>
/// 表示一个人的基本信息类。
/// 该类包含人的姓名和年龄信息,并提供了相应的属性进行访问。
/// </summary>
public class Person
{
    /// <summary>
    /// 获取或设置人的姓名。
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 获取或设置人的年龄。
    /// </summary>
    public int Age { get; set; }

    /// <summary>
    /// 构造函数,用于初始化人的姓名和年龄。
    /// </summary>
    /// <param name="name">人的姓名。</param>
    /// <param name="age">人的年龄。</param>
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    /// <summary>
    /// 打印人的基本信息。
    /// </summary>
    public void PrintInfo()
    {
        Console.WriteLine($"姓名:{Name},年龄:{Age}");
    }
}
方法的文档注释

在上述示例中,Person 类的构造函数和 PrintInfo 方法都使用了 /// 注释。对于方法,通常会使用 <summary> 标签来描述方法的功能,使用 <param> 标签来描述方法的参数,使用 <returns> 标签(如果方法有返回值)来描述方法的返回值。

XML文档注释标签

以下是一些常用的XML文档注释标签:

  • <summary>:用于提供对代码元素(如类、方法、属性等)的简要描述。
  • <param>:用于描述方法的参数,需要指定参数的名称。
  • <returns>:用于描述方法的返回值。
  • <remarks>:用于提供额外的详细说明。
  • <example>:用于提供代码示例,展示如何使用该代码元素。
  • <exception>:用于描述方法可能抛出的异常。

生成文档

在Visual Studio中,可以通过以下步骤生成XML文档:

  1. 右键单击项目,选择“属性”。
  2. 在“生成”选项卡中,勾选“XML文档文件”,并指定文档文件的输出路径和名称。
  3. 编译项目后,会在指定的路径生成一个XML文档文件,其中包含了代码中的文档注释信息。

查看文档

生成的XML文档可以通过Visual Studio的智能感知功能查看。当你在代码中使用某个类或方法时,鼠标悬停在上面,就可以看到相应的文档注释信息。

综上所述,/// 注释在C#中用于生成XML文档注释,帮助开发者编写更易于理解和维护的代码。

3.3 region

在C#中,#region#endregion 是预处理器指令,用于在代码中创建可折叠的代码区域。这两个指令主要用于组织和管理代码,提高代码的可读性和可维护性,特别是在处理大型代码文件时非常有用。

基本语法

#region 区域名称
// 这里是代码区域的内容
#endregion

功能和用途

代码组织和折叠
  • 使用 #region#endregion 可以将一段相关的代码块分组,并为这个代码块指定一个名称(区域名称)。在Visual Studio等集成开发环境(IDE)中,这些代码区域可以被折叠起来,只显示区域的名称,从而减少代码的视觉复杂度,让开发者能够更方便地浏览和管理代码。
示例
using System;

namespace RegionExample
{
    class Program
    {
        static void Main()
        {
            #region 初始化变量
            int number1 = 10;
            int number2 = 20;
            string message = "Hello, World!";
            #endregion

            #region 计算和输出
            int sum = number1 + number2;
            Console.WriteLine(message);
            Console.WriteLine($"两数之和: {sum}");
            #endregion
        }
    }
}

在上述示例中,代码被分为两个区域:“初始化变量”和“计算和输出”。在Visual Studio中,你可以点击区域名称旁边的减号(-)来折叠该区域的代码,只显示区域名称,这样可以更清晰地看到代码的整体结构。

嵌套使用

#region 指令还可以嵌套使用,即在一个 #region 代码块中可以包含另一个 #region 代码块。例如:

#region 外层区域
    // 外层区域的代码
    #region 内层区域
        // 内层区域的代码
    #endregion
#endregion

注意事项

  • #region#endregion 指令只是用于代码的组织和显示,不会影响代码的编译和运行。编译器在编译代码时会忽略这些指令,只处理实际的代码内容。
  • 区域名称可以是任何有效的字符串,但为了提高代码的可读性,建议使用描述性的名称来准确反映该区域的代码功能。
  • 虽然 #region#endregion 可以提高代码的可读性,但不要过度使用。过多的区域划分可能会使代码结构变得复杂,反而降低代码的可读性。一般来说,应该在逻辑上相关的代码块之间使用这些指令。

总之,#region#endregion 是C#中用于代码组织和管理的有用工具,可以帮助开发者更好地组织和浏览大型代码文件。

3.4 Convert

在C#中,System.Convert类提供了一组静态方法,用于将一个数据类型转换为另一个数据类型。这些方法可以处理各种基本数据类型之间的转换,并且在转换过程中会进行必要的验证和格式化。以下是Convert类的一些常见用法:

基本数据类型之间的转换

整数类型转换
// 将字符串转换为整数
string strNumber = "123";
int intValue = Convert.ToInt32(strNumber);
Console.WriteLine(intValue); // 输出: 123

// 将长整型转换为整数
long longValue = 123456789L;
int convertedInt = Convert.ToInt32(longValue);
Console.WriteLine(convertedInt); // 输出: 123456789
浮点类型转换
// 将字符串转换为双精度浮点数
string strDouble = "3.14";
double doubleValue = Convert.ToDouble(strDouble);
Console.WriteLine(doubleValue); // 输出: 3.14

// 将双精度浮点数转换为单精度浮点数
double doubleNum = 3.1415926;
float floatValue = Convert.ToSingle(doubleNum);
Console.WriteLine(floatValue); // 输出: 3.141593
字符类型转换
// 将字符转换为整数(字符的Unicode码值)
char charValue = 'A';
int charCode = Convert.ToInt32(charValue);
Console.WriteLine(charCode); // 输出: 65

// 将整数转换为字符(根据Unicode码值)
int code = 66;
char newChar = Convert.ToChar(code);
Console.WriteLine(newChar); // 输出: B
布尔类型转换
// 将字符串转换为布尔值
string strBool = "true";
bool boolValue = Convert.ToBoolean(strBool);
Console.WriteLine(boolValue); // 输出: True

// 将整数转换为布尔值(非零值为true,零值为false)
int intBool = 1;
bool convertedBool = Convert.ToBoolean(intBool);
Console.WriteLine(convertedBool); // 输出: True

日期和时间类型转换

// 将字符串转换为日期时间类型
string strDate = "2023-10-01";
DateTime dateValue = Convert.ToDateTime(strDate);
Console.WriteLine(dateValue); // 输出: 2023/10/1 0:00:00

// 将日期时间类型转换为字符串
string dateString = Convert.ToString(dateValue);
Console.WriteLine(dateString); // 输出: 2023/10/1 0:00:00

处理转换异常

在使用Convert类进行类型转换时,如果输入的字符串无法正确转换为目标类型,会抛出异常。例如,将一个非数字的字符串转换为整数时,会抛出FormatException异常。因此,在实际应用中,建议使用异常处理机制来捕获并处理这些异常。

try
{
    string invalidStr = "abc";
    int result = Convert.ToInt32(invalidStr);
    Console.WriteLine(result);
}
catch (FormatException ex)
{
    Console.WriteLine("输入的字符串不是有效的数字格式: " + ex.Message);
}

与其他类型转换方法的比较

Convert类的方法在进行类型转换时会尝试进行一些额外的处理,如将字符串转换为数字时会考虑不同的数字格式。相比之下,强制类型转换(如(int))只进行简单的二进制转换,不会进行格式验证。例如:

string str = "123";
int num1 = Convert.ToInt32(str); // 使用Convert类,会进行格式验证
double doubleNum = 3.14;
int num2 = (int)doubleNum; // 强制类型转换,直接截断小数部分

总之,Convert类提供了一种方便的方式来进行各种基本数据类型之间的转换,但在使用时要注意异常处理,以确保程序的健壮性。

3.5 try…catch

在C#中,try-catch 是一种异常处理机制,用于捕获和处理程序运行时可能出现的异常,从而避免程序因未处理的异常而崩溃。以下是关于 try-catch 的详细介绍:

基本语法

try
{
    // 可能会抛出异常的代码块
}
catch (ExceptionType1 ex1)
{
    // 处理 ExceptionType1 类型异常的代码块
}
catch (ExceptionType2 ex2)
{
    // 处理 ExceptionType2 类型异常的代码块
}
// 可以有多个 catch 块
finally
{
    // 无论是否发生异常,都会执行的代码块(可选)
}

详细解释

try
  • try 块中包含可能会抛出异常的代码。当程序执行到 try 块中的代码时,如果发生异常,程序会立即跳转到对应的 catch 块进行异常处理。
catch
  • catch 块用于捕获和处理特定类型的异常。每个 catch 块可以处理一种特定类型的异常,通过在 catch 关键字后面的括号中指定异常类型(如 ExceptionType1)来实现。
  • 当异常发生时,程序会按照 catch 块的顺序依次检查异常类型,找到匹配的 catch 块后,执行该 catch 块中的代码。
  • catch 块中,可以通过异常对象(如 ex1)来获取异常的详细信息,如异常消息、堆栈跟踪等。
finally 块(可选)
  • finally 块中的代码无论是否发生异常,都会在 try 块或 catch 块执行完毕后执行。
  • 通常用于释放资源,如关闭文件、数据库连接等,以确保资源的正确释放。

示例代码

示例1:处理单个异常类型
try
{
    int num1 = 10;
    int num2 = 0;
    int result = num1 / num2; // 这里会抛出 DivideByZeroException 异常
    Console.WriteLine(result);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("发生除零异常:" + ex.Message);
}
示例2:处理多个异常类型
try
{
    string str = "abc";
    int num = int.Parse(str); // 这里会抛出 FormatException 异常
    Console.WriteLine(num);
}
catch (FormatException ex)
{
    Console.WriteLine("字符串格式不正确:" + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("发生其他异常:" + ex.Message);
}
示例3:使用 finally
FileStream fs = null;
try
{
    fs = new FileStream("test.txt", FileMode.Open);
    // 读取文件内容的代码
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("文件未找到:" + ex.Message);
}
catch (IOException ex)
{
    Console.WriteLine("发生IO异常:" + ex.Message);
}
finally
{
    if (fs != null)
    {
        fs.Close(); // 确保文件流被关闭
    }
}

注意事项

  • 尽量捕获特定类型的异常,而不是直接捕获基类 Exception,这样可以更精确地处理不同类型的异常。
  • catch 块中,避免简单地捕获异常而不进行处理,应该根据异常的具体情况采取适当的措施,如记录日志、提示用户等。
  • finally 块中的代码应该尽量简洁,避免在其中抛出新的异常,以免掩盖原始异常。

3.6 MessageBox

在C#中,MessageBox 是一个用于显示消息框的静态类,它位于 System.Windows.Forms 命名空间下,常用于在Windows窗体应用程序(WinForm)中向用户显示提示信息、警告信息或获取用户的确认。以下是关于 MessageBox 的详细介绍:

基本语法

MessageBox 类最常用的方法是 Show 方法,它有多个重载形式,以下是一些常见的用法:

显示简单消息框
MessageBox.Show("这是一条消息");

此方法会显示一个包含指定消息的消息框,消息框只有一个“确定”按钮。

显示带有标题的消息框
MessageBox.Show("这是一条消息", "消息标题");

第二个参数用于指定消息框的标题。

显示带有自定义按钮的消息框
DialogResult result = MessageBox.Show("你确定要删除吗?", "确认删除", MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
    // 用户点击了“是”按钮,执行删除操作
}
else
{
    // 用户点击了“否”按钮,不执行删除操作
}

MessageBoxButtons 枚举用于指定消息框中显示的按钮类型,常见的有 MessageBoxButtons.OK(确定按钮)、MessageBoxButtons.YesNo(是/否按钮)、MessageBoxButtons.YesNoCancel(是/否/取消按钮)等。Show 方法会返回一个 DialogResult 枚举值,表示用户点击的按钮。

显示带有图标和自定义按钮的消息框
DialogResult result = MessageBox.Show("操作失败,请重试!", "错误提示", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
if (result == DialogResult.Retry)
{
    // 用户点击了“重试”按钮,重新执行操作
}
else
{
    // 用户点击了“取消”按钮,退出操作
}

MessageBoxIcon 枚举用于指定消息框中显示的图标类型,常见的有 MessageBoxIcon.Information(信息图标)、MessageBoxIcon.Warning(警告图标)、MessageBoxIcon.Error(错误图标)等。

常用的 MessageBoxButtons 枚举值

  • MessageBoxButtons.OK:显示一个“确定”按钮。
  • MessageBoxButtons.OKCancel:显示“确定”和“取消”按钮。
  • MessageBoxButtons.YesNo:显示“是”和“否”按钮。
  • MessageBoxButtons.YesNoCancel:显示“是”、“否”和“取消”按钮。
  • MessageBoxButtons.RetryCancel:显示“重试”和“取消”按钮。

常用的 MessageBoxIcon 枚举值

  • MessageBoxIcon.None:不显示图标。
  • MessageBoxIcon.Information:显示信息图标(一个蓝色的圆圈,里面有一个白色的字母“i”)。
  • MessageBoxIcon.Warning:显示警告图标(一个黄色的三角形,里面有一个黑色的感叹号)。
  • MessageBoxIcon.Error:显示错误图标(一个红色的圆圈,里面有一个白色的叉号)。
  • MessageBoxIcon.Question:显示问号图标(一个蓝色的圆圈,里面有一个白色的问号)。

示例代码

以下是一个完整的示例,演示了如何使用 MessageBox 显示不同类型的消息框:

using System;
using System.Windows.Forms;

namespace MessageBoxExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 显示简单消息框
            MessageBox.Show("这是一条简单的消息");

            // 显示带有标题的消息框
            MessageBox.Show("这是一条带有标题的消息", "消息标题");

            // 显示带有自定义按钮的消息框
            DialogResult result1 = MessageBox.Show("你确定要继续吗?", "确认操作", MessageBoxButtons.YesNo);
            if (result1 == DialogResult.Yes)
            {
                MessageBox.Show("你选择了继续。");
            }
            else
            {
                MessageBox.Show("你选择了取消。");
            }

            // 显示带有图标和自定义按钮的消息框
            DialogResult result2 = MessageBox.Show("操作失败,请重试!", "错误提示", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
            if (result2 == DialogResult.Retry)
            {
                MessageBox.Show("你选择了重试。");
            }
            else
            {
                MessageBox.Show("你选择了取消。");
            }
        }
    }
}

注意事项

  • MessageBox 是模态对话框,当消息框显示时,用户必须对其进行操作(如点击按钮)才能继续与应用程序进行交互。
  • MessageBox 主要用于简单的信息提示和用户确认,不适合用于复杂的用户交互场景。

3.7 Form_Load

在C#的Windows Forms应用程序中,Form_Load 是一个事件处理方法,它会在窗体加载时自动触发。以下是关于 Form_Load 的详细介绍:

事件的作用

Form_Load 事件通常用于在窗体显示之前执行一些初始化操作,例如:

  • 初始化窗体上的控件,如设置控件的初始值、文本、颜色等。
  • 加载数据到窗体上的控件,如从数据库中读取数据并显示在列表框、文本框等控件中。
  • 执行一些必要的计算或逻辑判断。

事件的声明和使用

自动生成的代码

当你在Visual Studio中创建一个新的Windows Forms应用程序并打开窗体设计器时,双击窗体空白处,Visual Studio会自动为你生成 Form_Load 事件处理方法的框架代码。例如,对于一个名为 Form1 的窗体,生成的代码如下:

using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 这里可以编写窗体加载时需要执行的代码
        }
    }
}
代码解释
  • Form1_Load 是事件处理方法的名称,其中 Form1 是窗体的名称,Load 是事件的名称。
  • object sender:表示触发事件的对象,在 Form_Load 事件中,sender 就是当前的窗体对象。
  • EventArgs e:包含事件的相关信息,对于 Load 事件,EventArgs 通常不包含额外的信息。
手动添加事件处理方法

如果你需要手动添加 Form_Load 事件处理方法,可以按照以下步骤操作:

  1. 打开窗体的代码文件(如 Form1.cs)。
  2. 在窗体的构造函数(如 public Form1())中添加以下代码来订阅 Load 事件:
public Form1()
{
    InitializeComponent();
    this.Load += new EventHandler(Form1_Load);
}
  1. 在代码文件中添加 Form1_Load 事件处理方法:
private void Form1_Load(object sender, EventArgs e)
{
    // 编写窗体加载时需要执行的代码
}

示例代码

以下是一个简单的示例,演示了在 Form_Load 事件中初始化窗体上的控件:

using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 初始化文本框的文本
            textBox1.Text = "初始文本";

            // 初始化列表框的数据
            listBox1.Items.Add("选项1");
            listBox1.Items.Add("选项2");
            listBox1.Items.Add("选项3");

            // 设置标签的字体和颜色
            label1.Font = new System.Drawing.Font("微软雅黑", 12, System.Drawing.FontStyle.Bold);
            label1.ForeColor = System.Drawing.Color.Red;
        }
    }
}

注意事项

  • Form_Load 事件中执行的代码应该尽量简洁,避免执行耗时过长的操作,以免影响窗体的加载速度,导致界面卡顿。如果需要执行耗时操作,可以考虑使用异步编程或者在后台线程中执行。
  • 确保在 Form_Load 事件中使用的控件已经在窗体设计器中正确添加和初始化,否则可能会引发 NullReferenceException 等异常。

3.8 new Form

在C#的Windows Forms应用程序中,new Form2() 是用于创建 Form2 窗体类的一个新实例的代码。以下是关于 new Form2() 的详细解释和相关用法:

1. 基本含义

Form2 通常是一个继承自 System.Windows.Forms.Form 的类,代表一个Windows窗体。new Form2() 会调用 Form2 类的构造函数,在内存中创建一个新的 Form2 窗体对象。

2. 常见用法

显示新窗体

创建 Form2 的实例后,通常会调用 Show()ShowDialog() 方法来显示该窗体。

  • Show() 方法:以非模态方式显示窗体,即显示新窗体后,用户仍然可以与原窗体进行交互。
// 创建 Form2 的新实例
Form2 form2 = new Form2();
// 以非模态方式显示 Form2
form2.Show();
  • ShowDialog() 方法:以模态方式显示窗体,即显示新窗体后,用户必须先关闭该窗体才能与原窗体进行交互。
// 创建 Form2 的新实例
Form2 form2 = new Form2();
// 以模态方式显示 Form2
DialogResult result = form2.ShowDialog();
if (result == DialogResult.OK)
{
    // 用户点击了 Form2 上的“确定”按钮(假设 Form2 上有“确定”按钮)
    // 可以在这里执行相应的操作
}
传递参数给新窗体

有时候需要在创建 Form2 实例时传递一些参数,以便 Form2 可以根据这些参数进行初始化或执行特定的操作。可以通过在 Form2 类中定义构造函数来实现参数的传递。

  • Form2 类中定义带参数的构造函数:
public partial class Form2 : Form
{
    private string _message;

    public Form2(string message)
    {
        InitializeComponent();
        _message = message;
        // 可以根据传递的参数进行初始化操作,例如设置窗体上控件的文本
        label1.Text = message;
    }
}
  • 在调用 new Form2() 时传递参数:
// 创建 Form2 的新实例并传递参数
Form2 form2 = new Form2("这是传递给 Form2 的消息");
form2.Show();
从新窗体获取返回值

如果需要从 Form2 窗体获取一些返回值,可以在 Form2 窗体中定义一个公共属性,然后在关闭 Form2 窗体之前设置该属性的值。

  • Form2 类中定义公共属性:
public partial class Form2 : Form
{
    public string ReturnValue { get; set; }

    public Form2()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        // 设置返回值
        ReturnValue = textBox1.Text;
        // 关闭窗体
        this.DialogResult = DialogResult.OK;
        this.Close();
    }
}
  • 在调用 ShowDialog() 方法后获取返回值:
// 创建 Form2 的新实例
Form2 form2 = new Form2();
// 以模态方式显示 Form2
DialogResult result = form2.ShowDialog();
if (result == DialogResult.OK)
{
    // 获取 Form2 的返回值
    string returnValue = form2.ReturnValue;
    // 可以在这里使用返回值进行相应的操作
    MessageBox.Show("从 Form2 获取的返回值是:" + returnValue);
}

3. 注意事项

  • 每次调用 new Form2() 都会创建一个新的 Form2 窗体实例,因此要注意内存的使用,避免创建过多的窗体实例导致内存泄漏。
  • 在使用 ShowDialog() 方法显示模态窗体时,要确保在合适的时机关闭窗体,否则会导致程序无法继续执行。

3.9 toString

在C#中,ToString() 是一个非常重要且常用的方法。以下是关于 ToString() 方法的详细介绍:

1. 基本概念

ToString() 方法用于将一个对象转换为其字符串表示形式。它是在 System.Object 类中定义的一个虚方法,这意味着所有的 C# 类型(因为所有类型都直接或间接继承自 Object 类)都可以调用该方法,并且可以在派生类中重写该方法以提供自定义的字符串表示。

2. 默认实现

对于大多数引用类型,如果没有重写 ToString() 方法,默认的实现会返回对象的类型名称。例如:

class MyClass
{
    // 没有重写 ToString() 方法
}

MyClass obj = new MyClass();
string str = obj.ToString(); // 返回 "MyClass"

对于值类型,如 intdouble 等,ToString() 方法会返回其数值的字符串表示。例如:

int num = 123;
string numStr = num.ToString(); // 返回 "123"

3. 重写 ToString() 方法

在自定义类中,可以重写 ToString() 方法以提供更有意义的字符串表示。例如:

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    // 重写 ToString() 方法
    public override string ToString()
    {
        return $"Name: {Name}, Age: {Age}";
    }
}

Person person = new Person { Name = "John", Age = 30 };
string personStr = person.ToString(); // 返回 "Name: John, Age: 30"

4. 格式化输出

ToString() 方法通常支持格式化字符串作为参数,用于控制输出的格式。不同类型的格式化选项不同。

数值类型的格式化

对于数值类型,如 intdouble 等,可以使用格式化字符串来指定小数位数、千位分隔符等。例如:

double num = 1234.5678;
string formattedStr1 = num.ToString("F2"); // 返回 "1234.57",保留两位小数
string formattedStr2 = num.ToString("N"); // 返回 "1,234.57",使用千位分隔符
日期时间类型的格式化

对于 DateTime 类型,可以使用格式化字符串来指定日期和时间的显示格式。例如:

DateTime now = DateTime.Now;
string dateStr1 = now.ToString("yyyy-MM-dd"); // 返回 "2024-01-01" (假设当前日期是2024年1月1日)
string dateStr2 = now.ToString("HH:mm:ss"); // 返回 "12:30:00" (假设当前时间是12:30:00)

5. 使用场景

  • 调试和日志记录:在调试代码或记录日志时,经常需要将对象的状态信息转换为字符串输出,方便查看和分析。
  • 显示信息:在用户界面中,需要将对象的信息以字符串形式显示给用户。
  • 数据转换:在进行数据存储或传输时,有时需要将对象转换为字符串格式。

6. 注意事项

  • 在重写 ToString() 方法时,要确保返回的字符串能够准确地反映对象的状态或信息,并且不会抛出异常。
  • 当使用格式化字符串时,要注意不同类型支持的格式化选项,避免使用不支持的格式导致异常。

3.10 this.Hide

在C#的Windows Forms应用程序中,this.Hide() 是一个用于隐藏当前窗体的方法。以下是关于 this.Hide() 的详细介绍:

1. 基本含义

this 关键字在窗体类的方法中通常指代当前窗体实例。Hide()System.Windows.Forms.Form 类的一个方法,调用 this.Hide() 会使当前窗体从屏幕上隐藏起来,但并不会关闭该窗体。

2. 常见用法

隐藏主窗体并显示其他窗体

在某些情况下,可能需要隐藏主窗体,同时显示另一个窗体。例如,在用户登录成功后,隐藏登录窗体,显示主应用程序窗体。

private void buttonLogin_Click(object sender, EventArgs e)
{
    // 假设这里进行了登录验证,验证通过后执行以下操作
    if (LoginValidation())
    {
        // 隐藏当前登录窗体
        this.Hide();

        // 创建并显示主应用程序窗体
        MainForm mainForm = new MainForm();
        mainForm.Show();
    }
    else
    {
        MessageBox.Show("登录失败,请检查用户名和密码。");
    }
}
实现窗体的最小化到系统托盘效果

在一些应用程序中,当用户点击最小化按钮时,希望将窗体隐藏到系统托盘,而不是最小化到任务栏。可以在窗体的 SizeChanged 事件中处理这种情况。

private void Form1_SizeChanged(object sender, EventArgs e)
{
    if (this.WindowState == FormWindowState.Minimized)
    {
        // 隐藏窗体
        this.Hide();

        // 显示系统托盘图标(需要提前创建 NotifyIcon 控件)
        notifyIcon1.Visible = true;
    }
}

private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
    // 当用户双击系统托盘图标时,显示窗体
    this.Show();
    this.WindowState = FormWindowState.Normal;
    notifyIcon1.Visible = false;
}

3. 与 Close() 方法的区别

  • Hide() 方法:只是将窗体隐藏起来,窗体对象仍然存在于内存中,窗体的状态和数据都不会丢失。可以通过调用 Show() 方法再次显示该窗体。
  • Close() 方法:会关闭当前窗体,并释放与该窗体相关的大部分资源。关闭后,窗体对象将被销毁,无法再通过 Show() 方法显示。如果是应用程序的主窗体调用 Close() 方法,通常会导致整个应用程序退出。

4. 注意事项

  • 当窗体隐藏后,虽然用户看不到该窗体,但它仍然在内存中占用一定的资源。因此,如果不再需要该窗体,应该调用 Close() 方法来释放资源。
  • 在隐藏窗体时,要注意处理好窗体上的控件和数据,确保在再次显示窗体时,这些控件和数据的状态仍然正确。

3.11 Application.Exit

在C#中,Application.Exit() 是一个用于终止当前应用程序的静态方法,它属于 System.Windows.Forms 命名空间下的 Application 类。以下是关于 Application.Exit() 的详细介绍:

1. 基本功能

Application.Exit() 方法用于关闭所有应用程序的消息循环,并退出应用程序。当调用这个方法时,应用程序会开始关闭过程,释放所有的资源并终止运行。

2. 常见使用场景

正常退出应用程序

在某些情况下,当用户执行特定操作后,需要正常退出应用程序。例如,在应用程序的主菜单中提供一个“退出”选项,点击该选项时调用 Application.Exit() 方法。

private void menuItemExit_Click(object sender, EventArgs e)
{
    // 提示用户确认是否退出
    DialogResult result = MessageBox.Show("确定要退出应用程序吗?", "确认退出", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
    if (result == DialogResult.Yes)
    {
        // 退出应用程序
        Application.Exit();
    }
}
满足特定条件时退出

在应用程序运行过程中,如果满足某些特定条件,也可以调用 Application.Exit() 方法退出应用程序。例如,在一个数据处理应用程序中,如果检测到关键数据文件丢失或损坏,可能需要退出应用程序。

if (!File.Exists("data.txt"))
{
    MessageBox.Show("关键数据文件丢失,应用程序将退出。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
    Application.Exit();
}

3. 与其他退出方式的比较

Environment.Exit() 的区别
  • Application.Exit():主要用于Windows Forms应用程序,它会先触发 Form.ClosingForm.Closed 事件,允许应用程序在退出前进行一些清理工作,如保存数据、释放资源等。然后关闭所有的窗体,停止消息循环,最终退出应用程序。
  • Environment.Exit():是一个更底层的退出方法,它会立即终止当前进程,不会触发任何应用程序级别的事件,也不会执行任何清理工作。因此,使用 Environment.Exit() 可能会导致一些资源没有被正确释放。
Form.Close() 的区别
  • Application.Exit():会关闭整个应用程序,包括所有打开的窗体。
  • Form.Close():只是关闭指定的窗体。如果关闭的是应用程序的主窗体,并且没有其他非模态窗体保持打开状态,应用程序通常会退出;但如果还有其他非模态窗体打开,应用程序不会退出。

4. 注意事项

  • 在调用 Application.Exit() 之前,最好先提示用户确认是否退出,以避免用户误操作导致数据丢失。
  • 确保在退出应用程序之前,所有的资源(如文件句柄、数据库连接等)都已经正确释放,以避免资源泄漏。
  • 如果应用程序中有后台线程在运行,调用 Application.Exit() 可能不会立即终止这些线程。在这种情况下,可能需要手动管理这些线程的生命周期,确保它们在应用程序退出前正常结束。

3.12 this.close

在C#的Windows Forms应用程序中,this.Close() 是一个用于关闭当前窗体的方法。以下是关于 this.Close() 的详细介绍:

1. 基本含义

this 关键字在窗体类的方法中通常指代当前窗体实例。Close()System.Windows.Forms.Form 类的一个方法,调用 this.Close() 会关闭当前窗体。

2. 工作原理

当调用 this.Close() 时,会触发一系列与窗体关闭相关的事件,主要包括:

  • FormClosing 事件:在窗体即将关闭时触发,可以在该事件的处理方法中进行一些验证或清理工作,甚至可以取消窗体的关闭操作。
  • FormClosed 事件:在窗体关闭后触发,通常用于执行一些最终的清理工作。

3. 常见用法

关闭当前窗体

在窗体上的某个按钮的点击事件中,调用 this.Close() 可以关闭当前窗体。例如:

private void buttonClose_Click(object sender, EventArgs e)
{
    this.Close();
}
FormClosing 事件中进行验证

在某些情况下,可能需要在关闭窗体之前进行一些验证,例如检查用户是否保存了数据。如果用户没有保存数据,可以提示用户是否保存或取消关闭操作。

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if (IsDataModified && !IsDataSaved)
    {
        DialogResult result = MessageBox.Show("数据已修改,是否保存?", "提示", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
        if (result == DialogResult.Yes)
        {
            // 保存数据的代码
            SaveData();
        }
        else if (result == DialogResult.Cancel)
        {
            // 取消关闭操作
            e.Cancel = true;
        }
    }
}

4. 与 Application.Exit() 的区别

  • this.Close():只是关闭当前窗体。如果关闭的是应用程序的主窗体,并且没有其他非模态窗体保持打开状态,应用程序通常会退出;但如果还有其他非模态窗体打开,应用程序不会退出。
  • Application.Exit():会关闭整个应用程序,包括所有打开的窗体。

5. 与 this.Hide() 的区别

  • this.Close():会关闭当前窗体,并释放与该窗体相关的大部分资源。关闭后,窗体对象将被销毁,无法再通过 Show() 方法显示。
  • this.Hide():只是将窗体隐藏起来,窗体对象仍然存在于内存中,窗体的状态和数据都不会丢失。可以通过调用 Show() 方法再次显示该窗体。

6. 注意事项

  • 在调用 this.Close() 之前,要确保已经处理好窗体上的数据和资源,避免数据丢失或资源泄漏。
  • 如果在 FormClosing 事件中取消了窗体的关闭操作(通过设置 e.Cancel = true),要注意给用户明确的提示,告知用户操作被取消的原因。

3.13 this.load

在C#的Windows Forms应用程序中,this.Load += Form4_Load; 这行代码的作用是手动订阅当前窗体(this 所指代的窗体)的 Load 事件,并将 Form4_Load 方法指定为该事件的处理程序。以下是详细解释:

1. 事件订阅的基本原理

在C#中,事件是一种特殊的委托,用于在特定的操作发生时通知订阅者。Load 事件是 System.Windows.Forms.Form 类的一个事件,当窗体加载时会触发该事件。+= 运算符用于将一个事件处理方法(如 Form4_Load)添加到事件的委托列表中,这样当事件触发时,所有订阅的方法都会被依次调用。

2. 代码示例及解释

示例代码
using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
    public partial class Form4 : Form
    {
        public Form4()
        {
            InitializeComponent();
            // 手动订阅 Load 事件
            this.Load += Form4_Load;
        }

        // Load 事件的处理方法
        private void Form4_Load(object sender, EventArgs e)
        {
            // 窗体加载时执行的代码
            MessageBox.Show("Form4 已加载");
        }
    }
}
代码解释
  • public Form4() 构造函数:在窗体的构造函数中,InitializeComponent() 方法用于初始化窗体上的所有控件。this.Load += Form4_Load; 这行代码将 Form4_Load 方法添加到 Load 事件的委托列表中。
  • private void Form4_Load(object sender, EventArgs e) 方法:这是 Load 事件的处理方法。当窗体加载时,该方法会被调用。sender 参数表示触发事件的对象(在这里就是当前的 Form4 窗体),EventArgs e 包含事件的相关信息(对于 Load 事件,通常不需要使用 e 中的信息)。在这个方法中,使用 MessageBox.Show("Form4 已加载"); 显示一个消息框,表示窗体已经加载。

3. 自动生成与手动订阅的区别

通常,在Visual Studio的设计器中,你可以通过双击窗体来自动生成 Load 事件的处理方法,此时Visual Studio会自动为你添加事件订阅的代码。手动订阅事件(如 this.Load += Form4_Load;)和自动生成的效果是一样的,但手动订阅可以让你更清楚地控制事件的订阅过程,尤其是在一些复杂的场景中。

4. 注意事项

  • 确保 Form4_Load 方法的签名(参数类型和返回类型)与 Load 事件的委托类型 EventHandler 匹配,即 void 方法名(object sender, EventArgs e)
  • 如果在多个地方订阅了同一个事件,要注意事件处理方法的执行顺序和可能的冲突。例如,如果在不同的地方对同一个控件的状态进行初始化,可能会导致意外的结果。

3.14 timer

在C#的Windows Forms应用程序中,timer1.Tick += timer1_Tick; 这行代码的作用是为 Timer 控件的 Tick 事件添加一个事件处理程序 timer1_Tick。以下是详细解释:

1. Timer 控件和 Tick 事件

Timer 控件是Windows Forms中用于定时执行任务的一个重要控件。它会按照指定的时间间隔(通过 Interval 属性设置,单位为毫秒)周期性地触发 Tick 事件。每当 Timer 控件的时间间隔过去后,Tick 事件就会被触发一次。

2. 事件订阅的原理

在C#中,事件本质上是一种特殊的委托。Tick 事件是 System.Windows.Forms.Timer 类的一个事件,它的类型是 EventHandler+= 运算符用于将一个事件处理方法(如 timer1_Tick)添加到 Tick 事件的委托列表中。这样,当 Tick 事件触发时,所有订阅的方法都会被依次调用。

3. 代码示例及解释

示例代码
using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            // 订阅 Timer 控件的 Tick 事件
            timer1.Tick += timer1_Tick;
            // 设置 Timer 控件的时间间隔为 1000 毫秒(即 1 秒)
            timer1.Interval = 1000;
            // 启动 Timer 控件
            timer1.Start();
        }

        private int counter = 0;

        // Tick 事件的处理方法
        private void timer1_Tick(object sender, EventArgs e)
        {
            // 每次 Tick 事件触发时,计数器加 1
            counter++;
            // 更新标签的文本,显示计数器的值
            label1.Text = "已过去 " + counter + " 秒";
        }
    }
}
代码解释
  • public Form1() 构造函数
    • InitializeComponent() 方法用于初始化窗体上的所有控件。
    • timer1.Tick += timer1_Tick; 这行代码将 timer1_Tick 方法添加到 timer1 控件的 Tick 事件的委托列表中。
    • timer1.Interval = 1000; 设置 timer1 控件的时间间隔为1000毫秒(即1秒)。
    • timer1.Start(); 启动 timer1 控件,使其开始计时。
  • private void timer1_Tick(object sender, EventArgs e) 方法
    • 这是 Tick 事件的处理方法。当 timer1 控件的时间间隔过去后,该方法会被调用。
    • sender 参数表示触发事件的对象(在这里就是 timer1 控件),EventArgs e 包含事件的相关信息(对于 Tick 事件,通常不需要使用 e 中的信息)。
    • 在这个方法中,每次事件触发时,counter 变量加1,并将计数器的值显示在 label1 标签上。

4. 注意事项

  • 确保 timer1_Tick 方法的签名(参数类型和返回类型)与 Tick 事件的委托类型 EventHandler 匹配,即 void 方法名(object sender, EventArgs e)
  • 如果 Timer 控件的 Interval 属性设置得太小,可能会导致CPU占用率过高,影响程序性能。
  • 在不需要使用 Timer 控件时,应该调用 timer1.Stop(); 方法停止计时,以释放系统资源。

3.15 DataTime

你可能想要表达的是 DateTime.Now 或者 new DateTime(...) ,以下是关于这两者的详细介绍:

1. DateTime.Now

含义

DateTime.NowDateTime 结构的一个静态属性,用于获取当前的日期和时间,返回的是一个 DateTime 类型的对象,表示当前系统的本地日期和时间。

示例代码
using System;

class Program
{
    static void Main()
    {
        // 获取当前的日期和时间
        DateTime currentDateTime = DateTime.Now;

        // 输出当前日期和时间
        Console.WriteLine("当前日期和时间: " + currentDateTime);

        // 获取当前日期和时间的各个部分
        int year = currentDateTime.Year;
        int month = currentDateTime.Month;
        int day = currentDateTime.Day;
        int hour = currentDateTime.Hour;
        int minute = currentDateTime.Minute;
        int second = currentDateTime.Second;

        Console.WriteLine($"年份: {year}, 月份: {month}, 日期: {day}");
        Console.WriteLine($"小时: {hour}, 分钟: {minute}, 秒: {second}");
    }
}
输出示例
当前日期和时间: 2024-01-01 12:30:45
年份: 2024, 月份: 1, 日期: 1
小时: 12, 分钟: 30, 秒: 45

2. new DateTime(...)

含义

DateTime 是一个结构类型,也可以使用 new 关键字来创建一个 DateTime 对象,并通过指定的参数来初始化日期和时间。DateTime 有多个构造函数重载,可以根据不同的需求来创建日期和时间对象。

常用构造函数重载
  • DateTime(int year, int month, int day):创建一个指定年、月、日的日期对象,时间部分默认为 00:00:00
  • DateTime(int year, int month, int day, int hour, int minute, int second):创建一个指定年、月、日、时、分、秒的日期和时间对象。
示例代码
using System;

class Program
{
    static void Main()
    {
        // 创建一个指定日期的 DateTime 对象
        DateTime specificDate = new DateTime(2024, 1, 1);
        Console.WriteLine("指定日期: " + specificDate);

        // 创建一个指定日期和时间的 DateTime 对象
        DateTime specificDateTime = new DateTime(2024, 1, 1, 12, 30, 45);
        Console.WriteLine("指定日期和时间: " + specificDateTime);
    }
}
输出示例
指定日期: 2024-01-01 00:00:00
指定日期和时间: 2024-01-01 12:30:45

注意事项

  • DateTime.Now 获取的是当前系统的本地日期和时间,它会随着系统时间的变化而变化。
  • 使用 new DateTime(...) 创建的日期和时间对象是固定的,不会自动更新。
  • 在处理日期和时间时,要注意时区的问题,DateTime.Now 返回的是本地时间,如果需要处理不同时区的时间,可以考虑使用 DateTimeOffsetTimeZoneInfo 类。

3.16 Enum.Parse

在C#中,Enum.Parse 是一个用于将字符串表示形式转换为枚举类型值的静态方法。以下是关于 Enum.Parse 的详细介绍:

方法签名

Enum.Parse 方法有多个重载形式,最常用的重载形式的签名如下:

public static object Parse(Type enumType, string value);
public static object Parse(Type enumType, string value, bool ignoreCase);

参数说明

  • enumType:要将字符串转换为的枚举类型。这是一个 System.Type 对象,通常使用 typeof(EnumType) 来获取,其中 EnumType 是具体的枚举类型名称。
  • value:要转换的字符串,表示枚举类型中的一个枚举成员的名称或数值。
  • ignoreCase(可选):一个布尔值,指示在转换过程中是否忽略字符串的大小写。如果为 true,则忽略大小写;如果为 false,则区分大小写。默认值为 false

返回值

Enum.Parse 方法返回一个 object 类型的对象,需要将其显式转换为具体的枚举类型。

示例代码

假设有一个枚举类型 DaysOfWeek 定义如下:

enum DaysOfWeek
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}
基本用法
// 将字符串 "Tuesday" 转换为 DaysOfWeek 枚举类型的值
DaysOfWeek day = (DaysOfWeek)Enum.Parse(typeof(DaysOfWeek), "Tuesday");
Console.WriteLine(day); // 输出: Tuesday
忽略大小写
// 忽略字符串的大小写进行转换
DaysOfWeek day2 = (DaysOfWeek)Enum.Parse(typeof(DaysOfWeek), "tuesday", true);
Console.WriteLine(day2); // 输出: Tuesday
处理可能的异常
try
{
    // 尝试将字符串 "InvalidDay" 转换为 DaysOfWeek 枚举类型的值
    DaysOfWeek day3 = (DaysOfWeek)Enum.Parse(typeof(DaysOfWeek), "InvalidDay");
    Console.WriteLine(day3);
}
catch (ArgumentException ex)
{
    Console.WriteLine("转换失败: " + ex.Message); // 输出: 转换失败: 请求的值 'InvalidDay' 不是有效的枚举值。
}

注意事项

  • 如果 value 参数不是枚举类型中定义的有效成员名称或数值,Enum.Parse 方法会抛出 ArgumentException 异常。因此,在使用 Enum.Parse 方法时,建议进行异常处理,以避免程序因异常而崩溃。
  • Enum.Parse 方法返回的是 object 类型,需要进行显式类型转换才能得到具体的枚举类型值。
  • 从 .NET 6 开始,推荐使用 Enum.TryParse 方法,因为它可以避免抛出异常,而是返回一个布尔值表示转换是否成功。例如:
if (Enum.TryParse(typeof(DaysOfWeek), "Tuesday", true, out object result))
{
    DaysOfWeek day4 = (DaysOfWeek)result;
    Console.WriteLine(day4); // 输出: Tuesday
}
else
{
    Console.WriteLine("转换失败");
}

3.17 ListViewItem

在C#的Windows Forms应用程序中,ListViewItem 类用于表示 ListView 控件中的一个项。ListView 是一个非常强大的控件,可用于显示列表、详细信息等多种视图的项目集合,而 ListViewItem 则是这些集合中的单个项目。以下是关于 ListViewItem 的详细介绍:

1. 基本概念

ListViewItem 可以包含一个或多个子项(ListViewSubItem),每个子项可以显示不同的文本信息。例如,在一个显示文件列表的 ListView 中,每个 ListViewItem 可以表示一个文件,而子项可以分别表示文件名、文件大小、修改日期等信息。

2. 常用属性

Text 属性
  • 作用:用于获取或设置 ListViewItem 的主文本,即第一列显示的文本。
  • 示例代码
ListViewItem item = new ListViewItem("文件1");
Console.WriteLine(item.Text); // 输出: 文件1
SubItems 属性
  • 作用:用于获取 ListViewItem 的子项集合,通过该集合可以添加、删除或修改子项。
  • 示例代码
ListViewItem item = new ListViewItem("文件1");
item.SubItems.Add("100KB"); // 添加一个子项表示文件大小
item.SubItems.Add("2024-01-01"); // 添加一个子项表示修改日期
ImageIndex 属性
  • 作用:用于获取或设置 ListViewItem 显示的图像的索引。当 ListView 控件的 SmallImageListLargeImageList 属性设置了图像列表后,可以通过 ImageIndex 属性指定每个 ListViewItem 显示的图像。
  • 示例代码
ListViewItem item = new ListViewItem("文件1");
item.ImageIndex = 0; // 设置显示图像列表中的第一个图像

3. 使用方法

在设计器中使用 ListViewItem
  • 打开Visual Studio的Windows Forms设计器。
  • 从工具箱中找到 ListView 控件,将其拖动到窗体上。
  • ListView 控件的属性窗口中,设置 View 属性为合适的视图模式(如 Details)。
  • ListView 控件的 Columns 集合中添加所需的列。
  • 在代码中创建 ListViewItem 对象,并添加到 ListView 控件的 Items 集合中。
示例代码
using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // 设置 ListView 的视图模式为 Details
            listView1.View = View.Details;

            // 添加列
            listView1.Columns.Add("文件名", 150);
            listView1.Columns.Add("文件大小", 100);
            listView1.Columns.Add("修改日期", 150);

            // 创建 ListViewItem 对象并添加子项
            ListViewItem item1 = new ListViewItem("文件1");
            item1.SubItems.Add("100KB");
            item1.SubItems.Add("2024-01-01");

            ListViewItem item2 = new ListViewItem("文件2");
            item2.SubItems.Add("200KB");
            item2.SubItems.Add("2024-01-02");

            // 将 ListViewItem 对象添加到 ListView 控件中
            listView1.Items.Add(item1);
            listView1.Items.Add(item2);
        }
    }
}

4. 注意事项

  • ListView 控件的视图模式为 Details 时,需要先添加列,然后才能正确显示 ListViewItem 的子项。
  • 在添加 ListViewItem 时,要确保子项的数量与列的数量一致,否则可能会导致显示异常。
  • 可以通过 ListViewItemTag 属性存储与该项相关的额外信息,例如对象引用、ID 等,方便在事件处理中使用。

3.18 Invoke

在C#中,Invoke(new Action(...)) 常用于在多线程编程中实现线程安全的控件访问。这是因为在Windows Forms或WPF等应用程序中,控件通常是在创建它们的线程(通常是主线程,也称为UI线程)上创建和操作的。如果尝试从其他线程直接访问或修改这些控件,就会引发 InvalidOperationException 异常,提示“跨线程操作无效: 从不是创建控件的线程访问它”。Invoke 方法可以帮助我们解决这个问题。

基本原理

Control.Invoke 方法用于在拥有此控件的基础窗口句柄的线程上执行指定的委托。Action 是一个委托类型,表示一个没有返回值的方法。Invoke(new Action(...)) 就是将一个 Action 委托传递给 Invoke 方法,让这个委托在UI线程上执行。

示例代码

以下是一个简单的Windows Forms应用程序示例,演示了如何使用 Invoke(new Action(...))

using System;
using System.Threading;
using System.Windows.Forms;

namespace InvokeExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 启动一个新线程来模拟耗时操作
            Thread thread = new Thread(DoWork);
            thread.Start();
        }

        private void DoWork()
        {
            // 模拟耗时操作
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(1000); // 暂停1秒

                // 由于是在新线程中,不能直接访问UI控件,需要使用Invoke
                this.Invoke(new Action(() =>
                {
                    label1.Text = $"已过去 {i + 1} 秒";
                }));
            }
        }
    }
}

代码解释

  1. button1_Click 方法:当用户点击按钮时,启动一个新线程 thread 并调用 DoWork 方法。
  2. DoWork 方法:在新线程中模拟一个耗时操作,使用 Thread.Sleep(1000) 暂停1秒。由于不能直接从新线程访问 label1 控件,所以使用 this.Invoke(new Action(() => {... })) 来更新 label1Text 属性。Action 委托中的代码会在UI线程上执行,从而确保线程安全。

注意事项

  • 频繁调用 Invoke 方法可能会影响性能,因为每次调用都会涉及到线程间的上下文切换。如果需要频繁更新UI,可以考虑使用 BeginInvoke 方法(异步调用)或 BackgroundWorker 组件。
  • 在使用 Invoke 方法时,要确保传递的 Action 委托中的代码不会引发异常,否则可能会导致应用程序崩溃。
  • 从.NET Framework 4.5开始,可以使用 InvokeIfRequired 扩展方法来简化代码,判断是否需要调用 Invoke
public static void InvokeIfRequired(this Control control, Action action)
{
    if (control.InvokeRequired)
    {
        control.Invoke(new Action(() => InvokeIfRequired(control, action)));
    }
    else
    {
        action();
    }
}

使用方法如下:

private void DoWork()
{
    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(1000);
        label1.InvokeIfRequired(() => label1.Text = $"已过去 {i + 1} 秒");
    }
}

这样可以避免每次都手动判断 InvokeRequired

4 变量与数据类型

4.1 变量

在C#中,变量是用于存储数据的命名内存位置,而基本数据类型则定义了变量可以存储的数据的种类。以下是关于C#变量与基本数据类型的详细介绍:

变量的声明与初始化

声明变量

在C#中,声明变量需要指定变量的类型和名称。语法如下:

<数据类型> <变量名>;

例如:

int age;
string name;
double salary;
初始化变量

变量声明后,可以通过赋值语句对其进行初始化。例如:

age = 25;
name = "John";
salary = 5000.50;

也可以在声明变量的同时进行初始化:

int age = 25;
string name = "John";
double salary = 5000.50;

4.2 数据类型

基本数据类型

整数类型
  • int(System.Int32):用于表示32位有符号整数,取值范围大约是 -2,147,483,648 到 2,147,483,647。这是最常用的整数类型。
    int number = 10;
    
  • short(System.Int16):16位有符号整数,取值范围是 -32,768 到 32,767。
    short smallNumber = 100;
    
  • long(System.Int64):64位有符号整数,取值范围非常大,大约是 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。在处理大整数时使用。
    long bigNumber = 100000000000L; // 注意后缀L表示这是一个long类型的常量
    
  • byte(System.Byte):8位无符号整数,取值范围是 0 到 255,常用于处理二进制数据。
    byte binaryData = 200;
    
浮点类型
  • float(System.Single):32位单精度浮点数,用于表示小数。在声明float类型的变量或常量时,需要在数字后面加上fF后缀。
    float pi = 3.14f;
    
  • double(System.Double):64位双精度浮点数,是默认的小数类型,精度比float更高。
    double morePrecisePi = 3.14159265358979;
    
字符类型
  • char(System.Char):用于表示单个字符,用单引号括起来。char类型实际上是一个16位的Unicode字符。
    char letter = 'A';
    
布尔类型
  • bool(System.Boolean):用于表示布尔值,只有两个可能的值:truefalse,常用于条件判断。
    bool isTrue = true;
    bool isFalse = false;
    
字符串类型
  • string(System.String):用于表示文本,是一个不可变的字符序列,用双引号括起来。
    string message = "Hello, World!";
    

类型转换

隐式类型转换

当把一个较小范围的数据类型赋值给一个较大范围的数据类型时,会自动进行隐式类型转换。例如:

int num = 10;
long bigNum = num; // 隐式类型转换,int可以自动转换为long
显式类型转换

当把一个较大范围的数据类型赋值给一个较小范围的数据类型时,需要进行显式类型转换,使用强制类型转换运算符()。例如:

long bigNum = 1000;
int num = (int)bigNum; // 显式类型转换,需要注意可能会发生数据丢失

常量

在C#中,使用const关键字来声明常量。常量的值在声明时必须初始化,并且在程序运行期间不能更改。例如:

const int MAX_VALUE = 100;

总之,C#提供了丰富的基本数据类型来满足不同的编程需求,同时通过变量和常量来存储和管理数据。在使用变量和数据类型时,需要注意类型的选择和类型转换的问题,以确保程序的正确性和性能。

4.3 字符串+

在C#中,+ 运算符用于连接两个字符串,这种操作被称为字符串拼接。以下是关于C#中使用 + 连接两个字符串的详细介绍:

基本用法

连接字符串字面量

可以直接使用 + 运算符将两个字符串字面量连接起来。例如:

string str1 = "Hello";
string str2 = " World";
string result = str1 + str2;
Console.WriteLine(result); // 输出: Hello World
连接字符串变量和字面量

也可以将字符串变量和字符串字面量混合使用进行连接。例如:

string name = "John";
string greeting = "Hello, " + name + "!";
Console.WriteLine(greeting); // 输出: Hello, John!

注意事项

类型转换

当使用 + 运算符连接字符串和其他数据类型时,其他数据类型会自动转换为字符串类型。例如:

int age = 25;
string message = "My age is " + age;
Console.WriteLine(message); // 输出: My age is 25

在这个例子中,整数 age 会自动转换为字符串,然后与其他字符串进行连接。

性能考虑

在循环中多次使用 + 运算符进行字符串连接时,会产生性能问题。因为每次使用 + 运算符进行字符串连接时,都会创建一个新的字符串对象,这会导致频繁的内存分配和垃圾回收。在这种情况下,建议使用 System.Text.StringBuilder 类来进行字符串的拼接,它的性能更高。例如:

// 使用 + 运算符在循环中拼接字符串
string result = "";
for (int i = 0; i < 1000; i++)
{
    result += i.ToString();
}

// 使用 StringBuilder 类在循环中拼接字符串
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i);
}
string result2 = sb.ToString();

在上述示例中,使用 StringBuilderAppend 方法进行字符串拼接的性能要比使用 + 运算符高得多,尤其是在循环次数较多的情况下。

空字符串的处理

当连接的字符串中有一个或多个为空字符串时,空字符串会被视为普通字符串进行连接。例如:

string str1 = "Hello";
string str2 = "";
string result = str1 + str2;
Console.WriteLine(result); // 输出: Hello

总之,在C#中使用 + 运算符可以方便地连接两个字符串,但在处理大量字符串拼接时,要注意性能问题。

4.4 List

在C#中,List<T> 是一个非常常用的泛型集合类,它位于 System.Collections.Generic 命名空间下。List<T> 可以动态地添加、删除和访问元素,提供了高效的随机访问能力,以下是关于它的详细介绍:

1. 定义和初始化

List<T> 是一个泛型类,T 表示列表中元素的类型。你可以使用以下几种方式来定义和初始化一个 List<T>

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // 方式一:初始化一个空的列表
        List<int> numbers1 = new List<int>();

        // 方式二:使用集合初始化器初始化列表
        List<int> numbers2 = new List<int> { 1, 2, 3, 4, 5 };

        // 方式三:从数组初始化列表
        int[] array = { 6, 7, 8, 9, 10 };
        List<int> numbers3 = new List<int>(array);
    }
}

2. 常用属性和方法

属性
  • Count:获取列表中实际包含的元素数量。
List<int> numbers = new List<int> { 1, 2, 3 };
int count = numbers.Count; // count 的值为 3
方法
添加元素
  • Add(T item):在列表的末尾添加一个元素。
List<int> numbers = new List<int>();
numbers.Add(10);
  • AddRange(IEnumerable<T> collection):将指定集合中的元素添加到列表的末尾。
List<int> numbers = new List<int> { 1, 2, 3 };
int[] array = { 4, 5, 6 };
numbers.AddRange(array);
插入元素
  • Insert(int index, T item):在列表的指定索引处插入一个元素。
List<int> numbers = new List<int> { 1, 2, 3 };
numbers.Insert(1, 10); // 列表变为 { 1, 10, 2, 3 }
删除元素
  • Remove(T item):从列表中移除指定元素的第一个匹配项。
List<int> numbers = new List<int> { 1, 2, 3, 2 };
numbers.Remove(2); // 列表变为 { 1, 3, 2 }
  • RemoveAt(int index):移除列表中指定索引处的元素。
List<int> numbers = new List<int> { 1, 2, 3 };
numbers.RemoveAt(1); // 列表变为 { 1, 3 }
  • RemoveRange(int index, int count):从列表中移除一定范围的元素。
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
numbers.RemoveRange(1, 3); // 列表变为 { 1, 5 }
查找元素
  • Contains(T item):确定列表中是否包含指定元素。
List<int> numbers = new List<int> { 1, 2, 3 };
bool containsTwo = numbers.Contains(2); // containsTwo 的值为 true
  • IndexOf(T item):返回指定元素在列表中第一次出现的索引,如果未找到则返回 -1。
List<int> numbers = new List<int> { 1, 2, 3, 2 };
int index = numbers.IndexOf(2); // index 的值为 1
排序
  • Sort():对列表中的元素进行排序,元素类型必须实现 IComparable<T> 接口。
List<int> numbers = new List<int> { 3, 1, 2 };
numbers.Sort(); // 列表变为 { 1, 2, 3 }

3. 遍历 List<T>

可以使用 foreach 循环或传统的 for 循环来遍历 List<T> 中的元素:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// 使用 foreach 循环
foreach (int number in numbers)
{
    Console.WriteLine(number);
}

// 使用 for 循环
for (int i = 0; i < numbers.Count; i++)
{
    Console.WriteLine(numbers[i]);
}

4. 注意事项

  • 性能方面List<T> 支持随机访问,通过索引访问元素的时间复杂度是 O ( 1 ) O(1) O(1)。但在列表中间插入或删除元素时,可能需要移动后续元素,时间复杂度为 O ( n ) O(n) O(n)
  • 泛型约束List<T> 是泛型集合,要求存储的元素类型一致。如果需要存储不同类型的元素,可以考虑使用 ArrayList(非泛型集合),但使用 ArrayList 时需要进行类型转换,可能会引发运行时错误。

5 WinForm

在C#中,Windows 窗体(WinForm)是一种用于创建桌面应用程序用户界面的技术。它是.NET Framework的一部分,提供了丰富的控件和工具,使开发者能够轻松地构建具有图形用户界面(GUI)的应用程序。以下是关于C#中WinForm的详细介绍:

1. 基本概念

窗体(Form)
  • 窗体是WinForm应用程序的主要容器,类似于窗口。每个WinForm应用程序至少包含一个主窗体。可以在窗体上添加各种控件,如按钮、文本框、标签等,来实现与用户的交互。
控件(Control)
  • 控件是WinForm中用于显示信息或接收用户输入的组件。例如,Button控件用于触发操作,TextBox控件用于输入文本,Label控件用于显示文本信息等。

2. 创建WinForm应用程序

使用Visual Studio创建项目
  • 打开Visual Studio,选择“创建新项目”。
  • 在模板列表中,选择“Windows 窗体应用(.NET Framework)”或“Windows 窗体应用(.NET Core)”(根据需要选择合适的.NET版本)。
  • 输入项目名称和位置,然后点击“创建”按钮。Visual Studio会自动生成一个包含主窗体的WinForm项目。

3. 窗体设计

设计视图
  • 在Visual Studio的解决方案资源管理器中,双击Form1.cs(默认的主窗体文件),会打开窗体设计器。在设计器中,可以通过拖放控件的方式来设计窗体的布局。
属性窗口
  • 选中一个控件后,在属性窗口中可以设置控件的各种属性,如大小、位置、颜色、字体等。例如,要更改按钮的文本,可以在属性窗口中找到Text属性并进行修改。

4. 事件处理

事件驱动编程
  • WinForm应用程序是基于事件驱动的,即当用户执行某个操作(如点击按钮、输入文本等)时,会触发相应的事件,开发者可以编写代码来处理这些事件。
事件处理示例
  • 以下是一个简单的示例,当用户点击按钮时,在消息框中显示一条信息:
using System;
using System.Windows.Forms;

namespace WinFormExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("你点击了按钮!");
        }
    }
}
  • 在这个示例中,button1_Click方法是按钮的点击事件处理程序。当用户点击按钮时,会自动调用这个方法,显示一个消息框。

5. 常用控件及用法

1. 按钮(Button)
  • 用途:用于触发特定的操作或事件,当用户点击按钮时,可以执行相应的代码逻辑。
  • 示例代码
private void button1_Click(object sender, EventArgs e)
{
    MessageBox.Show("按钮被点击了!");
}
  • 使用场景:在登录界面中,“登录”按钮用于验证用户输入的用户名和密码;在文件操作界面中,“保存”按钮用于保存用户编辑的文件内容。
2. 标签(Label)
  • 用途:用于显示文本信息,通常作为提示或说明文字,不能接收用户输入。
  • 示例代码
label1.Text = "请输入用户名:";
  • 使用场景:在表单中,用于提示用户输入信息;在显示数据的界面中,用于标注数据的含义。
3. 文本框(TextBox)
  • 用途:用于接收用户输入的文本信息,可以是单行或多行输入。
  • 示例代码
string userInput = textBox1.Text;
  • 使用场景:在登录界面中,用于输入用户名和密码;在搜索框中,用于输入搜索关键词。
4. 复选框(CheckBox)
  • 用途:用于提供一个或多个选项供用户选择,用户可以选择多个复选框。
  • 示例代码
if (checkBox1.Checked)
{
    MessageBox.Show("复选框被选中了!");
}
  • 使用场景:在设置界面中,用于选择多个功能选项;在调查问卷中,用于选择多个答案。
5. 单选按钮(RadioButton)
  • 用途:用于提供一组互斥的选项供用户选择,用户只能选择其中一个。
  • 示例代码
if (radioButton1.Checked)
{
    MessageBox.Show("选项1被选中了!");
}
  • 使用场景:在性别选择中,用户只能选择“男”或“女”;在答题界面中,用户只能选择一个正确答案。
6. 列表框(ListBox)
  • 用途:用于显示一个列表,用户可以选择其中的一个或多个项目。
  • 示例代码
listBox1.Items.Add("选项1");
listBox1.Items.Add("选项2");
string selectedItem = listBox1.SelectedItem.ToString();
  • 使用场景:在文件选择界面中,显示文件列表供用户选择;在商品列表界面中,显示商品信息供用户选择。
7. 组合框(ComboBox)
  • 用途:类似于下拉列表,用户可以从预定义的选项中选择一个值,也可以手动输入文本。
  • 示例代码
comboBox1.Items.Add("选项1");
comboBox1.Items.Add("选项2");
string selectedValue = comboBox1.SelectedValue.ToString();
  • 使用场景:在选择城市的界面中,用户可以从下拉列表中选择城市名称;在选择日期的界面中,用户可以从下拉列表中选择日期。
8. 图片框(PictureBox)
  • 用途:用于显示图片,可以加载本地图片或从网络获取图片。
  • 示例代码
pictureBox1.Image = Image.FromFile("image.jpg");
  • 使用场景:在用户头像显示界面中,显示用户的头像图片;在产品展示界面中,显示产品的图片。
9. 进度条(ProgressBar)
  • 用途:用于显示任务的进度,通常用于长时间运行的任务,如文件下载、数据处理等。
  • 示例代码
progressBar1.Value = 50; // 设置进度为50%
  • 使用场景:在文件下载界面中,显示文件的下载进度;在数据备份界面中,显示数据备份的进度。
10. 数据网格视图(DataGridView)
  • 用途:用于显示和编辑表格数据,支持数据的排序、筛选和编辑。
  • 示例代码
DataTable dataTable = new DataTable();
dataTable.Columns.Add("姓名");
dataTable.Columns.Add("年龄");
dataTable.Rows.Add("张三", 20);
dataGridView1.DataSource = dataTable;
  • 使用场景:在数据管理界面中,显示和编辑数据库中的数据;在报表界面中,显示统计数据。

以上是WinForm中一些常用的控件,通过组合使用这些控件,可以创建出各种功能丰富的桌面应用程序。

11. GroupBOx

在C#的Windows Forms应用程序中,GroupBox 是一个常用的容器控件,用于将一组相关的控件进行分组,使界面更加清晰和有条理。以下是关于 GroupBox 的详细介绍:

  1. 基本功能
    GroupBox 控件在窗体上显示一个带有边框和标题的矩形区域,可以将其他控件(如按钮、文本框、复选框等)放置在该区域内,从而在视觉上对这些控件进行分组。用户可以通过 GroupBox 的标题来识别该组控件的功能或用途。

  2. 常用属性

Text 属性

  • 作用:用于设置 GroupBox 的标题文本。
  • 示例代码
groupBox1.Text = "用户信息";

Enabled 属性

  • 作用:用于控制 GroupBox 及其内部的所有控件是否可用。当 Enabled 属性设置为 false 时,GroupBox 及其内部的控件将变为灰色,用户无法与它们进行交互。
  • 示例代码
groupBox1.Enabled = false;

Visible 属性

  • 作用:用于控制 GroupBox 及其内部的所有控件是否可见。当 Visible 属性设置为 false 时,GroupBox 及其内部的控件将从窗体上隐藏。
  • 示例代码
groupBox1.Visible = false;
  1. 使用方法

在设计器中使用 GroupBox

  • 打开Visual Studio的Windows Forms设计器。
  • 从工具箱中找到 GroupBox 控件,将其拖动到窗体上。
  • GroupBox 的属性窗口中设置其 Text 属性,为其添加标题。
  • 从工具箱中选择其他控件(如按钮、文本框等),将它们拖动到 GroupBox 内部,即可完成控件的分组。

在代码中动态添加控件到 GroupBox

using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // 创建一个 GroupBox 控件
            GroupBox groupBox1 = new GroupBox();
            groupBox1.Text = "用户信息";
            groupBox1.Location = new System.Drawing.Point(20, 20);
            groupBox1.Size = new System.Drawing.Size(200, 150);

            // 创建一个 Label 控件并添加到 GroupBox 中
            Label label1 = new Label();
            label1.Text = "姓名:";
            label1.Location = new System.Drawing.Point(10, 20);
            groupBox1.Controls.Add(label1);

            // 创建一个 TextBox 控件并添加到 GroupBox 中
            TextBox textBox1 = new TextBox();
            textBox1.Location = new System.Drawing.Point(60, 20);
            groupBox1.Controls.Add(textBox1);

            // 将 GroupBox 控件添加到窗体中
            this.Controls.Add(groupBox1);
        }
    }
}
  1. 注意事项
  • GroupBox 只是一个容器控件,它本身不处理用户输入或事件,主要用于视觉上的分组。
  • GroupBoxEnabled 属性设置为 false 时,其内部的所有控件也会变为不可用状态,但它们的 Enabled 属性值不会改变。
  • 在布局 GroupBox 及其内部控件时,要注意控件的位置和大小,避免出现控件重叠或布局混乱的情况。

6. 布局管理

布局管理器
  • WinForm提供了多种布局管理器,如FlowLayoutPanelTableLayoutPanelDockPanel等,用于自动调整控件的位置和大小,以适应窗体的大小变化。
使用布局管理器
  • 可以将控件添加到布局管理器中,然后设置布局管理器的属性来控制控件的排列方式。例如,使用FlowLayoutPanel可以使控件按照水平或垂直方向依次排列。

7. 数据绑定

数据绑定概念
  • 数据绑定是将控件与数据源(如数据库、集合等)关联起来,使控件能够自动显示数据源中的数据,并在数据发生变化时自动更新。
数据绑定示例
  • 以下是一个简单的数据绑定示例,将一个ListBox控件与一个字符串列表绑定:
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace WinFormDataBindingExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // 创建一个字符串列表
            List<string> fruits = new List<string> { "苹果", "香蕉", "橙子" };

            // 将列表绑定到ListBox控件
            listBox1.DataSource = fruits;
        }
    }
}

总之,C#中的WinForm是一种强大的桌面应用程序开发技术,通过使用窗体、控件、事件处理、布局管理和数据绑定等功能,可以创建出功能丰富、用户友好的桌面应用程序。

6 SOCKET

6.1 什么是socket

我们平时说的最多的socket是什么呢,实际上socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API)。也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。关于TCP/IP和HTTP协议的关系,网络有一段比较容易理解的介绍:“我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容。如果想要使传输的数据有意义,则必须使用到应用层协议。

6.1.1 Socket与Servlet

  • 进程之间通信利用socket发送或接收消息
  • Socket:使用TCP/IP或者UDP协议在服务器与客户端之间进行传输的技术,是网络编程的基础
  • Servlet:使用http协议在服务器与客户端之间通信的技术。是Socket的一种应用
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

在C#中,Socket 类提供了实现网络通信的能力,可用于创建客户端和服务器应用程序,实现基于TCP或UDP协议的网络通信。下面分别介绍如何使用 Socket 类创建TCP和UDP的客户端与服务器程序。

TCP 通信示例

TCP 服务器端代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class TcpServer
{
    static void Main()
    {
        // 创建一个TCP/IP套接字
        Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // 绑定IP地址和端口
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 8888);
        serverSocket.Bind(localEndPoint);

        // 开始监听,设置最大连接数为10
        serverSocket.Listen(10);
        Console.WriteLine("服务器已启动,等待客户端连接...");

        while (true)
        {
            // 接受客户端连接
            Socket clientSocket = serverSocket.Accept();
            Console.WriteLine("客户端已连接:" + clientSocket.RemoteEndPoint);

            // 接收客户端消息
            byte[] buffer = new byte[1024];
            int bytesRead = clientSocket.Receive(buffer);
            string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
            Console.WriteLine("收到客户端消息:" + message);

            // 向客户端发送响应
            string response = "服务器已收到消息:" + message;
            byte[] responseBytes = Encoding.ASCII.GetBytes(response);
            clientSocket.Send(responseBytes);

            // 关闭客户端连接
            clientSocket.Shutdown(SocketShutdown.Both);
            clientSocket.Close();
        }
    }
}
TCP 客户端代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class TcpClient
{
    static void Main()
    {
        // 创建一个TCP/IP套接字
        Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // 连接到服务器
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
        IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, 8888);
        clientSocket.Connect(remoteEndPoint);

        // 发送消息到服务器
        string message = "Hello, Server!";
        byte[] messageBytes = Encoding.ASCII.GetBytes(message);
        clientSocket.Send(messageBytes);

        // 接收服务器响应
        byte[] buffer = new byte[1024];
        int bytesRead = clientSocket.Receive(buffer);
        string response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
        Console.WriteLine("收到服务器响应:" + response);

        // 关闭客户端连接
        clientSocket.Shutdown(SocketShutdown.Both);
        clientSocket.Close();
    }
}

UDP 通信示例

UDP 服务器端代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpServer
{
    static void Main()
    {
        // 创建一个UDP套接字
        UdpClient server = new UdpClient(8888);
        IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);

        Console.WriteLine("UDP服务器已启动,等待消息...");

        while (true)
        {
            // 接收客户端消息
            byte[] receiveBytes = server.Receive(ref remoteEndPoint);
            string receiveMessage = Encoding.ASCII.GetString(receiveBytes);
            Console.WriteLine("收到客户端消息:" + receiveMessage + " 来自:" + remoteEndPoint);

            // 向客户端发送响应
            string responseMessage = "服务器已收到消息:" + receiveMessage;
            byte[] sendBytes = Encoding.ASCII.GetBytes(responseMessage);
            server.Send(sendBytes, sendBytes.Length, remoteEndPoint);
        }
    }
}
UDP 客户端代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpClientExample
{
    static void Main()
    {
        // 创建一个UDP套接字
        UdpClient client = new UdpClient();
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
        IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, 8888);

        // 发送消息到服务器
        string message = "Hello, UDP Server!";
        byte[] sendBytes = Encoding.ASCII.GetBytes(message);
        client.Send(sendBytes, sendBytes.Length, remoteEndPoint);

        // 接收服务器响应
        IPEndPoint receiveEndPoint = new IPEndPoint(IPAddress.Any, 0);
        byte[] receiveBytes = client.Receive(ref receiveEndPoint);
        string receiveMessage = Encoding.ASCII.GetString(receiveBytes);
        Console.WriteLine("收到服务器响应:" + receiveMessage);

        // 关闭客户端
        client.Close();
    }
}

代码说明

  • TCP 通信:TCP 是面向连接的协议,服务器端需要先绑定地址和端口,然后开始监听,客户端连接到服务器后,双方可以进行数据的收发。
  • UDP 通信:UDP 是无连接的协议,服务器和客户端直接进行数据的发送和接收,不需要建立连接。

以上代码只是简单的示例,实际应用中可能需要处理更多的异常情况和进行性能优化。

6.2 Accept

在C#中,Accept 方法主要用于基于TCP协议的网络编程,是 Socket 类的一个重要方法,用于接受客户端的连接请求。以下是关于它的详细介绍:

基本定义和作用

Accept 方法用于在服务器端监听客户端的连接请求,并在有客户端尝试连接时,创建一个新的 Socket 对象来处理与该客户端的通信。这个新的 Socket 对象代表了服务器与客户端之间的一个具体连接,通过它可以进行数据的发送和接收。

方法签名

public Socket Accept();
  • 返回值:一个新的 Socket 对象,该对象用于与发起连接的客户端进行通信。

使用示例

下面是一个简单的服务器端代码示例,演示了 Accept 方法的使用:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class TcpServer
{
    static void Main()
    {
        // 创建一个TCP/IP套接字
        Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // 绑定IP地址和端口
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 8888);
        serverSocket.Bind(localEndPoint);

        // 开始监听,设置最大连接数为10
        serverSocket.Listen(10);
        Console.WriteLine("服务器已启动,等待客户端连接...");

        while (true)
        {
            try
            {
                // 接受客户端连接
                Socket clientSocket = serverSocket.Accept();
                Console.WriteLine("客户端已连接:" + clientSocket.RemoteEndPoint);

                // 接收客户端消息
                byte[] buffer = new byte[1024];
                int bytesRead = clientSocket.Receive(buffer);
                string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                Console.WriteLine("收到客户端消息:" + message);

                // 向客户端发送响应
                string response = "服务器已收到消息:" + message;
                byte[] responseBytes = Encoding.ASCII.GetBytes(response);
                clientSocket.Send(responseBytes);

                // 关闭客户端连接
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("发生错误:" + ex.Message);
            }
        }
    }
}

代码解释

  1. 创建服务器套接字:使用 Socket 类创建一个TCP/IP套接字,并绑定到指定的IP地址和端口。
  2. 开始监听:调用 Listen 方法开始监听客户端的连接请求,参数表示最大连接数。
  3. 接受客户端连接:在一个无限循环中调用 Accept 方法,当有客户端连接时,会返回一个新的 Socket 对象,用于与该客户端进行通信。
  4. 数据收发:通过新的 Socket 对象进行数据的发送和接收。
  5. 关闭连接:处理完客户端请求后,关闭与客户端的连接。

注意事项

  • Accept 方法是一个阻塞方法,意味着在没有客户端连接时,程序会暂停执行,直到有客户端连接进来。
  • 在实际应用中,为了提高服务器的并发处理能力,通常会使用多线程或异步编程来处理每个客户端连接。

6.3 Listen

在C#的网络编程中,Listen 方法是 Socket 类的一个重要方法,用于将套接字置于侦听状态,等待客户端的连接请求。以下为你详细介绍它的使用方法、参数、返回值等信息。

方法定义与作用

Listen 方法用于将 Socket 对象设置为监听模式,使服务器能够接收来自客户端的连接请求。在调用 Listen 方法之前,需要先将 Socket 绑定到一个本地终结点(IP地址和端口)。

方法签名

public void Listen(int backlog);

参数说明

  • backlog:一个整数,表示挂起连接队列的最大长度。即允许在服务器处理当前连接时,等待处理的客户端连接请求的最大数量。如果有超过 backlog 数量的客户端请求连接,后续的请求将被拒绝。

返回值

该方法没有返回值(void)。

使用示例

以下是一个简单的TCP服务器示例,展示了 Listen 方法的使用:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class TcpServer
{
    static void Main()
    {
        // 创建一个TCP/IP套接字
        Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // 绑定IP地址和端口
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 8888);
        serverSocket.Bind(localEndPoint);

        // 开始监听,设置最大连接数为10
        serverSocket.Listen(10);
        Console.WriteLine("服务器已启动,等待客户端连接...");

        while (true)
        {
            try
            {
                // 接受客户端连接
                Socket clientSocket = serverSocket.Accept();
                Console.WriteLine("客户端已连接:" + clientSocket.RemoteEndPoint);

                // 接收客户端消息
                byte[] buffer = new byte[1024];
                int bytesRead = clientSocket.Receive(buffer);
                string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                Console.WriteLine("收到客户端消息:" + message);

                // 向客户端发送响应
                string response = "服务器已收到消息:" + message;
                byte[] responseBytes = Encoding.ASCII.GetBytes(response);
                clientSocket.Send(responseBytes);

                // 关闭客户端连接
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("发生错误:" + ex.Message);
            }
        }
    }
}

代码解释

  1. 创建套接字:使用 Socket 类创建一个TCP/IP套接字。
  2. 绑定终结点:使用 Bind 方法将套接字绑定到指定的IP地址和端口。
  3. 开始监听:调用 Listen 方法,将 backlog 参数设置为 10,表示允许最多 10 个客户端连接请求等待处理。
  4. 接受连接:在一个无限循环中,使用 Accept 方法接受客户端的连接请求,并处理客户端的消息。

注意事项

  • backlog 参数只是一个建议值,实际的最大连接数可能会受到操作系统的限制。
  • Listen 方法只是将套接字设置为监听状态,不会阻塞线程。真正阻塞线程的是 Accept 方法,它会等待客户端的连接请求。
  • 在实际应用中,为了提高服务器的并发处理能力,可以使用多线程或异步编程来处理每个客户端连接。

6.4 Task.Run

在C#中,Task.Run 是一个非常有用的静态方法,它位于 System.Threading.Tasks 命名空间下,主要用于在新的线程上异步执行一个操作。下面将从多个方面详细介绍 Task.Run

基本定义和作用

Task.Run 方法允许你在后台线程上运行一个委托(如 ActionFunc<TResult>),从而避免阻塞当前线程,使得程序可以继续执行其他任务,提高程序的响应性和并发处理能力。它是一种简单且常用的开启异步操作的方式,适用于执行一些耗时的计算密集型或I/O密集型任务。

方法重载

Task.Run 有多个重载版本,常用的有以下两种:

1. 执行 Action 委托
public static Task Run(Action action);
  • 参数action 是一个无返回值的委托,表示要异步执行的操作。
  • 返回值:一个表示异步操作的 Task 对象。
2. 执行 Func<TResult> 委托
public static Task<TResult> Run<TResult>(Func<TResult> function);
  • 参数function 是一个有返回值的委托,表示要异步执行的操作,返回类型为 TResult
  • 返回值:一个表示异步操作的 Task<TResult> 对象,当操作完成时,可以通过 Result 属性获取操作的返回值。

使用示例

执行无返回值的操作
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Console.WriteLine("主线程开始");

        // 使用 Task.Run 执行无返回值的操作
        Task task = Task.Run(() =>
        {
            Console.WriteLine("异步操作开始,当前线程ID: " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000); // 模拟耗时操作
            Console.WriteLine("异步操作结束");
        });

        Console.WriteLine("主线程继续执行其他任务");

        // 等待异步操作完成
        task.Wait();

        Console.WriteLine("主线程结束");
    }
}

在这个示例中,Task.Run 接受一个 Action 委托,该委托中的代码会在新的线程上异步执行,而主线程可以继续执行其他任务。

执行有返回值的操作
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Console.WriteLine("主线程开始");

        // 使用 Task.Run 执行有返回值的操作
        Task<int> task = Task.Run(() =>
        {
            Console.WriteLine("异步操作开始,当前线程ID: " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000); // 模拟耗时操作
            return 42;
        });

        Console.WriteLine("主线程继续执行其他任务");

        // 获取异步操作的返回值
        int result = task.Result;
        Console.WriteLine("异步操作的返回值: " + result);

        Console.WriteLine("主线程结束");
    }
}

在这个示例中,Task.Run 接受一个 Func<int> 委托,该委托返回一个整数。通过 task.Result 属性可以获取异步操作的返回值,但需要注意的是,Result 属性会阻塞当前线程,直到异步操作完成。

注意事项

  • 异常处理:如果异步操作中抛出异常,可以通过 await 关键字(在异步方法中)或 task.Waittask.Result 来捕获异常。例如:
try
{
    await task;
}
catch (Exception ex)
{
    Console.WriteLine("异步操作发生异常: " + ex.Message);
}
  • 资源管理Task.Run 会使用线程池中的线程来执行操作,要避免在 Task.Run 中执行长时间运行的任务,以免耗尽线程池资源。对于长时间运行的任务,可以考虑使用 Task.Factory.StartNew 并指定 TaskCreationOptions.LongRunning 选项。
  • 上下文问题Task.Run 默认会在新的线程上执行,因此可能会丢失当前线程的上下文信息,如 SynchronizationContext。如果需要在特定的上下文中执行后续操作,可以使用 ConfigureAwait 方法。

6.6 InvokeRequired

在C#中,comboBox1.InvokeRequired 是一个常用的属性,主要用于处理多线程环境下对Windows Forms控件进行安全访问的问题。下面从几个方面详细介绍它:

背景

在Windows Forms应用程序中,所有的UI控件(如 ComboBoxTextBox 等)都与创建它们的线程相关联,这个线程通常是主线程(也称为UI线程)。根据Windows Forms的规则,只有创建控件的线程才能直接访问和修改该控件的属性、调用其方法。如果在其他线程中尝试直接访问UI控件,就会抛出 InvalidOperationException 异常。

InvokeRequired 属性的作用

InvokeRequiredControl 类(所有Windows Forms控件都继承自 Control 类)的一个布尔属性。对于 comboBox1 这个 ComboBox 控件来说,comboBox1.InvokeRequired 用于检查当前代码所在的线程是否与创建 comboBox1 的线程(即UI线程)相同。

  • 如果 comboBox1.InvokeRequired 返回 true,表示当前代码所在的线程不是创建 comboBox1 的线程,不能直接访问 comboBox1,需要使用 InvokeBeginInvoke 方法来跨线程访问该控件。
  • 如果返回 false,则表示当前线程就是创建 comboBox1 的线程,可以直接访问 comboBox1

示例代码

以下是一个简单的示例,展示了如何使用 InvokeRequired 属性来安全地跨线程更新 ComboBox 控件:

using System;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 启动一个新线程来模拟耗时操作
            Thread workerThread = new Thread(DoWork);
            workerThread.Start();
        }

        private void DoWork()
        {
            // 模拟耗时操作
            Thread.Sleep(2000);

            // 检查是否需要跨线程调用
            if (comboBox1.InvokeRequired)
            {
                // 如果需要,使用 Invoke 方法来执行更新操作
                comboBox1.Invoke(new Action(() =>
                {
                    comboBox1.Items.Add("新项");
                    comboBox1.SelectedIndex = comboBox1.Items.Count - 1;
                }));
            }
            else
            {
                // 如果不需要,直接更新控件
                comboBox1.Items.Add("新项");
                comboBox1.SelectedIndex = comboBox1.Items.Count - 1;
            }
        }
    }
}

代码解释

  1. 按钮点击事件:当用户点击 button1 时,会启动一个新的线程 workerThread,并执行 DoWork 方法。
  2. 耗时操作:在 DoWork 方法中,使用 Thread.Sleep(2000) 模拟一个耗时操作。
  3. 检查是否需要跨线程调用:通过 comboBox1.InvokeRequired 检查当前线程是否与UI线程相同。
  4. 跨线程调用:如果 comboBox1.InvokeRequired 返回 true,则使用 comboBox1.Invoke 方法来执行更新 ComboBox 的操作。Invoke 方法接受一个 Delegate 作为参数,这里使用 Action 委托来封装要执行的代码。
  5. 直接调用:如果 comboBox1.InvokeRequired 返回 false,则可以直接更新 ComboBox 控件。

替代方案

在.NET 4.0及以后的版本中,推荐使用 async/await 模式和 Task 类来处理异步操作,这样可以更简洁地实现跨线程更新UI控件,避免手动使用 InvokeRequiredInvoke 方法。例如:

private async void button1_Click(object sender, EventArgs e)
{
    await Task.Run(() =>
    {
        // 模拟耗时操作
        Thread.Sleep(2000);
    });

    // 可以直接更新UI控件,因为 await 会自动切换回UI线程
    comboBox1.Items.Add("新项");
    comboBox1.SelectedIndex = comboBox1.Items.Count - 1;
}

这种方式更加简洁和直观,同时也能保证线程安全。

7 C#代码打包

在C#中,将代码打包通常有多种场景,比如将控制台应用程序、Windows Forms应用程序、ASP.NET应用程序等打包成可部署的形式,以下为你分别介绍不同场景下的打包方法。

1. 打包控制台应用程序或Windows Forms应用程序

可以使用Visual Studio自带的发布功能将应用程序打包成可执行文件或安装包,以下是详细步骤:

步骤一:打开项目

在Visual Studio中打开你的C#项目(控制台应用程序或Windows Forms应用程序)。

步骤二:选择发布配置

在“解决方案资源管理器”中,右键单击项目名称,选择“发布”。

步骤三:选择发布目标

在弹出的“发布向导”中,选择发布目标。常见的发布目标有:

  • 文件夹:将应用程序发布到指定的文件夹中,生成可执行文件和相关依赖项。
  • Azure应用服务:将应用程序发布到Azure云平台。
  • App Package:生成Windows应用程序包(.appx)。

这里以发布到文件夹为例,选择“文件夹”,然后点击“下一步”。

步骤四:配置发布设置

在“目标位置”中指定发布文件夹的路径,然后点击“完成”。

步骤五:开始发布

点击“发布”按钮,Visual Studio会将应用程序编译并发布到指定的文件夹中。发布完成后,你可以在指定的文件夹中找到可执行文件(.exe)和相关依赖项。

2. 打包ASP.NET应用程序

对于ASP.NET应用程序,同样可以使用Visual Studio的发布功能,以下是具体步骤:

步骤一:打开项目

在Visual Studio中打开你的ASP.NET项目。

步骤二:选择发布配置

在“解决方案资源管理器”中,右键单击项目名称,选择“发布”。

步骤三:选择发布目标

常见的发布目标有:

  • 文件夹:将应用程序发布到指定的文件夹中,生成网站文件和相关依赖项。
  • IIS:将应用程序发布到本地或远程的IIS服务器。
  • Azure应用服务:将应用程序发布到Azure云平台。

这里以发布到文件夹为例,选择“文件夹”,然后点击“下一步”。

步骤四:配置发布设置

在“目标位置”中指定发布文件夹的路径,然后点击“完成”。

步骤五:开始发布

点击“发布”按钮,Visual Studio会将应用程序编译并发布到指定的文件夹中。发布完成后,你可以将发布文件夹中的文件部署到IIS服务器或其他Web服务器上。

3. 使用第三方工具打包

除了使用Visual Studio的发布功能,还可以使用第三方工具来打包C#应用程序,例如:

Inno Setup

Inno Setup是一个免费的安装程序制作工具,可以用于创建Windows应用程序的安装包。以下是使用Inno Setup打包C#应用程序的基本步骤:

  1. 下载并安装Inno Setup:从官方网站(https://jrsoftware.org/isinfo.php)下载并安装Inno Setup。
  2. 创建脚本文件:打开Inno Setup,选择“文件” -> “新建”,然后按照向导的提示创建一个新的脚本文件。在脚本文件中,需要指定应用程序的可执行文件、依赖项、安装路径等信息。
  3. 编译脚本文件:完成脚本文件的编辑后,选择“编译” -> “编译”,Inno Setup会根据脚本文件生成安装包。
WiX Toolset

WiX Toolset是一个开源的Windows安装程序制作工具,可以用于创建Windows Installer(.msi)安装包。以下是使用WiX Toolset打包C#应用程序的基本步骤:

  1. 下载并安装WiX Toolset:从官方网站(https://wixtoolset.org/releases/)下载并安装WiX Toolset。
  2. 创建WiX项目:在Visual Studio中创建一个新的WiX项目,然后在项目中添加应用程序的可执行文件、依赖项等信息。
  3. 编译项目:在Visual Studio中编译WiX项目,生成Windows Installer(.msi)安装包。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万码无虫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值