【C#学习6】委托、事件、观察者模式

委托

委托的定义和使用

  1. 定义
  • 委托是一个类型,这个类型可以赋值一个方法的引用
  • 首先定义委托,需要告诉编译器我们这个委托可以指向哪些类型的方法,要定义方法的参数和返回值(方法的签名),使用关键字delegate定义,然后创建该委托的实例
  • 委托类型即可以引用静态方法,也可以引用普通方法
  1. 使用
delagate void IntMethodInvoker(int x);
class Program
    {
        private delegate string GetAString();
        static void Main(string[] args)
        {
             int x = 40 ,y=50; //所有的类型都是继承object的
            //string str = x.ToString(); //tostring用来将数据转换为字符串
            //通过委托类型调用一个方法,跟直接调用这个方法作用是一样的
            
            //初始化的两种方法,使用委托类型创建实例,使de指向tostring方法
            GetAString de1 = new GetAString(x.ToString);
            GetAString de2 = y.ToString;

            //调用的两种方法
            Console.WriteLine(de1());
            Console.WriteLine(de2.Invoke());

			Console.ReadKey();
        }
    }
  • 委托类型可以当作参数传递
class Program
    {
        static void Main(string[] args)
        {
            //委托类型可以当作参数传递
            PrintString method = Method1; //赋初值,使method指向Method1
            PrintStr(method);
            method = Method2;
            PrintStr(method);

            Console.ReadKey();
        }
      
        private delegate void PrintString();
        static void PrintStr(PrintString print)
        {
            print();
        }
        static void Method1()
        {
            Console.WriteLine("Method1");
        }
        static void Method2()
        {
            Console.WriteLine("Method2");
        }
    }

Action委托

  • 除了我们自己定义的委托之外,系统还给我们提供内置的委托类型。Action委托可以指向一个没有返回值,没有参数的方法。
class Program
    {
        static void Main(string[] args)
        {     
        	//Action是系统内置(预定义)的一个委托类型,它可以指向一个没有返回值/参数的方法
            Action action1 = PrintString; 
            action1();

			//泛型Action委托,可以指向一个指定类型参数的方法
            Action<int> action2 = PrintInt;
            action2(2);

			//若方法重名,系统会自动匹配泛型Action的参数类型相符合的方法
            Action<string> action3 = PrintString;
            action3("JOJO");

			//多个参数,可以有0-16个参数类型
            Action<string, int> action4 = PrintMix; 
            action4("JOJO", 4);

            Console.ReadKey();
        }

         static void PrintString()
        {
            Console.WriteLine("PrintString");
        }
        static void PrintInt(int x)
        {
            Console.WriteLine(x);
        }
        static void PrintString(string str)
        {
            Console.WriteLine(str);
        }
        static void PrintMix(string str,int x)
        {
            Console.WriteLine(str + x);
        }

    }

Func委托

  • Func委托要求指向的方法必须得有一个返回值。它可以传递0-16个参数类型,和一个返回类型。
 class Program
    {
        static int Test1()
        {
            return 100;
        }
        static int Test2(string str)
        {
            Console.WriteLine(str);
            return 2333;
        }
        static int Test3(int i, int j)
        {
            return i + j;
        }

        static void Main(string[] args)
        {
            Func<int> func1 = Test1; 
            Console.WriteLine(func1());

            //Func后面可以跟很多类型,但最后一个是返回值类型
            Func<string, int> func2 = Test2;
            Console.WriteLine(func2("bilibli"));
            Func<int, int, int> func3 = Test3;
            Console.WriteLine(func3(2, 4));

            Console.ReadKey();
        }
    }

int类型的冒泡排序

  • 对集合进行排序,冒泡排序
namespace _028_冒泡排序拓展
{
    class Program
    {
        static void Sort(int[] sortArray) //对集合进行排序,冒泡排序
        {
            bool swapped = true;
            do
            {
                swapped = false;
                //每一次for循环就交换两个数,到最后没有数可以交换了就推出while循环
                for (int i = 0; i < sortArray.Length - 1; i++) 
                {
                    if (sortArray[i] > sortArray[i + 1])
                    {
                        int temp = sortArray[i];
                        sortArray[i] = sortArray[i + 1];
                        sortArray[i + 1] = temp;
                        swapped = true;
                    }
                }
            } while (swapped);
        }
        static void Main(string[] args)
        {
            int[] sortArray = { 12, 31, 54, 87, 61, 3, 798, 46, 4, 6 };
            Sort(sortArray);
            foreach (var temp in sortArray)
            {
                Console.Write(temp + " ");
            }
            Console.ReadKey();
        }
    }
}

拓展:通用的冒泡排序

题目:比较雇员的工资,然后排序输入

Employee类

namespace _028_冒泡排序拓展
{
    class Employee
    {
        //定义雇员类的属性
        public string Name { get; set; }
        public int Salary { get; set; }

        public Employee(string name, int salary)
        {
            this.Name = name;
            this.Salary = salary;
        }

        //创建方法处理自身类型的比较
        //如果e1大于e2则返回true,否则返回false
        public static bool Compare(Employee e1, Employee e2)
        {
            if (e1.Salary > e2.Salary) return true;
            return false;
        }

