[C#] C# 知识回顾,

[C#] C# 知识回顾,

[C#] C# 知识回顾,

[.NET] C# 知识回顾,

【博主】反骨仔    【原文】 

[.NET] C# 知识回顾,

C# 知识回顾 –  序列化

 【博主】反骨仔    【原文地址】

C# 知识回顾 – 委托 delegate

【博主】反骨仔    【原文】

你真的懂异常(Exception)吗?

目录

  • 异常介绍
  • 异常的特点
  • 怎样使用异常
  • 处理异常的 try-catch-finally
    • 捕获异常的 Catch 块
    • 释放资源的 Finally 块

 

  上篇《C# 知识回顾 – 委托
delegate》已经介绍委托的基本知识,这里是对其进行补充说明及加深理解。

 

C# 知识回顾 – Event 事件

目录

  • 序列化的含义
  • 通过序列化保存对象数据
  • 众说纷纭

 

目录

  • What’s 委托
  • 委托的属性概述
  • Use
    委托

 

一、异常介绍

  我们平时在写程序时,无意中(或技术不够),而导致程序运行时出现意外(或异常),对于这个问题,
C# 有专门的异常处理程序。
异常处理所涉及到的关键字有 trycatch 和 finally 等,用来处理失败的情况。
CLR、.NET
自身的类库、其它第三方库或者你写的程序代码都有可能会出现异常。当然,你也可以直接使用
throw ,通过显式的形式来进行创建异常。

  在你的代码中出现异常的时候,程序会找到并执行最先匹配的 catch 块。
如果在调用堆栈中的任意位置中,异常处理程序都没有找到合适(你写的)的 catch 块,就会自动终止该进程,并向用户显示(抛出)一条错误的信息。

  在这里我写了个被 0 除会出现的异常(一个显式引发
DivideByZeroException 异常)并捕获该异常的示例:

 1         /// <summary>
 2         /// 除法
 3         /// </summary>
 4         /// <param name="x"></param>
 5         /// <param name="y"></param>
 6         /// <returns></returns>
 7         static double Division(double x, double y)
 8         {
 9             if (y == 0)
10             {
11                 throw new DivideByZeroException();
12             }
13 
14             return x / y;
15         }
16 
17         static void Main(string[] args)
18         {
19             //定义两个变量 x, y
20             double x = 250, y = 0;
21             
22             try
23             {
24                 var result = Division(x, y);
25                 Console.WriteLine($"result: {result}");
26             }
27             catch (DivideByZeroException e)
28             {
29 
30                 Console.WriteLine(e);
31             }
32 
33             Console.Read();
34         }

使用 try 块包围你认为可能会出现异常的代码。

  • 一旦 try 块中发生异常,控制流将按顺序找到与之关联的 catch
    块,如果没有找到合适的,就会引发最终的异常基类 Exception
    内的处理程序(前提你已经 catch)。“

  • 如果出现异常却没有对应的异常处理程序,则该程序将会停止执行,并抛出对应错误的信息。

  • 在 catch 定义了的异常变量,可以获取对应异常类型的信息。比如调用堆栈的状态和错误的说明,具体看
    Excetion 的属性。

  • throw 关键字可以显式引发异常。

  • 即使出现异常也会执行 finally 块中的代码。一般来说,我们会使用 finally 块释放资源,例如,关闭xx流。

 

目录

  • 两个简单 Demo:带命名方法的委托和带匿名方法的委托
  • 创建多播委托
  • 委托的简单演化过程

 

【博主】反骨仔    【原文】  

一、序列化的含义

  序列化
(Serialization)将对象的状态信息处理为字节流,以便于存储或传输到内存、数据库或文件的一个过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区,主要目的是保存对象的状态。便于日后从该状态中进行还原,创建新的对象,这个过程又称为反序列化。

 

What’s 委托

  delegate 是表示对具有特定参数列表和返回类型的方法的引用的类型。在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。你可以通过委托实例调用方法。委托用于将方法作为参数传递给其他方法。事件处理程序就是通过委托调用的方法。你可以创建一个自定义方法,当发生特定事件时,某个类(如
Windows 控件)就可以调用你的方法。

  下面的示例演示了一个委托声明:

public delegate int Del(int x, int y);

  可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。该方法可以是静态方法,也可以是实例方法。这样便能通过编程方式来更改方法调用,还可以向现有类中插入新代码。

  【备注】在方法重载的上下文中,方法的签名不包括返回值。但在委托的上下文中,签名包括返回值。换句话说,方法和委托必须具有相同的返回类型。

  将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。例如,对比较两个对象的方法的引用可以作为参数传递到排序算法中。由于比较代码在一个单独的过程中,因此可通过更常见的方式编写排序算法。

 

三、怎样使用异常

  程序在运行时出现的错误,会不断在程序中进行传播,这种机制称为“异常”。
异常通常由错误的代码引发,并由能够更正错误的代码进行 catch。
异常也可以由 .NET 的 CLR 或由程序中的代码引发,
一旦引发了异常,这个异常将会在调用堆栈中一直向上进行传播,直到寻找到跟它匹配的 catch 语句。没有
catch
的异常会由系统提供的默认的异常处理程序进行处理,也就是你经常看到的一个突然造成调试中断并显示异常信息的对话框。

  所有的异常,它们都是从 Exception 派生出来的。这些异常的类型,都会包含详细描述异常的属性。在这里我将自定义了一个新的异常类,其实也可以自定义配置异常的属性(这是可选的),然后我使用 throw 关键字显示引发该对象(即异常)。 

 1         /// <summary>
 2         /// 定义新异常
 3         /// </summary>
 4         class MyException : Exception
 5         {
 6             public MyException(string msg) { }
 7         }
 8 
 9         /// <summary>
10         /// 抛出新定义的异常
11         /// </summary>
12         static void ThrowMyExcetion()
13         {
14             throw new MyException("Sorry, this is test!");
15         }

 

  在引发异常之后,运行时程序会检查当前语句确定它是否包含在 try 块中。
如果是的话,就会检查与该 try 块相关联的所有 catch 块,来确定它们是否能够
catch 该异常。
 catch 块通常会指定异常类型;如果该 catch 块的类型与异常或它的基类的相同(或匹配),则该 catch 块就能够捕获并处理。

 1         static void Main(string[] args)
 2         {
 3             try
 4             {
 5                 ThrowMyExcetion();  //直接调用抛出异常的方法
 6             }
 7             catch (MyException e)
 8             {
 9                 Console.WriteLine(e);
10             }
11 
12             Console.Read();
13         }

1 static void Main(string[] args) 2 { 3 StreamWriter sw = null; 4 5
try 6 { 7 sw = new StreamWriter(@”C:\book\小二和小三的故事.txt”); 8
sw.Write(“You are 250.”); 9 } 10 catch (FileNotFoundException e) 11 { 12
//将具体的异常放在第一位 13 Console.WriteLine(e); 14 } 15 catch
(IOException e) 16 { 17 //将并不具体的放在相对后面的位置 18
Console.WriteLine(e); 19 } 20 catch (Exception e) 21 { 22
Console.WriteLine(e); 23 } 24 finally 25 { 26 if (sw != null) 27 { 28
sw.Close(); 29 } 30 } 31 32 Console.Read(); 33 }

 

  执行 catch 块之前,CLR
会检查 finally 块。 finally 块使程序员能够清除中止的 try 块可能遗留下的任何模糊状态,或者释放任何外部资源(例如图形句柄、db
连接或 IO 流),而无需等待 CLR 中的垃圾回收器终结这些对象。 例如:

 1         static void Main(string[] args)
 2         {
 3             FileStream fs = null;
 4             FileInfo fi = new FileInfo(@"小二和小三的故事.txt");
 5 
 6             try
 7             {
 8                 fs = fi.OpenWrite();
 9                 fs.WriteByte(0);
10             }
11             finally
12             {
13                 //记住哦,如果你忘记 close,将会引发 IO 异常!
14                 //if (fs != null)
15                 //{
16                 //    fs.Close();
17                 //}
18             }
19 
20             try
21             {
22                 fs = fi.OpenWrite();
23                 fs.WriteByte(1);
24                 Console.WriteLine("OK!");
25             }
26             catch (IOException e)
27             {
28                 Console.WriteLine("Fail!");
29             }
30 
31             Console.Read();
32         }

1 static void Main(string[] args) 2 { 3 try 4 { 5 //需要执行的代码 6
} 7 catch (Exception e) 8 { 9 //这里可以获取到被捕获的异常 10
//你需要知道自己应该如何处理该异常 11 } 12 }

 

  (2)try-finally:

1             try
2             {
3                 //需要执行的代码
4             }
5             finally
6             {
7                 //在 try 块后执行的代码
8             }

 

  (3)try-catch-finally:

 1             try
 2             {
 3                 //需要执行的代码
 4             }
 5             catch (Exception e)
 6             {
 7                 //这里处理异常
 8             }
 9             finally
10             {
11                 //在 try 块(也可能是 catch 块)后执行的代码
12             }

  【备注】不带有 catch 或 finally 块的 try 块将导致编译器错误。

 

一、两个简单 Demo:带命名方法的委托和带匿名方法的委托

  委托可以与命名方法关联。  使用命名方法对委托进行实例化时,该方法将作为参数传递,例如:  

 1     class Program
 2     {
 3         //声明一个委托
 4         delegate void MyDel(string message);
 5 
 6         
 7         static void Main(string[] args)
 8         {
 9             //使用静态方法作为参数实例化委托
10             MyDel del = Print;
11         }
12 
13         //声明一个方法
14         private static void Print(string message)
15         {
16             Console.WriteLine(message);
17         }
18     }

  这被称为使用命名的方法。使用命名方法构造的委托可以封装静态方法或实例方法。在早期版本的
C# 中,命名方法是对委托进行实例化的唯一方式。但是,在不希望付出创建新方法的系统开销时,C#
使您可以对委托进行实例化,并立即指定委托在被调用时将处理的代码块。代码块可以包含
lambda 表达式或匿名方法。  

  【备注】①作为委托参数传递的方法必须与委托声明具有相同的签名。②委托实例可以封装静态或实例方法。③尽管委托可以使用 out 参数,但建议您不要将其用于多路广播事件委托,因为您无法知道哪个委托将被调用。
    示例1:以下是声明及使用委托的一个简单示例。  注意,委托 MyDel 和关联的方法 Print 具有相同的签名(即便方法的参数名称 m 和 n 的位置替换)  。

 1     class Program
 2     {
 3         //声明一个委托
 4         delegate void MyDel(int n, int m);
 5 
 6         static void Main(string[] args)
 7         {
 8             //使用静态方法 Print 作为参数实例化委托
 9             MyDel del = Print;
10             Console.WriteLine("准备好了哦,要开始调用委托了哦!");
11 
12             for (int i = 0; i < 10; i++)
13             {
14                 del(i, 1);
15             }
16 
17             Console.Read();
18         }
19 
20         //声明一个方法
21         private static void Print(int m, int n)
22         {
23             Console.Write(m - n + " ");
24         }
25     }

1 class Program 2 { 3 //声明一个委托 4 delegate void MyDel(int n, int
m); 5 6 static void Main(string[] args) 7 { 8 //使用静态方法 Print
作为参数实例化委托 9 //MyDel del = Print; 10 11 //使用匿名方法 12 MyDel
del = delegate (int m, int n) 13 { 14 Console.Write(m – n + ” “); 15 };
16 Console.WriteLine(“准备好了哦,要开始调用委托了哦!”); 17 18 for (int
i = 0; i < 10; i++) 19 { 20 del(i, 1); 21 } 22 23 Console.Read(); 24
} 25 }

   【备注】感谢 2 楼 随碟附送520 的修正。感谢 4 楼
潇十一郎 的补充,也可以用 Lambda 的形式 (m, n) => 创建委托。

 

 

  昨天,通过《C# 知识回顾 –
事件入门》介绍了事件的定义及简单用法,今天我们通过控制台来看下“发布 –
订阅”的基本用法。

 

1.1 序列化的工作方式

图片 1

图1.1-1

 

图片 2

  对象被序列化为字节流(包括数据、对象的类型信息:如版本、区域性和程序集名称)
–> 存储到 DB、IO 等地方。

 

委托的属性概述

  • 类似于
    C 和 C++ 中的函数指针,但它们是类型安全的。

  • 允许将方法作为参数进行传递。

  • 可用于定义回调方法。

  • 委托可以链接在一起;例如,可以对一个事件调用多个方法。

  • 方法不必与委托类型完全匹配。

 

4.1 捕获异常的 Catch 块

catch 块可以指定要捕捉的异常类型,又可以称为“异常筛选器”。
异常类型都是从 Exception 派生出来。 一般而言,不会将所有异常的基类
System.Exception 指定为要 catch
的“异常筛选器”,除非你非常了解如何处理由 try 块引发的所有异常,或者在 catch 块中包括了 throw 语句。

  多个 catch 块可以串联在一起(要求异常筛选器不同)。
多个 catch 块的执行顺序是:在代码中,从顶部到底部,但是,对于在运行时所引发的每一个异常,程序都只会执行一个 catch 数据块。
与指定的异常类型或它的基类相匹配的第一个 catch 块,才会被执行。 通常,我们需要将最特殊(最具体或者说派生程度最最最高)的异常类,这段 catch 块放在所有
catch 块的最前面,而他们的基类 Excetion 的 catch
块就放在最后(当然,也可以不写)。

  在以下条件为真时,你应该选择 catch 异常:

  • 了解引发异常的原因,并可实现有选择性的恢复。例如,在捕获 FileNotFoundException
    时你可以提示用户“文件找不到”和“请输入新的文件名”等。

  • 你也可以新建一个更具体或者说更具有代表性的异常,并选择引发该异常。

 1         double GetNum(double[] nums,int index)
 2         {
 3             try
 4             {
 5                 return nums[index];
 6             }
 7             catch (IndexOutOfRangeException e)
 8             {
 9                 throw new ArgumentOutOfRangeException("Sorry, 你想要的索引已经超出界限!");
10             }
11         }

  

  希望在将异常抛出去时,我们通常会选择处理部分异常。
在下面这个示例中,catch 块在再次 throw 异常之前,添加错误日志。

 1             try
 2             {
 3                 //尝试访问系统资源
 4             }
 5             catch (Exception e)
 6             {
 7                 //伪代码:记录错误日志
 8                 log.Error(e);
 9 
10                 //再重新抛出错误
11                 throw;
12             }

 

二、创建多播委托

  本示例演示如何创建多播委托。委托对象的一个有用属性是:

  可以使用 + 运算符将多个对象分配给一个委托实例。多播委托包含已分配委托的列表。在调用多播委托时,它会按顺序调用列表中的委托。只能合并相同类型的委托。 运算符可用于从多播委托中移除组件委托。

 1     class Program
 2     {
 3         //声明一个委托
 4         delegate void MyDel();
 5 
 6         static void Main(string[] args)
 7         {
 8             //Action:你也可以自己尝试使用 Action 代替 MyDel 试试
 9 
10             MyDel del = Start;  //创建一个委托对象
11             MyDel del2 = Stop;  //创建一个委托对象
12             MyDel del3 = del + Stop;    //合并前两个委托对象
13             MyDel del4 = del3 - Start;  //移除一个委托对象
14 
15             Console.WriteLine($"This is {nameof(del)}: ");
16             del();
17             Console.WriteLine($"This is {nameof(del2)}: ");
18             del2();
19             Console.WriteLine($"This is {nameof(del3)}: ");
20             del3();
21             Console.WriteLine($"This is {nameof(del4)}: ");
22             del4();
23 
24             Console.Read();
25         }
26 
27         private static void Start()
28         {
29             Console.WriteLine($"    {nameof(Start)}...");
30         }
31 
32         private static void Stop()
33         {
34             Console.WriteLine($"    {nameof(Stop)}!");
35         }
36     }

演化过程。

 1     class Program
 2     {
 3         //声明一个委托
 4         delegate void MyDel();
 5 
 6         static void Main(string[] args)
 7         {
 8             //以下是不同版本的声明和初始化委托的方式
 9 
10             // ≥ C# 1
11             MyDel del1 = new MyDel(Print);
12 
13             // ≥ C# 2
14             MyDel del2 = Print; //上面的简化版
15             MyDel del3 = delegate ()
16             {
17                 Console.WriteLine($"    {nameof(Print)}...");
18             };  //匿名方法
19 
20             // ≥ C# 3
21             MyDel del4 = () =>
22             {
23                 Console.WriteLine($"    {nameof(Print)}...");
24             };  //Lambda 表达式
25 
26             Console.Read();
27         }
28 
29         private static void Print()
30         {
31             Console.WriteLine($"    {nameof(Print)}...");
32         }
33     }

 

目录

  • 发布符合 .NET 准则的事件
  • 采用
    EventHandler 模式发布事件
  • 一个简单的发布订阅 Demo
  • 实现自定义事件访问器

 

1.2 用于序列化

  我们在数据交换的时候常进行序列化保存对象的数据信息,在需要使用它的时候再进行反序列化重新读取对象的信息并进行校验和存储的一些工作。常用于
Web 间传递数据,跨域传递,ASP .NET 后台代码往前端传递数据(js
进行解析处理)。

 

Use 委托

  委托是安全封装方法的类型,类似于
C 和 C++ 中的函数指针。与 C
函数指针不同的是,委托是面向对象的、类型安全的和可靠的。委托的类型由委托的名称确定。

    //该委托可以封装 “,参数类型 string,返回类型 void” 的方法 
    public delegate void MyDel(string message);

 

  委托对象通常通过提供委托将封装的方法的名称或使用匿名方法构造。对委托进行实例化后,委托会将对其进行的方法调用传递到该方法。调用方传递到委托的参数将传递到该方法,并且委托会将方法的返回值(如果有)返回到调用方。这被称为调用委托。实例化的委托可以按封装的方法本身进行调用。例如:

 1     //该委托名为 MyDel,可以封装 “参数类型 string,返回值类型 void” 的方法 
 2     public delegate void MyDel(string message);
 3 
 4     class Program
 5     {
 6         static void Main(string[] args)
 7         {
 8             //实例化委托
 9             MyDel del = Print;
10             //调用委托
11             del("Hi");
12 
13             Console.Read();
14         }
15 
16         /// <summary>
17         /// 打印文本
18         /// </summary>
19         /// <remarks>这是一个可用于 MyDel 委托的方法</remarks>
20         /// <param name="message"></param>
21         private static void Print(string message)
22         {
23             Console.WriteLine(message);
24         }
25     }

异步回调,是在长进程完成时通知调用方的常用方法。当以这种方式使用委托时,使用委托的代码不需要知道要使用的实现方法。功能类似于封装接口提供的功能。
   
  回调的另一个常见用途是定义自定义比较方法并将该委托传递到短方法。  它允许调用方的代码成为排序算法的一部分。  以下示例方法使用 Del 类型作为参数:
 

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             MyDel del = Print;
 6             CallbackMethod(100, 150, del);  //将委托传递到 CallbackMethod 方法
 7 
 8             Console.Read();
 9         }
10 
11         /// <summary>
12         /// 回调方法
13         /// </summary>
14         /// <param name="m"></param>
15         /// <param name="n"></param>
16         /// <param name="del"></param>
17         private static void CallbackMethod(int m, int n, MyDel del)
18         {
19             del((m + n).ToString());
20         }
21 
22         private static void Print(string message)
23         {
24             Console.WriteLine(message);
25         }
26     }

委托不知道除其所封装方法以外的实例类型,因此委托可以引用任何类型的对象,只要该对象上有与委托签名匹配的方法。当委托构造为封装静态方法时,委托仅引用方法。  请考虑以下声明:
 

 1     //该委托可以封装 “名 MyDel,参数类型 string,返回值类型 void” 的方法 
 2     public delegate void MyDel(string message);
 3 
 4     class MyClass
 5     {
 6         public void Print1(string message)
 7         {
 8             Console.WriteLine($"{message} - {nameof(Print1)}");
 9         }
10 
11         public void Print2(string message)
12         {
13             Console.WriteLine($"{message} - {nameof(Print2)}");
14         }
15     }
16 
17     class Program
18     {
19         static void Main(string[] args)
20         {
21             var myClass = new MyClass();
22             MyDel del1 = myClass.Print1;
23             MyDel del2 = myClass.Print2;
24             MyDel del3 = Print;
25 
26             var del = del1 + del2;
27             del += del3;    //这里使用 +=
28             del("Hi!");
29 
30             Console.Read();
31         }
32 
33         private static void Print(string message)
34         {
35             Console.WriteLine($"{message} - {nameof(Print)}");
36         }
37     }

多播。若要向委托的方法列表(调用列表)添加其他方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。

  此时,del 的调用列表中包含三个方法,分别为 Print1、Print2 和
Print。原有的三个委托(del1、del2 和 del3)保持不变。调用
allMethodsDelegate
时,将按顺序调用所有三个方法。如果委托使用引用参数,引用将按相反的顺序传递到所有这三个方法,并且一种方法进行的任何更改都将在另一种方法上见到。当方法引发未在方法内捕获到的异常时,该异常将传递到委托的调用方,并且不会调用调用列表中的后续方法。如果委托具有返回值和/或输出参数,它将返回上次调用方法的返回值和参数。若要删除调用列表中的方法,请使用减法运算符或减法赋值运算符(“-”或“-=”)。  例如:
 

 1         static void Main(string[] args)
 2         {
 3             var myClass = new MyClass();
 4             MyDel del1 = myClass.Print1;
 5             MyDel del2 = myClass.Print2;
 6             MyDel del3 = Print;
 7 
 8             var del = del1 + del2;
 9             del += del3;    //使用 +=
10             del("Hi!");
11 
12             Console.WriteLine("======分割线======");
13 
14             del -= del2;    //使用 -=
15             del("Hi!");
16 
17             Console.Read();
18         }

1 static void Main(string[] args) 2 { 3 var myClass = new MyClass();
4 MyDel del1 = myClass.Print1; 5 MyDel del2 = myClass.Print2; 6 MyDel
del3 = Print; 7 8 var del = del1 + del2; 9 del += del3; //使用 += 10
//del(“Hi!”); 11 12 var count = del.GetInvocationList().Length;
//获取委托调用列表中方法的数量 13 Console.WriteLine(count); 14 15
Console.WriteLine(“======分割线======”); 16 17 del -= del2; //使用 -= 18
//del(“Hi!”); 19 20 count = del.GetInvocationList().Length;
//获取委托调用列表中方法的数量 21 Console.WriteLine(count); 22 23
Console.Read(); 24 }

 


【参考】微软官方文档

] C# 知识回顾, C# 知识回顾 – 委托
delegate 【博主】反骨仔
【原文】 目录 What’s 委托
委托的属性概述…

4.2 释放资源的 Finally 块

  可以使用 finally 块释放(清理)在 try 块中需要执行释放(清理)资源的操作。
如果存在finally 块,它将在最后执行,也就是在 try 块和任何匹配 catch 块之后执行。
不管是否引发异常或者说是否找到与异常类型相匹配的 catch 块,finally 块它始终都会运行。

  可以使用 finally 块释放资源(如 IO 流、DB
连接和图形句柄),而不要等待运行时中的垃圾回收器来完成对象资源的回收。
其实,我们更建议使用 using 语句。

  在下面的示例中,我使用 finally 块关闭在 try 块中打开的文件。注意,在关闭文件之前你应该要检查该文件句柄的状态。
如果 try 块无法打开文件,则文件句柄的值依然为 null,这时, finally 块就不会尝试关闭它。
或者说,如果在 try 块中成功打开该文件,则 finally 块才会成功地关闭正在打开的文件。

 1         static void Main(string[] args)
 2         {
 3             FileStream fs = null;
 4             FileInfo fi = new System.IO.FileInfo("C:\\小二和小三的故事.txt");
 5 
 6             try
 7             {
 8                 fs = fi.OpenWrite();
 9                 fs.WriteByte(0);
10             }
11             finally
12             {
13                 // 记得判断 null 哦,不然可能触发其它异常
14                 if (fs != null)
15                 {
16                     fs.Close();
17                 }
18             }
19 
20         }

 

传送门

  《C# 知识回顾 – 序列化》

  《C# 知识回顾 – 表达式树 Expression Trees》

  《C# 知识回顾 – 特性 Attribute》、《剖析 AssemblyInfo.cs –
了解常用的特性 Attribute》  


【参考】微软官方文档

] C# 知识回顾, C# 知识回顾 – 委托
delegate (续) 【博主】反骨仔
【原文】 序 上篇《C#
知识回顾…

一、发布符合 .NET 准则的事件

  下面的过程演示了如何将符合标准
.NET 模式的事件添加到您的类和结构中。.NET类库中的所有事件均基于 EventHandler 委托,定义如下:  

public delegate void EventHandler(object sender, EventArgs e);

  你可以尝试手动输入 EventHandler ,然后按下“F12”跳转到定义:

.NET Framework 2.0
引入了此委托的一个泛型版本,即 EventHandler<TEventArgs>。

  【备注】虽然您定义的类中的事件可基于任何有效委托类型(甚至是可返回值的委托),但是,通常建议您使用 EventHandler 让事件基于
.NET 模式,如下面的示例所示。

 

1.3 使对象可序列化

  进行序列化的操作,需要:一个带序列化的对象,一个是包含序列化对象的流和一个序列化器(Formatter)。

  我们进行编写的类中,默认会给类加上 SerializableAttribute 特性的,当然你也可以使用显式的方式进行添加。当你打算对一个对象进行序列化时,如果它不包含 SerializableAttribute,将会引发异常。在某些时候,我们可能不希望类中的某些字段或属性进行序列化操作,你可以在该字段或属性上使用 NonSerializedAttribute ,以告诉序列化器不对它们进行序列化操作。

 

  【备注】如果已序列化的类中包含了其他类对象的引用,而这些类又恰巧也有
SerializableAttribute,那么它们也会被进行序列化。

  【备注】序列化的关键字特性:SerializableAttribute、NonSerializedAttribute。

 

  这里介绍一种常见的序列化和一种不常见的序列化:

  (1)二进制序列化:使用二进制编码来生成精简的序列化,会序列化所有成员,并提高性能。常用于存储和
socket 传输。

  (2)XML
序列化:可读性更高,也就表示有更高的灵活性,操作的便利性。可用 XmlSerializer 进行序列化操作。

 

 


C# 基础回顾系列

  《C# 知识回顾 – 序列化》

  《C# 知识回顾 – 表达式树 Expression Trees》

  《C# 知识回顾 – 特性 Attribute》、《剖析 AssemblyInfo.cs –
了解常用的特性 Attribute》

  《C# 知识回顾 – 委托 delegate》、《C# 知识回顾 – 委托 delegate
(续)》

  《C# 知识回顾 – 事件入门》、《C# 知识回顾 – Event 事件》

  《string 与 String,大 S 与小 S 之间没有什么不可言说的秘密》

 

 


【博主】反骨仔

【出处】

【参考】微软官方文档

 

] C# 知识回顾,
你真的懂异常(Exception)吗? 目录 异常介绍 异常的特点 怎样使用异常
处理异常的 try-catch-finally 捕获异常的 Catch 块 释…

