第二阶段Winfrom-8:特性和反射,加密和解密,单例模式

1_预处理指令

(1)源代码指定了程序的定义,预处理指令(preprocessor directive)指示编译器如何处理源代码。例如,在某些情况下,我们希望编译器能够忽略一部分代码,而在其他情况下,我们希望代码被编译,这时我们就可以使用预处理指令了。

(2)基本规则

  • 预处理指令必须和C#代码在不同的行

  • 与C#语句不同,预处理指令不需要以分号结尾

  • 包含预处理指令的每一行必须与 ''#'' 字符开始(在#字符前可以有空格,在#字符和指令之间也可以有空格)

  • 允许行尾注释

  • 在预处理指令所在的行不允许有分隔符注释

(3)C# 预处理器指令列表

预处理器指令 描述
#define 它用于定义一系列成为符号的字符。
#undef 它用于取消定义符号。
#if 它用于测试符号是否为真
#else 它用于创建复合条件指令,与 #if 一起使用。
#elif 它用于创建复合条件指令。
#endif 指定一个条件指令的结束。
#line 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。
#error 它允许从代码的指定位置生成一个错误。
#warning 它允许从代码的指定位置生成一级警告。
#region 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。
#endregion 它标识着 #region 块的结束。
cs 复制代码
#define Debug //定义一个编译符号类似于定义一个变量,#define 声明,c#中的预处理指令
#define Log
#undef Log
static void Main(string[] args)
{
#if Log //当存在Log的时候执行1,后续不执行
            Console.WriteLine("执行了");//执行1
#elif Debug //当Log不存在,Debug存在时执行2,后续不执行
            Console.WriteLine("Debug");//执行2
#else  //当上述条件都不满足时执行
            Console.WriteLine("world");//执行3
#endif
    //上述相当于if elseif ...else 
}

2_特性

2.1_特性的定义

(1)特性 (Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

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

特性(Attribute)是一种可由用户自有定义的修饰符(Modifier),可以用来修饰各种需要被修饰的目标。我们可以对类、以及C#程序集中的成员进行进一步的描述。

简单地说,Attribute的作用是为它们的附着体追加上一些额外的信息(这些信息保存在附着物的体内)------比如"这个类是我写的"或者"这个函数以前出过问题"等等

(2)元数据:保存在程序集中有关程序及其类型的数据。元数据主要用来描述C#中各种元素(类,方法,构造函数,属性等)。

(3)Attribute与注释的区别

注释是对程序源代码的一种说明,主要目的是给人看的,在程序被编译的时候会被编译器所丢弃,因此,它丝毫不会影响到程序的执行。

Attribute是程序代码的一部分,它不但不会被编译器丢弃,而且还会被编译器编译进程序集(Assembly)的元数据(Metadata)里。在程序运行的时候,随时可以从元数据中提取出这些附加信息,并以之决策程序的运行。

元数据:.NET中元数据是指程序集中的命名空间、类、方法、属性等信息,这些信息是可以通过Reflection读取出来的。

(4)常用特性:

AttributeUsage,Conditional,Obsolete,Category , Description , Browsable , DefaultValue ,Serializable

2.2_三种预定义特性分类
2.2.1_Conditional条件特性

这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace。例如,当调试代码时显示变量的值。

Conditional条件特性 应用在方法上,让方法按照条件执行 全称:ConditionalAttribute

cs 复制代码
#define Debug
static void Main(string[] args)
{
    Test1();
    Test2();
}
public static void Test1()
{
    Console.WriteLine("Test1");
}
//Conditional条件特性 应用在方法上,让方法按照条件执行 全称:ConditionalAttribute
//Debug就是一个编译符号,当定义了这个编译符号,才会编译Test2;
[Conditional("Debug")]
public static void Test2()
{
    Console.WriteLine("Test2");
}
2.2.2_Obsolete废弃特性

这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。

  • 参数 message,是一个字符串,描述项目为什么过时以及该替代使用什么。

  • 参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。

cs 复制代码
[Obsolete(message)]
[Obsolete(message,iserror)]
static void Main(string[] args)
{
    Test1();
    Test2();
}
//Obsolete 参数1:提示信息,参数2 bool值 为true的时候,代表此方法不可用
[Obsolete("这个方法已经被弃用,可以使用Test2方法代替",true)]
 //这个方法已经被弃用,可以使用Test2方法代替
public static void Test1()//1.0
{
    Console.WriteLine("Test1");
}
public static void Test2()//2.0
{
    Console.WriteLine("Test2");
}
2.2.3_AttributeUsage预定义特性

预定义特性 AttributeUsage 描述了如何使用一个自定义特性。它规定了特性可应用到的项目的类型。

  • 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。

  • 参数 AllowMultiple(可选的)为该特性的 AllowMultiple属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。

  • 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。

cs 复制代码
[AttributeUsage(validon,AllowMultiple=allowmultiple,Inherited=inherited)]
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property, 
AllowMultiple = true)]
2.2.4_自定义特性