        //默认输出当前的类型:命名空间+类名(继承object类的),可以选择重写
        public override string ToString()
        {
            return Name + " " + Salary;
        }
    }
}

Main方法

namespace _028_冒泡排序拓展
{
    class Program
    {
         //泛型+委托
        //因为排序的数据类型不确定,所以使用泛型
        //重要!!:如果要比较其他的属性,需要重写Compare方法,然后当作委托类型的参数进行传递(当作排序的if条件)
        static void CommonSort<T>(T[] sortArray,Func<T,T,bool>compareMethod)
        {
            //每次比较的时候,把数组传递过来,还有一个比较的方法
            //基本的判断方法没有变化,还是冒泡排序的方法
            bool swapped = true;
            do
            {
                swapped = false;
                for (int i = 0; i < sortArray.Length - 1; i++)
                {
                    if (compareMethod(sortArray[i],sortArray[i+1]))
                    {
                        T temp = sortArray[i];
                        sortArray[i] = sortArray[i + 1];
                        sortArray[i + 1] = temp;
                        swapped = true;
                    }
                }
            } while (swapped);
        }

        static void Main(string[] args)
        {
            Employee[] employees = new Employee[]//Employee类型的数组
                {
                    new Employee("1A",22),
                    new Employee("2A",2345),
                    new Employee("2B",35),
                    new Employee("9S",223),
                    new Employee("S",75)
                };

            //泛型类型为Employee,传入的数组也是Employee类型,所以foreach的时候需要声明一个对象
            CommonSort<Employee>(employees, Employee.Compare);
            foreach (Employee em in employees)
            {
                Console.WriteLine(em.Name +":"+ em.Salary);
            }
            //也可以重写ToString方法
            //foreach (Employee item in employees)
            //{
            //    Console.WriteLine(item.ToString());
            //    //Console.WriteLine(item); 默认会输出item.ToString()
            //}

            Console.ReadKey();
        }
    }
}

多播委托

  • 前面使用的委托都只包含一个方法的调用,但是委托也可以包含多个方法,这种委托叫做多播委托。使用多播委托就可以按照顺序调用多个方法多播委托只能得到调用的最后一个方法的返回值,所以一般我们把多播委托的返回值声明为void
  • 多播委托包含一个逐个调用的委托集合,如果通过委托调用的其中一个方法抛出异常,整个迭代就会停止。
namespace _029_多播委托
{
    class Program
    {
        static void Test1()
        {
            Console.WriteLine("Test1");
            //throw new Exception(); 
            //抛出异常,多播委托要是先调用的方法出现异常,后面的不执行
        }
        static void Test2()
        {
            Console.WriteLine("Test2");
        }
        
        //有参数、返回值的方法
        static string Test3(int x)
        {
            Console.WriteLine(x);
            return "Test3";
        }
        static string Test4(int y)
        {
            Console.WriteLine(y);
            return "Test4";
        }

        static void Main(string[] args)
        {
            Action action1 = Test1;
            action1 += Test2; //添加一个委托的引用
            //action -= Test1; //删除一个委托的引用
            //action -= Test2;
            if (action1!=null) //当一个委托没有指向任何方法的时候,调用的会出现异常null
            action1();

            //引用多个 带有参数、返回值的方法,只会返回最后一个返回值
            Func<int,string> action2 = Test3; 
            action2 += Test4;
            Console.WriteLine(action2(2333));

            //取得多播委托中所有方法的委托
            //Delegate[] delegates = action.GetInvocationList();
            //foreach (Delegate de in delegates)//遍历多播委托中所有的委托,然后单独调用
            //{
            //    de.DynamicInvoke(null); //可以传递参数调用不同的方法
            //}

            Console.ReadKey();
        }
    }
}
Test1
Test2
2333
2333
Test4

匿名方法

	//匿名方法,本质上是一个方法,只是没有名字
	//任何使用委托变量的地方都可以使用匿名方法赋值
	Func<int, int, int> plus = delegate (int arg1, int arg2) //关键字delegate
	{
	     return arg1 + arg2;
	 }; //加分号
	 
	 //相当于
	 static int Plus(int arg1,int arg2)
	 {
	 	return arg1 + arg2;
	 }
	 Func<int,int,int> plus = Plus;

Lambda表达式

  • Lambda表达式表示一个方法的定义,可以使用Lambda表达式代替匿名方法
  • Lambda运算符“=>”的左边列出了需要的参数,如果是一个参数可以直接写a=>(参数名自己定义),如果多个参数就使用括号括起来,参数之间以","间隔
Func<int, int, int> test1 = (arg1, arg2) => //lambda表达式的参数不需要声明类型
	{
	    return arg1 + arg2;
	}; //加分号  
	
Console.WriteLine(test1(20,30));
  • 如果Lambda表达式只有一条语句,在方法内就不需要花括号和return语句,编译器会自动添加return语句
//lambda表示的参数只有一个的时候,可以不加上括号()
//当函数体语句只有一句的时候,可以不加大括号{},也可以不加return语句
Func<int, int> test2 = a => a + 1;
Func<int, int> test3 = (a) => { return a + 1; };

