@TOC


前言

搞编程的,基础不牢,地动山摇,多多复习哦,与诸君共勉。

一、委托和事件

1.简略八股

  • 事件
    • 用于对象或类之间的信息传递(消息推送)、动作协调
      • 例子。对象Student拥有一个事件E1:意思就是当事件E1发生时,对象Student有能力通知别的对象一些消息【可以理解为类或者对象之间通信、同步信息】
    • 事件不会主动发生,只会被触发

2.事件的使用经验/个人理解

  • 建一个window forms或者WPF程序,在里面建好textbox及button等,然后调出button的属性,点那个闪电符号,也可以找到button的一些微软给咱们提供的事件,可以找到咱们需要的click事件,也可以自定义事件处理器【其实就是自己重新起个名字,然后点击或者双击进入自定义的事件处理器回调函数中,编写逻辑即可】,双击,就进入到事件处理器那个回调方法中了,然后编辑逻辑即可。当然啦,你也可以 += 连续按两下tab键,也可以生成事件处理器,两种方法都可以
  • 事件的拥有者.事件 += 事件的响应者.事件处理器。+=代表订阅事件。用+=把+=右边的函数挂到+=左边的事件上,按tab键/ctrl+.实现。也就是当+=左边的这个事件发生时+=右边的函数就会执行。+=右边这个方法响应+=左边这个事件,所以+=右边的函数或者方法也叫事件处理器
  • 事件用法或者说写法+怎么理解以委托为基础呢?或者说为什么要使用委托类型来声明事件【有三层意思:事件需要用委托类型做一个约束(这个约束规定了事件可以发送什么样的消息给事件响应者,也规定了事件响应者能收到什么样的消息,这就决定了事件响应者的事件处理器必须能够跟约束匹配上,然后才能订阅这个事件)、当事件响应者向事件拥有者提供了能够匹配这个事件的事件处理器后,总得找个地方把这个事件处理器保存或记录下来,能够记录或保存方法的任务也只有委托类型的实例才能做到

3.事件常见用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(左边) += new EventHamdler(this.ButtonClicked),用委托绑定了咱们的回调函数:
namespace WindowsFormsExercise
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.button3.Click += new EventHandler(this.ButtonClicked);
}

private void ButtonClicked(object sender, EventArgs e)
{
...
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace WindowsFormsExercise
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.button3.Click += (object sender, EventArgs e) => {
this.textBox1.Text = "8.2Demo";
};
}
}
}

//等价写法,或者说优化写法
//省略Lambda表达式中的形参列表的类型...namespace WindowsFormsExercise
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.button3.Click += (sender, e) => {
this.textBox1.Text = "8.2Demo";
};
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
namespace WpfExercise
{
/// <summary>
/// StepOne.xaml 的交互逻辑
/// </summary>
public partial class StepOne : Window
{
public StepOne()
{
InitializeComponent();
this.average.Click += this.HbClickedEvent;
}


private void JumpToStepTwo(object sender, RoutedEventArgs e)
{
StepTwo stepTwo = new StepTwo();
stepTwo.Show();
}

private void HbClickedEvent(object sender, RoutedEventArgs e)
{
this.HuBox.Text = "8.2,first wpf";
}
}
}

<Window x:Class="WpfExercise.StepOne"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfExercise"
mc:Ignorable="d"
Title="StepOne" Height="450" Width="800">
<Grid>
<Button Content="进入步骤二:" HorizontalAlignment="Left" Margin="638,378,0,0" VerticalAlignment="Top" Width="154" Height="41" Click="JumpToStepTwo"/>
<Button x:Name="average" Content="Click Me,show the average value." HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Top" Width="782" Height="97" Click="HbClickedEvent"/>
<TextBox x:Name="HuBox" HorizontalAlignment="Left" Height="120" Margin="10,97,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="772"/>