二、采用 EventHandler 模式发布事件

  1.(如果不需要与事件一起发送自定义数据,请跳过此步骤,进入步骤
3a。)在发行者类和订阅方类均可看见的范围中声明自定义数据的类。然后添加保留您的自定义事件数据所需的成员。

1     class MyEventArgs : EventArgs
2     {
3         public string Message { get; private set; }
4 
5         public MyEventArgs(string message)
6         {
7             Message = message;
8         }
9     }

 

  2.(如果您使用的是 EventHandler<TEventArgs> 的泛型版本,请跳过此步骤。)在发布类中声明一个委托。  为它指定以
EventHandler 结尾的名称。  第二个参数指定自定义
EventArgs 类型。 

    delegate void MyEventHandler(object sender, MyEventArgs args);

 

  3.使用以下任一步骤,在发布类中声明事件。

    (1)如果没有自定义
EventArgs 类,事件类型就是非泛型 EventHandler 委托。无需声明委托,因为它已在创建
C# 项目时包含的 System 命名空间中进行了声明。将以下代码添加到发行者类中。 

public event EventHandler MyEvent;

 

    (2)如果使用的是 EventHandler 的非泛型版本,并且您有一个由 EventArgs 派生的自定义类,请在发布类中声明您的事件,并且将来自步骤