Console.WriteLine(test2(22));
Console.WriteLine(test3(22));

Lambda表达式外部的变量

  • 通过Lambda表达式可以访问Lambda表达式外部的变量,但是如果不能正确使用,也会非常危险。(因为不但受到参数控制,还受到变量的控制,结果不可控)
int somVal = 5;
Func<int, int> test4 = x => x + somVal;
Console.WriteLine(test4(4));

事件

  1. 定义
  • 事件(event)基于委托,为委托提供了一个发布/订阅机制,是类或对象 向其他的类或对象 通知发生的事情的 一种特殊签名的委托
  • 事件的声明只能作为类里面的一个成员来声明,无法声明为局部,使用方法跟委托没什么区别
  1. 事件的声明
  • public event 委托类型 事件名
  • 事件使用event关键词来声明,他的返回类值是一个委托类型
  • 通常事件的命名,以名字+Event作为它的名称,在编码中尽量使用规范命名,增加代码的可读性
namespace _031_事件
{
    class Program
    {
        public delegate void MyDelegate();

        //public MyDelegate mydelegate; //声明一个委托类型的变量,作为类的成员
        public event MyDelegate mydelegate; //事件的声明只能作为类里面的一个成员来声明,无法声明为局部
        static void Main(string[] args)
        {
            Program p = new Program();
            p.mydelegate = Test1;
            p.mydelegate();

            Console.ReadKey();
        }
        static void Test1()
        {
            Console.WriteLine("Test1");
        }
    }
}

观察者设计模式-猫抓老鼠

C#设计模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象这个主题对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为

  1. 事件与委托的联系和区别
  • 事件是一种特殊的委托,或者说是受限制的委托 ,是委托的一种特殊应用,只能使用"+=" "-="操作符,二者本质上是一个东西。(不能在类的外部调用,但是可以进行注册)
  • event ActionHandler Tick; //编译成创建一个私有的委托示例,和施加在其上的add,remove方法
  • event只允许用add,remove方法来操作,这导致了它不允许在类的外部被直接触发,只能在类的内部适合的时机触发委托可以在外部被触发,但是别这么用。
  • 使用中,委托常用来表达回调,是事件表达外发的接口。(给别人提供注册)
  1. 实例
  • Cat类
namespace _032_观察者设计模式_猫捉老鼠
{
    class Cat
    {
        private string name;
        private string color;
        public Cat(string name, string color)
        {
            this.name = name;
            this.color = color;
        }
        /// <summary>
        /// 猫的状态发生改变(被观察者的状态发生改变)
        /// </summary>
        //使用委托给观察者发消息
        public event Action catCome; //声明一个事件(发布消息)
        public void CatCome()
        {
            Console.WriteLine(color + "的" + name + "准备来抓老鼠啦!");
            if (catCome != null) catCome();
        }

        //当新增了观察者的对象后,被观察者需要需要修改代码,这样代码维护起来比较麻烦,耦合度高(互相调用)
        //被观察者类开发完成,功能固定后,尽量减少对其修改
        //不在被观察者内调用观察者,因为其数量等都不确定
        //被观察者模式发生改变,只有观察者自身知道
        //public void CatComing(Mouse mouse1,Mouse mouse2,Mouse mouse3)
        //{
        //    Console.WriteLine(color + "的" + name + "准备来抓老鼠啦!");
        //    mouse1.RunAway();
        //    mouse2.RunAway();
        //    mouse3.RunAway();
        //}
    }
}
  • Mouse类
namespace _032_观察者设计模式_猫捉老鼠
{
    /// <summary>
    /// 观察者类:老鼠
    /// </summary>
    class Mouse
    {
        private string name;
        private string color;
        public Mouse(string name, string color,Cat cat)
        {
            this.name = name;
            this.color = color;
            //把自身的逃跑方法 注册进 猫里面(订阅消息)
            cat.catCome += this.RunAway;
        }
        /// <summary>
        /// 逃跑功能
        /// </summary>
        public void RunAway()
        {
            Console.WriteLine(color + "的" + name + "说:猫来啦,快跑啊~~");
        }
    }
}
  • Main方法
namespace _032_观察者设计模式_猫捉老鼠
{
    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat("Tom", "蓝色");

            Mouse mouse1 = new Mouse("Jack", "棕色",cat);
            Mouse mouse2 = new Mouse("Jakkie", "黑色",cat);
            Mouse mouse3 = new Mouse("Zack", "红色",cat);
            //注册事件,把Mouse类的RunAway方法添加进Cat类里的catCome委托
            //cat.catCome += mouse1.RunAway; 
            //cat.catCome += mouse2.RunAway;
            //cat.catCome += mouse3.RunAway;
            cat.CatCome();

            //事件不能在类的外部触发,只能在类的内部触发(委托可以在类的外部被触发,很危险)
            //cat.catCome(); 

            //在cat中调用了观察者的方法,当观察者发生改变时,需要同时修改被观察者的代码
            //cat.CatComing(mouse1,mouse2);
            Console.ReadKey();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值