(1)特性本质上就是一个类:

  1. 命名建议以Attribute结尾,建议使用大驼峰。

  2. 必须继承基类Attribute

  3. 使用AttributeUsage特性来控制自定义的特性的应用范围。

(2)小技巧:怎么判断对象是一个特性呢?看对象的结尾是否以Attribute结尾,只要以Attribute结尾的基本上是特性。 特性在定义时,建议以Attribute结尾。特性肯定是一个类,必须继承Attribute。Atribute是特性的基类。

(3)自定义一个特性

cs 复制代码
//AttributeUsage也重载了|运算符
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple =true)]
public  class MyAttribute:Attribute
{
    /// <summary>
    /// 版本,提示,创建时间
    /// </summary>
    public string Version { get; set; }
    public string Message { get; set; }
    public string CallTime { get; set; }
    public MyAttribute(string version,string message,string callTime) 
    {
        Version= version;
        Message= message;
        CallTime= callTime;
    }
}

(4)自定义特性的使用

cs 复制代码
[MyAttribute("1.0","张三","2025-08-11")]
internal class Program
{
    //自定义特性可以简写
    // [My("1.0", "张三", "2025-08-11")]
    public string id { get; set; }
    static void Main(string[] args)
    {
        Test();//原始方法
        //反射预热
        //通过反射获取
        Type t1 = typeof(Program);//拿到Program类的类型,因为Test在Program
        Console.WriteLine(t1);
        MethodInfo mi=t1.GetMethod("Test");//拿到Test方法
        //mi不能使用()执行,需要使用Invoke调用
        mi.Invoke(null,null);
        //获取test1上使用的特性
        //参数1:特性的类型
        //参数2:是否支持继承搜索
        object[] attrs = mi.GetCustomAttributes(typeof(MyAttribute),false);
        foreach (MyAttribute attr in attrs)
        {
            Console.WriteLine(attr.Version);
        }
        Console.WriteLine(attrs);
    }
    [My("1.0", "张三", "2025-08-11")]
    [My("2.0", "李斯", "2025-08-11")]
    public static void Test()
    {
        Console.WriteLine("方法1");
    }
    public static void Test2(){}
    public static void Test3(){}
}
2.2.5_其他特性:应用于自定义控件中:

(1)特性:

  • Description特性用来给属性和事件添加描述信息(解释说明)

  • Category 特性用来给属性分类 默认分类放到"杂项"

  • Browsable 特性用来控制属性是否在属性窗口中出现

  • DefaultValue 特性 设置默认值 在 Visual Studio 的属性窗口中,如果属性值等于默认值,属性值会显示为普通文本;如果被修改过,则会显示为粗体。

(2)多个特性的时候,可以用英文逗号分割

cs 复制代码
//自定义控件,继承自UserControl用户空间 
public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }
        //Description特性用来给属性和事件添加描述信息(解释说明)
        //Category 特性用来给属性分类   默认分类放到"杂项"
        //Browsable 特性用来控制属性是否在属性窗口中出现
       // DefaultValue 特性 设置默认值 在 Visual Studio 的属性窗口中,如果属性值等于默认值,属性值会显示为普通文本;如果被修改过,则会显示为粗体。
        [Description("控制账号信息")]
        [Category("吴亦凡")]
        [Browsable(true)]
        [DefaultValue("吴亦凡的账号")]
        public  string Account
        {
            get { return textBox1.Text; }
            set { textBox1.Text = value; }
        }
        //多个特性的时候,可以用英文逗号分割
        [Description("控制密码信息"),Category("吴亦凡"),Obsolete("不建议使用此属性")]
        public string Password
        {
            get { return textBox2.Text; }
            set { textBox2.Text = value; }
        }
    }

3_反射