2 的委托用作类型。

public event MyEventHandler MyEvent;

 

    (3)如果使用的是泛型版本,则不需要自定义委托。相反,在发行者类中,您应将事件类型指定为 EventHandler<MyEventArgs>,将尖括号中的内容替换为自己的类的名称。  

public event EventHandler<MyEventArgs> MyEvent;

 

二、通过序列化保存对象数据

  这里使用
demo 进行简单演示如何对对象进行序列化和反序列化操作。

 

三、一个简单的发布订阅 Demo

  下面的示例通过将自定义的 MyEventArgs
类和 EventHandler<TEventArgs> 进行演示:

This is MyEventArgs.cs  //事件参数

 1     /// <summary>
 2     /// 事件参数
 3     /// </summary>
 4     /// <remarks>一个自定义的类:自定义事件的参数</remarks>
 5     class MyEventArgs : EventArgs
 6     {
 7         public string Message { get; }
 8 
 9         public MyEventArgs(string message)
10         {
11             Message = message;
12         }
13     }

 

This is Publisher.cs  //发布者

 1     /// <summary>
 2     /// 事件发布者
 3     /// </summary>
 4     class Publisher
 5     {
 6         //声明一个泛型事件
 7         public event EventHandler<MyEventArgs> MyEvent;
 8 
 9         public void Publish()
10         {
11             Console.WriteLine("Publis is starting");
12 
13             //你可以在事件触发前写些代码
14 
15             OnMyEvent(new MyEventArgs(DateTime.Now.ToString()));
16         }
17 
18         /// <summary>
19         /// 触发事件
20         /// </summary>
21         /// <param name="args"></param>
22         /// <remarks>虚方法,允许子类重写调用行为</remarks>
23         protected virtual void OnMyEvent(MyEventArgs args)
24         {
25             //只有在事件订阅时(!= null),才触发事件
26             MyEvent?.Invoke(this, args);
27         }
28     }

 