</Grid>
</Window>

  • 自定义事件

    • 事件完整声明【事件通过封装对外界隐藏了委托实例的大部分功能,仅仅暴露了add、remove事件处理器的功能】

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      ...
      /**
      * 这个委托字段就是用来存储或者引用那些事件处理器的
      */
      private OrderEventHandler orderEventHandler;

      /**
      * 事件Order
      * 声明咱们的事件,首先希望在外界也能访问,所以是public
      * 声明事件的关键字是event,告诉编译器说我现在声明的是事件
      * 用哪个委托类型来约束这个事件呢,要不自定义的要不C#自带的,这里是咱们自定义的OrderEventHandler
      * 事件名字,这里是Order,再跟一对{}
      */
      public event OrderEventHandler Order
      {
      /**
      * 事件处理器的添加器
      * value跟着占位符一样,此时我不知道我该用什么事件处理器,所以先用value把空占着
      */
      add
      {
      this.orderEventHandler += value;
      }

      /**
      * 事件处理器的移除器
      */
      remove
      {
      this.orderEventHandler -= value;
      }

      }
      ...
    • 事件简略声明方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * 这个委托字段就是用来存储或者引用那些事件处理器的
    */
    private OrderEventHandler orderEventHandler;

    /**
    * 事件Order
    * 声明咱们的事件,首先希望在外界也能访问,所以是public
    * 声明事件的关键字是event,告诉编译器说我现在声明的是事件
    * 用哪个委托类型来约束这个事件呢,要不自定义的要不C#自带的,这里是咱们自定义的OrderEventHandler
    * 事件名字,这里是Order,再跟一对{}
    */
    public event OrderEventHandler Order;

4.从小项目中继续学习委托与事件

  • 有类似需求,比如一个事件点击完成或者执行完成后进入另一个事件执行,然后执行完成之后再进入另一个事件执行,不能事件一个套一个,可以考虑一下流水线工作机制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    while(workLine.ToNextAction())
    workLine.DoCurrentAction()
    ...
    var results = new List<bool>();
    while(workLine.ToNextAction())
    {
    results..Add(workLine.DoCurrentAction());
    }
    Msg.Show($"流水线总共有{workLine.Count}个动作,成功执行了{results.Where(x => x == true).Count()}个动作。");