3.1_反射的定义

(1)反射是指在程序运行中,查看、操作其他程序集或者自身的元数据的各种信息(类、方法,属性、变量、对象等)的行为。C#中的反射(Reflection)是一种强大的功能,允许你在运行时检查和操作程序集、类型和对象的信息,基本上,使用反射可以在代码运行时检查和操作指定的类及其成员。C#反射的原理主要基于元数据(与C#特性相似),即程序集中存储的有关类型、方法等的信息。因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性。

反射就是为了拿到各种元素对应的标签。Reflection反射。使用反射时,代码性能低,因为反射使用了装箱和拆箱。

(2)优缺点

优点:

  • 反射提高了程序的灵活性和扩展性。

  • 降低耦合性,提高自适应能力。

  • 它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点:

  • 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。

  • 使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

(3)反射的作用

反射(Reflection)有下列用途:(可以让查看或使用程序集以及程序集中的对象多了一种方法)

  • 它允许在运行时查看特性(attribute)信息。

  • 它允许审查集合中的各种类型,以及实例化这些类型。

  • 它允许延迟绑定的方法和属性(property)。

  • 它允许在运行时创建新类型,然后使用这些类型执行一些任务。

3.2_反射示例

(1)没有反射之前:查看或使用程序集以及程序集中的对象方法:1.引用程序集 2.引入命名空间,3.实例化

(2)有反射后:反射通过元数据获取程序集或者程序集的对象信息;主要信息:类型,成员(字段 属性 方法)...

(3)Type类:类型声明的类,

属性 说明
Name属性 类型名称
Namespace属性 类所在的命名空间
FullName属性 类所在的命名空间+类名
GetField()方法 获取指定名称的公共字段
GetFields()方法 获取所有的公共字段
GetProperty()方法 获取指定名称的公共属性
GetProperties()方法 获取所有的公共属性
GetMethod()方法 获取指定名称的公共方法
GetMethods()方法 获取所有的公共方法
GetNestedType() 获取指定名称的嵌套类型(类中的类)
GetConstructor() 获取类的构造函数
GetEvent() 获取类的事件
GetCustomAttribute() 获取类的特性
typeof() 和 GetType() /获取类型
Invoke() 和 InvokeMember() 调用相应的成员
SetValue(),GetValue() 设置属性,获取属性

typeof():获取参数的数据类型

cs 复制代码
Type type1=typeof(string);
Type type2=typeof(int);
Type type3=typeof(Student);
Console.WriteLine(type1);
Console.WriteLine(type2);
Console.WriteLine(type3);
Student student=new Student();//没有使用反射,直接创建实例
Type type4 = student.GetType();
Console.WriteLine(type4);
Console.WriteLine(type1.Name);//类型名称:string
Console.WriteLine(type4.Name);//Studetn
Console.WriteLine(type4.Namespace);//类所在的命名空间
Console.WriteLine(type4.FullName);//类所在的命名空间+类名

(5)Assembly类:表示一个程序集

方法 说明
Load 通过给定程序集的名称加载程序集
LoadFile 加载指定路径上的程序集
LoadFrom 已知程序集得文件名或路径,加载程序集
CreateInstance 从程序集中加载指定得类型,并创建实例,返回值object类型

(4)通过反射获取信息的步骤

获取程序集------》创建类的实例------》获取实例的类型------》通过Type的方法获取类的成员

cs 复制代码
//2.通过反射获取字段,属性,方法
//通过反射创建实例,Load()加载程序集
//如果加载的程序集在当前项目中的bin/DeBug中不存在,会加载失败
Assembly assembly = Assembly.Load("03_反射");
//CreateInstance()创建,在()里面传入完整的对象名称,用加载得程序集创建一个程序集中得对象得实例
//使用反射基础创建程序集中得某个类得实例
object t1 =assembly.CreateInstance("_03_反射.Student");//类似于new一个实例对象
 //类似于Student student=new Student();
 //获取字段