This is Subscriber.cs  //订阅者

 1     /// <summary>
 2     /// 订阅者
 3     /// </summary>
 4     class Subscriber
 5     {
 6         public Guid Guid { get; }
 7 
 8         public Subscriber(Publisher publisher)
 9         {
10             Guid = Guid.NewGuid();
11             //使用 C# 2 的语法进行订阅
12             publisher.MyEvent += Publisher_MyEvent;
13         }
14 
15         /// <summary>
16         /// 事件处理程序
17         /// </summary>
18         /// <param name="sender"></param>
19         /// <param name="args"></param>
20         private void Publisher_MyEvent(object sender, MyEventArgs args)
21         {
22             Console.WriteLine($"    Message is {args.Message}, Guid is {Guid}.");
23         }
24     }

 

This is Program.cs   //控制台,用于启动

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             var publisher = new Publisher();
 6             var subscriber1 = new Subscriber(publisher);
 7             var subscriber2 = new Subscriber(publisher);
 8 
 9             //触发事件
10             publisher.Publish();
11 
12             Console.WriteLine("OK!");
13             Console.Read();
14         }
15     }

 四、实现自定义事件访问器

  事件是特殊类型的多路广播委托,只能从声明它的类中调用。客户端代码通过提供对应在引发事件时调用的方法的引用来订阅事件。这些方法通过事件访问器添加到委托的调用列表中,事件访问器类似于属性访问器,不同之处在于事件访问器被命名为 add 和 remove。在大多数情况下都不需要提供自定义的事件访问器。如果您在代码中没有提供自定义的事件访问器,编译器会自动添加事件访问器。但在某些情况下,您可能需要提供自定义行为。示例如下:

 1     class MyClass
 2     {
 3         /// <summary>
 4         /// 锁
 5         /// </summary>
 6         private static object Locker = new object();
 7 
 8         /// <summary>
 9         /// 接口
10         /// </summary>
11         public interface IMyEvent
12         {
13             event EventHandler OnCall;
14         }
15 
16         public class MyEvent : IMyEvent
17         {
18             /// <summary>
19             /// 触发前事件
20             /// </summary>
21             event EventHandler PreEvent;
22 
23             public event EventHandler OnCall
24             {
25                 add
26                 {
27                     lock (Locker)
28                     {
29                         PreEvent += value;
30                     }
31                 }
32                 remove
33                 {
34                     lock (Locker)
35                     {
36                         PreEvent += value;
37                     }
38                 }
39             }
40         }
41     }

 