5.事件处理程序基于委托,当咱们需要将一个函数作为另一个函数的参数时要想到用委托

  • 委托相关
    • 委托的一个注意点是:定义委托时的返回值和参数列表要和被委托函数的返回值和参数列表一致,只有完全匹配,.NET编译器才会进行成功转换。委托的引用名也相当于被委托函数的别名
    • 是一种特殊的类【和类一样同属于引用数据类型】,帮我们完成对这些方法的间接调用
    • .net规定,如果委托是为声明某个事件做准备,那么这个委托命名为XxxEventHandler,Xxx是事件的名字
    • 两种调用方法:
      • 调用委托后,直接方法名(….);
      • 方法名.Invoke(…);也行
    • C#中常见委托【委托重载了很多种,咱们根据咱们的形参列表选择适配的就行【有返回值的咱们一般选Func<>委托,没有返回值的咱们一般选Action<>委托】】
      • Action<>
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        例子:namespace EventAndDelegateAndLambdaExercise
        {
        class Program
        {
        static void Main(string[] args)
        {
        Calculator cal = new Calculator();
        cal.Report1();//调用方式一:直接调用方法
        Console.WriteLine("============");
        Action action2 = new Action(cal.Report2);//表示咱们用Action委托指向了Calculator.Report2方法
        action2.Invoke();//委托调用方式一
        Console.WriteLine("============");
        Action action3 = new Action(cal.Report3);//表示咱们用Action委托指向了Calculator.Report2方法
        action3();//委托调用方式二
        Console.WriteLine("============");
        Console.ReadLine();
        }
        }

        class Calculator
        {
        public void Report1()
        {
        Console.WriteLine("huhb learn event1");

        }
        public void Report2()
        {
        Console.WriteLine("huhb learn event2");

        }
        public void Report3()
        {
        Console.WriteLine("huhb learn event3");
        //Console.ReadLine();
        }
        }
        }
      • Func<>
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        //例子1......namespace EventAndDelegateAndLambdaExercise
        {
        class Program
        {
        static void Main(string[] args)
        {
        //Action temp = PrintName;
        //temp.Invoke();
        Func<double, double, double> addMethod = Add;
        var res = addMethod(100.1, 200.2);
        Console.WriteLine(res);
        Console.ReadLine();
        }

        static double Add(double a, double b)
        {
        return a + b;
        }
        }
        }

        ...

        //例子2......namespace EventAndDelegateAndLambdaExercise
        {
        class Program
        {
        static void Main(string[] args)
        {
        Calculator calculator = new Calculator();
        Func<double, double, double> AddRes = new Func<double, double, double>(calculator.Add);
        double x = 100.1;
        double y = 200.2;
        double z = 0D;
        z = AddRes(x, y);
        Console.WriteLine(z);
        Console.ReadLine();
        }


        }

        class Calculator
        {
        public double Add(double a, double b)
        {
        return a + b;

        }
        }
        }
        1
        2
        3
        4
        delegate string Func(string title, int number)...
        //等价写法
        Func f = (title, number)=>{return $"{title},{number}"}
        //其实就是等价于Func<string, int, string>
    • 自定义委托
      • 格式:public delegate double 委托名Demo(double x, doublie y);
        • 这种形式就等价于Action<double, doouble>
      • 委托必须与所封装的或者说包装的方法保持“类型兼容”。【返回值的数据类型一致、参数列表在个数和数据类型上一致,参数名字不需要一模一样】
      • C#允许委托嵌套在目标类中,要注意,此时如果调用委托的代码在目标类中,那无所谓,按照原来委托的写法调用委托即可。如果在目标类之外,那么需要目标类.委托类名去实例化,然后再调用,因为人家委托就是个类呀,相当于一个内部类一个外部类呀,你调用就得注意点,目标类中调用可省外部目标类,其他类中调用就不能省略目标类了
    • 如果你正定义一个稍后不需要删除的事件处理程序,请使用 lambda 表达式
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      /// <summary>
      /// 定义AnimalPlayDelegate委托
      /// </summary>
      /// <param name="AnimalName"></param>
      delegate void AnimalPlayDelegate(string AnimalName);

      /// <summary>
      /// 定义一个用来运行委托AnimalPlayDelegate的工具类
      /// </summary>
      /// <param name="animalPlayDelegate"></param>
      /// <param name="name"></param>
      static void RunPlay(AnimalPlayDelegate animalPlayDelegate, string name)
      {
      animalPlayDelegate(name);
      }
      • 用法一:委托是一种引用类型,用法一和类有点类似,先实例化委托,然后要要委托的方法传到实例化时的初始化对象中,此时不同的是就不传方法原来的参数列表了。
        1
        2
        3
        IAnimalPlay dogPlay = new DogPlay();
        AnimalPlayDelegate dogPlayDelegate = new AnimalPlayDelegate(dogPlay.StartPlay);
        RunPlay(dogPlayDelegate, "Dog");
      • 用法二:省去创建委托实例并传入参数的过程
        1
        2
        IAnimalPlay dogPlay = new DogPlay();
        RunPlay(dogPlay.StartPlay, "Dog new format");
    • 多播委托
      1
      2
      3
      4
      5
      6
      7
      //向一个委托中注册多个函数,包含多个函数的委托就叫多播委托:通过+=运算符向多播委托中注册多个函数
      IAnimalPlay dogPlay = new DogPlay();
      // 用法一,实例化委托
      AnimalPlayDelegate dogPlayDelegate = new AnimalPlayDelegate(dogPlay.StartPlay);
      dogPlayDelegate += new AnimalPlayDelegate(new CatPlay().StartPlay);
      dogPlayDelegate += new AnimalPlayDelegate(new LionPlay().StartPlay);
      RunPlay(dogPlayDelegate, "dog cat and lion");
    • 使用匿名函数创建委托实例
      1
      2
      3
      4
      5
      6
      7
      //使用匿名函数创建委托实例
      AddDelegate addDelegate = delegate (double x, double y)
      {
      return 3 * x + 5;
      };
      double res = AddDelegateRunMethod(addDelegate, 11.1, 22.2);
      Console.WriteLine("result = {0}", res);
  • 事件相关
    • Window应用程序时事件驱动的,比如写一个按钮,事件发送者就是按钮,事件接收者就是按钮所在窗体。当事件发生时系统如何找到对应的事件处理程序.NET中咱们自己可以自定义事件处理程序的名称,所以通过名字找肯定不是。是通过委托机制运行事件处理程序。
      • 系统通过这条语句把咱们定义的事件处理程序注入到事件上:nyButton.click += new System.EventHandler(this.myButton_Click);此时事件处理程序myButton_Click的引用备保存在myButton.Click事件的调用列表中,myButton.Click事件相当于事件处理程序myButton_Click的别名
    • 事件本质上就是一个委托,那么多播委托等类似与传递性依旧存在
      • 可以在一个事件中注册多个事件处理程序
      • 也可以将同一个事件处理程序注册到多个控件中
    • .NET中预定义了很多专门用于事件的委托类型
      • EventHandler: public delegate void EventHandler(object sender, EventArgs e) //sender代表发送事件的那个发送者.EventArgs e代表事件本身的参数
      • KeyEventHandler、MouseEventHandler
    • 使用步骤:
      • 1、声明关于事件的委托
      • 2、声明事件
      • 3、编写触发事件的函数
      • 4、创建事件处理程序
      • 5、注册事件处理程序
      • 6、在适当条件下触发事件
        1
        2
        3
        4
        5
        两个按钮控制一个Label的例子。创建一个事件驱动程序的步骤:用的默认的
        1&2public delegate void EventHandler(object sender, EventArgs e);
        3&4&5this.buttonOne.Click += new System.EventHandler(this.Button_Click);
        this.buttonTwo.Click += new System.EventHandler(this.Button_Click);
        Button_Click {...}