FieldInfo fieldInfo = type3.GetField("MyId");//获取公开的字段
fieldInfo.SetValue(t1, 12);
int myId=(int)fieldInfo.GetValue(t1);
Console.WriteLine(myId);
Console.WriteLine("=========获取所有的公共的字段============");
//获取所有的字段
FieldInfo[] fieldInfos= type4.GetFields();
foreach(var item in fieldInfos)
{
    Console.WriteLine(item.Name);
}
Console.WriteLine("----------获单个属性----------");
PropertyInfo  propertyInfo1=  type3.GetProperty("Name");
propertyInfo1.SetValue(t1, "吴亦凡");
string name = (string)propertyInfo1.GetValue(t1);
Console.WriteLine(name);
Console.WriteLine("----------获取所有公开属性----------");
PropertyInfo [] propertyInfos = type3.GetProperties();
foreach (var item in propertyInfos)
{
    Console.WriteLine(item);
    if (item.Name=="Name")
    {
        item.SetValue(t1, "罗志祥");
    }
}
Console.WriteLine("--------获取方法-------");
MethodInfo methodInfo= type3.GetMethod("Method2");
//注意:通过数组的形式传入参数
methodInfo.Invoke(t1, new object[] { "a123","b123"});
MethodInfo []methodInfos= type3.GetMethods();

Type s1= type3.GetNestedType("SamllStudent");//获取Student类下的SamllStudent
object s12 =  assembly.CreateInstance(s1.FullName);

4_加密和解密

(1)加密算法分类:对称加密,非对称加密,散列算法加密。 对称加密:使用相同的密钥对数据进行加密和解密。如:DES算法、AES算法 非对称加密:使用一对公钥和私钥来进行加密和解密。如:RSA算法 散列算法加密:任意长度的数据转换为固定长度的哈希值。如:MD5算法、SHA算法

公钥:主要用来把明文加密成密文,用到key。 私钥:主要用来把密文解密成明文,用到的key。

(2)常用加密算法了解: C#中的加密解密方法:对称、非对称与散列算法详解-CSDN博客

MD5和DES加密及解密封装参考: C#:使用MD5对用户密码加密与解密 - Healer2047 - 博客园

(3)将加密和解密的类封装,在使用的时候直接调用。

文件地址:"D:\上位机\扩展\EncryptTool.dll"

5_单例模式

什么是设计模式?

设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路。通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性好。我们使用设计模式最终的目的是实现代码的 高内聚 和 低耦合。

什么是高内聚和低耦合?

举例一个现实生活中的例子,例如一个公司,一般都是各个部门各司其职,互不干涉。各个部门需要沟通时通过专门的负责人进行对接。在软件里面也是一样的 一个功能模块只是关注一个功能,一个模块最好只实现一个功能。这个是所谓的内聚,模块与模块之间、系统与系统之间的交互,是不可避免的, 但是我们要尽量减少由于交互引起的单个模块无法独立使用或者无法移植的情况发生, 尽可能多的单独提供接口用于对外操作, 这个就是所谓的低耦合

什么是单例模式?

从"单例"字面意思上理解为一个类只有一个实例,所以单例模式也就是保证一个类只有一个实例的一种实现方法(设计模式其实就是帮助我们解决实际开发过程中的方法, 该方法是为了降低对象之间的耦合度,然而解决方法有很多种,所以前人就总结了一些常用的解决方法为书籍,从而把这本书就称为设计模式)

官方定义:确保一个类只有一个实例,并提供一个全局访问点。

为什么会有单例模式?它在什么情况下使用的?

从单例模式的定义中我们可以看出:单例模式的使用自然是当我们的系统中某个对象只需要一个实例的情况,例如:操作系统中只能有一个任务管理器,操作文件时,同一时间内只允许一个实例对其操作等,既然现实生活中有这样的应用场景,自然在软件设计领域必须有这样的解决方案了(因为软件设计也是现实生活中的抽象),所以也就有了单例模式了。

单例模式和静态类的区别?

  1. 首先单例模式会提供给你一个全局唯一的对象,静态类只是提供给你很多静态方法,这些方法不用创建对象,通过类就可以直接调用;

  2. 单例模式的灵活性更高,方法可以被override,因为静态类都是静态方法,所以不能被override;

  3. 如果是一个非常大的对象,单例模式可以懒加载,静态类就无法做到

那么什么时候时候应该用静态类,什么时候应该用单例模式呢?首先如果你只是想使用一些工具方法,那么最好用静态类,静态类比单例类更快 ,因为静态的绑定是在编译期进行的。***如果你要维护状态信息,或者访问资源时,应该选用单例模式。***还可以这样说,当你需要面向对象的能力时(比如继承、多态)时,选用单例类,当你仅仅是提供一些方法时选用静态类。