2.1 使用序列化保存对象

图片 3图片 4

 1     [Serializable]  //将类标记为可序列化
 2     public class Coupon : INotifyPropertyChanged
 3     {
 4         public decimal Amount { get; set; }
 5 
 6         public float InterestRate { get; set; }
 7 
 8         public int Term { get; set; }
 9 
10         private string _name;
11 
12         public string Name
13         {
14             get { return _name; }
15             set
16             {
17                 _name = value;
18                 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Customer"));
19             }
20         }
21 
22         [field: NonSerialized()]    //将可序列化的类中的某字段标记为不被序列化
23         public event PropertyChangedEventHandler PropertyChanged;
24 
25         public Coupon(decimal amount, float interestRate, int term, string name)
26         {
27             Amount = amount;
28             InterestRate = interestRate;
29             Term = term;
30             _name = name;
31         }
32     }

Coupon.cs

 1         static void Main(string[] args)
 2         {
 3             const string fileName = @"demo1.txt";
 4             var coupon = new Coupon(10000, 0.2f, 1, "反骨仔");
 5 
 6             using (var stream = File.Create(fileName))
 7             {
 8                 var deserializer = new BinaryFormatter();  //二进制格式序列化器
 9                 deserializer.Serialize(stream, coupon);  //序列化对象到文件中
10             }
11         }