6.杰哥给的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//杰哥关于事件、委托的指导......
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsForms0803
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += button1_Click;
button1.Click += new EventHandler(button1_Click);
button1.Click += (s, e) =>
{
button1_Click(s, e);
};

//button1.Click += (s, e) => button2_Click(s.GetType().Name);
//label1.Click += (s, e) => button2_Click(s.GetType().Name);
MyEventNotify.BtnTextEvent += IfIRecvButtonText;

button1.MouseDown += Button1_MouseDown;
}

private void Button1_MouseDown(object sender, MouseEventArgs e)
{

MyEventNotify.NotifyBtnText($"我点击的按钮名:{(sender as Button).Text},点击的鼠标键位是:{e.Button}");
}

private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("111");
}

private void button2_Click(string controlType)
{
switch(controlType)
{
case "Button":
MessageBox.Show("你点击了一个按钮");
break;
case "Label":
MessageBox.Show("你点击了一个标签");
break;
}
}

private void IfIRecvButtonText(string btnText)
{
label1.Text = btnText;
}
}


public static class MyEventNotify
{
public static event MyBtnTextDelegate BtnTextEvent;

public static void NotifyBtnText(string btnText)
{
//if(BtnTextEvent != null)
//{
// BtnTextEvent(btnText);
//}

// =>

BtnTextEvent?.Invoke(btnText);
}
}


public delegate void MyBtnTextDelegate(string btnText);
}

  • 事件尽量不要嵌套:比如event1=>event2=>event3=>event1,这种后期代码不利于代码扩展、理解与调试,尽量用switch…case等逻辑替代嵌套逻辑,switch(type)中的type可以作为一个开关,所以要考之前嵌套的事件之间的关系怎么用type来表示

二、Lambda:【省略方法名 + 省略形参列表中的参数类型】

  • System.Reflection.Assembly类是为控制程序集【获取程序集元数据信息】而设计。在获取程序集元数据之前需要先用Assembly对象的Load()方法把程序集加载到内存
  • 动态加载、动态调用、实现插件、System.Activator.CreateInstance(XxxType, null);XxxType代表咱们用.GetType获取的类,null代表构造函数的参数、xxx as xxx,强制类型转换,前面大后面小【强制类型转换是为了得到对象的方法,咱们也可以用Type.GetMethod(“欲调用方法名”)】

三、可空类型:可以为空的值类型

1.遇到不处理的异常,比如Xxx.A().B().C().D(),如果你要按照正常逻辑来写的话,就得每个方法的返回值都得判断一下,代码就很冗余了,用?.能够使得代码看起来很健壮

1
Type? type = Type.GetType("System.Double");

四、正则表达式

1.通配符:如Huhb\d\d\d代表Huhb开头以三个数字结尾的那一类字符串

1
2
3
4
5
6
7
\d匹配数字0-9
\w匹配单词字符,包括大小写26+26字母,数字0-9和下划线
\s匹配任何空白字符,空格、制表符、换页符
\D匹配任何非数字字符
\W匹配任何非单词字符
\S匹配任何非空白字符
.匹配除换行(\n)外的任何字符
1
2
3
4
.NET与正则表达式相关类,System.Text.RegularExpressions.Regex类。
Regex exp = new Regex(...);
MatchCollection matcher = exp.Matches(girls);
exp.Match()、exp.NextMatch()

2.可选字符集

1
2
3
4
5
6
7
8
9
10
11
12
[A-DM-P]表示文职可以出现A到D或M到P
[1-5]、[A-F]可以出现15,A到F
|表示或,^表示非,找除...以外的所有字符
*将前面的字符重复0次或多次去寻找
+将前面的字符重复1次或多次
?将前面的字符重复0次或1
{n}将前面的字符重复n遍
{n.}将前面的字符至少重复n遍
{n, m}将前面的字符重复n到m遍
^在整个字符串的首部寻找匹配的子串
¥在整个字符串的尾部寻找匹配的子串
\b来检索以特定字符开头或结尾的单词

五、字符串

1.字符串的复制

  • str2 = str1,将str1字符串变量的引用复制了一份,使得str1和str2两个变量同时指向同一个字符串
    • 注意事项:字符串不可更改,如果str1此时被赋值了新值,那么会在内存中创建一个全新的字符串让str1指向,此时str1和str2指向不同
  • Copy()函数:会创建一个新字符串,新旧变量指向不同的字符串常量
  • CopyTo()函数,常用于把字符串的一部分复制到另一个字符数组

2.字符串的比较

  • ==
    • 比较str1和str2是否相等,区分大小写(Hel !== hel)
  • Equals()与==相等,两个字符串相等返回true不等返回false
  • string.CompareTo()函数,用来比较两个字符串的大小,前面的小等大依次返回返回-1、0、1
  • 验证字符串首尾
    • string.StartsWith(“xxx”)判断字符串是否以特定子串xxx开头
    • string.EndsWith(“xxx”)判断字符串是否以特定子串xxx结尾

3.字符串的字符截取、子串截取、定位

  • IndexOf()可找到指定字符或子串在字符串中第一次出现的位置,也可以指定搜索的起始位置及向后搜索多少字符
  • LastIndexOf()可找到指定字符或子串在字符串中最后一次出现的位置,也可以指定搜索的起始位置及向后搜索多少字符
  • IndexOfAny()函数可指定同时搜索多个指定字符,搜到任意一个指定字符即可停止搜索
  • xxx.Substring(x, y),截取从位置x开始的y个字符
  • xxx.Split(‘ ‘),比如以空格为分隔符
  • ToUpper()、ToLower()更改大小写
  • string.Insert()、string.Replace()、string.Remove()生成并返回一个新字符串可以在指定位置操作。string.Format(…)可在字符串中插入变量,并可设置变量格式

4.StringBuilder类

  • 不可修改,修改String类对象相当于创建一个新字符串,如果频繁操作内存不堪重负。StringBuilder是可变字符串,可对字符串本身直接修改
  • 不明确长度则StringBuilder型字符串初始容量是16的偶数倍,初始容量不足可用EnsureCapacity()加倍补余增加容量
  • Append()向字符串尾部添加字符、AppendFormat()向字符串尾部添加指定格式的字符,Insert()在字符串任意位置插入各种类型的数据,Replace()、Remove()

巨人的肩膀