图片 5

图2-1 

 

  现在尝试反序列化,看看与之前 Coupon 对象的值是否一致。

 1         static void Main(string[] args)
 2         {
 3             const string fileName = @"demo1.txt";
 4             //var coupon = new Coupon(10000, 0.2f, 1, "反骨仔");
 5 
 6             //判断该文件是否存在
 7             if (!File.Exists(fileName))
 8             {
 9                 return;
10             }
11 
12             using (var stream = File.OpenRead(fileName))
13             {
14                 var deserializer = new BinaryFormatter();   //二进制序列化器
15                 var coupon = deserializer.Deserialize(stream) as Coupon;    //反序列化
16 
17                 if (coupon == null)
18                 {
19                     return;
20                 }
21 
22                 Console.WriteLine($"{nameof(Coupon)}:");
23                 Console.WriteLine($"    {nameof(coupon.Amount)}: {coupon.Amount}");
24                 Console.WriteLine($"    {nameof(coupon.InterestRate)}: {coupon.InterestRate}%");
25                 Console.WriteLine($"    {nameof(coupon.Term)}: {coupon.Term}");
26                 Console.WriteLine($"    {nameof(coupon.Name)}: {coupon.Name}");
27             }
28 
29             Console.Read();
30         }

图片 6

图2-2

 

传送门

  《C# 知识回顾 – 序列化》

  《C# 知识回顾 – 表达式树 Expression Trees》

  《C# 知识回顾 – 特性 Attribute》、《剖析 AssemblyInfo.cs –
了解常用的特性 Attribute》

  《C# 知识回顾 – 委托 delegate》、《C# 知识回顾 – 委托 delegate
(续)》

  《C# 知识回顾 – 事件入门》

 


【参考】微软官方文档

] C# 知识回顾, C# 知识回顾 – Event 事件
【博主】反骨仔 【原文】
序 昨天,通过《C# 知识回顾 – 事…

2.2 使用 SOAP 格式保存对象

 1         static void Main(string[] args)
 2         {
 3             const string fileName = @"demo1.txt";
 4             var coupon = new Coupon(10000, 0.2f, 1, "反骨仔");
 5 
 6             using (var stream = File.Create(fileName))
 7             {
 8                 var deserializer = new SoapFormatter(); //Soap 格式化器
 9                 deserializer.Serialize(stream, coupon); //序列化
10             }
11         }

图片 7

图2-3

  反序列化时也采用 SoapFormatter 即可,结果同图2-2。

                var deserializer = new SoapFormatter();   //Soap 格式化器
                var coupon = deserializer.Deserialize(stream) as Coupon;    //反序列化

  【注意】不应将这些格式用于敏感数据,如密码或信用卡信息。

  【备注】二进制格式对于大多数
Windows 应用程序均适用。对于以前来说,使用
Web 应用程序或者是 Web 服务,建议使用 SOAP 的 XML
进行传输。而现在,当然是使用大众化的 json
格式进行传输啦。

 

  同样,也可以通过 XmlSerializer 将对象序列化保存在
XML 文件。我们可以根据需求选择合适的序列化器,操作基本是一样的。

 

众说纷纭

  @hi丶小时候 使用
SerializableAttribute
特性时,是不建议使用自动属性的,序列化后的字段都是多出
k_BackingField<> 17个字符,如果对象很大会浪费一部分流量,建议使用
DataContractAttribute 和 DataMemberAttribute

  @梁逸晨 除非对方系统强制要求 SOAP
才能通信,否则该人人抵制这么反人类的东西,建议楼主 JSON 或 Protobuf

 

 


【参考】

【参考】微软官方文档

 

相关文章