一、索引器

索引器:可以对对象添加一个  this[int index]  这种属性,以后可以通过  对象[索引值]  方式进行访问。

// 标准索引器语法
public 返回值类型 this[参数类型 索引名]
{
    get { /* 获取数据 */ }
    set { /* 设置数据,value 是传入的值 */ }
}

1、索引器创建举例

1.  int 下标
using System;

// 自定义数组类
public class MyArray
{
    // 内部私有数组(对外隐藏)
    private int[] _items = new int[5];

    // 索引器:int 下标,读写 int 类型数据
    public int this[int index]
    {
        get
        {
            // 安全校验:防止索引越界
            if (index < 0 || index >= _items.Length)
                throw new IndexOutOfRangeException("索引超出范围");
            
            return _items[index];
        }
        set
        {
            if (index < 0 || index >= _items.Length)
                throw new IndexOutOfRangeException("索引超出范围");
            
            // value 是外界传入的值
            _items[index] = value;
        }
    }
}

// 测试调用
class Program
{
    static void Main()
    {
        MyArray arr = new MyArray();
        
        // 像数组一样使用索引赋值
        arr[0] = 10;
        arr[1] = 20;
        
        // 像数组一样使用索引取值
        Console.WriteLine(arr[0]); // 输出 10
        Console.WriteLine(arr[1]); // 输出 20
    }
}
2. 字符串索引
public class Student
{
    // 用字典存储属性:键=属性名,值=属性值
    private Dictionary<string, object> _data = new Dictionary<string, object>();

    // 字符串索引器
    public object this[string key]
    {
        get => _data.ContainsKey(key) ? _data[key] : null;
        set => _data[key] = value;
    }
}

// 调用
Student stu = new Student();
stu["Name"] = "张三";
stu["Age"] = 18;

Console.WriteLine(stu["Name"]); // 张三
Console.WriteLine(stu["Age"]);  // 18
3. 泛型类 + 索引器
// 泛型自定义集合
public class MyList<T>
{
    private T[] _arr = new T[10];
    
    public T this[int index]
    {
        get => _arr[index];
        set => _arr[index] = value;
    }
}

// 使用
MyList<string> list = new MyList<string>();
list[0] = "测试";

2、索引器练习

0.索引器练习0
 internal class Program
 {
     static void Main(string[] args)
     {


         ClassRoom room  = new ClassRoom();
         room.Add(new Student() { Name = "张三", Age = 10 });
         room.Add(new Student() { Name = "李四", Age = 20 });
         room[0]  = new Student() { Name="王五",Age= 30 }; //通过索引值访问并且修改了
         Console.WriteLine(room[0].Name + room[0].Age); // 通过索引值访问
         Console.WriteLine(room[1].Name + room[1].Age);
         //Console.WriteLine(room[2].Name + room[2].Age);//索引值超出界限

         //封装room["李四"]获取整个对象
         Console.WriteLine(room["李四"].Age);
         Console.WriteLine(room["王五"].Age);

         // 封装room[对象]获取名字相同的索引值
         Console.WriteLine(room[new Student() { Name="李四",Age=20}]);
     }
 }
 class Student 
 { 
     public string Name { get; set; }
     public int Age {  get; set; }
 }
//1班级类对象可以通过Add添加一个学生对象 封装Add方法实现添加学生的功能
//2需要通过  room[0]访问添加学生对象   定义这样的this[int index]属性

class ClassRoom 
{
    private List<Student> list = new List<Student>();  //创建一个存储学生的集合
    //添加学生的方法
    public void Add( Student stu)
    {
        list.Add(stu);
    }
    //索引器的写法
    //这种属性的访问方式 :对象[0]方式进行访问  room[0]
    public Student this[int index] 
    {
        //get通过list[索引值]返回指定学生对象
        get
        {
            return list[index];  // index 就是以后room[0]方式,[]的数字就是索引
        }
        set
        {   // set以后使用room[0]给属性进行修改
            // room[0] =值,走set访问器

            list[index] = value;
        }
    }

    //封装按照姓名返回整体对象的方法
    public Student this[string s]
    {
        get
        {
            //s就是传递名字  room["李四"]
            return list.Find(v => v.Name == s);
        }
    }

    //返回值索引值 :通过对象[Student s1]方式获取索引值
    public int this[Student s1]
    {
        get
        {
           int i1 = list.FindIndex(v => v.Name.Equals(s1.Name));
            return i1;
        }
    }
}
1. 索引器练习1
 internal class Program
 {
     static void Main(string[] args)
     {
         //定义学生对象
         Student stu = new Student(new string[] { "六一","高考","中考","端午"});
         Console.WriteLine(stu.Name);//默认值为stu对象的第一个

         stu[0] = "期末";
         Console.WriteLine(stu[0]);
         Console.WriteLine(stu[1]);
         Console.WriteLine(stu[2]);
         Console.WriteLine(stu[3]);
          stu[4] = "12"; //set访问器
         Console.WriteLine(stu[4]);//走get方法

     }
 }
 //定义学生类 定义姓名属性 定义构造函数传递一个数组参数
 public class Student
 {
     private string[] names = new string[4];//定义字符串数组长度为4
     public string  Name { get => names[0]; } //names第一个
     public Student(string[] s)
     {
         names = s;
     }
     public string this[int index] //使用stu[0]方式访问this[int index]属性
     {
         get
         {
             return names[index];
         }
         set
         {
             if (index>=names.Length) //证明names数组长度不过 需要长度加1
             {
                 //新数组的长度为原先数组的长度+1
                 string[] newArr = new string[names.Length+1];
                 //把原数组的元素拷贝到新数组里面
                 Array.Copy(names, newArr,names.Length);
                 //新数组的新位置添加一个元素
                 newArr[index] = value;
                 names = newArr;//
             }else
             {
                 names[index] = value; // 直接把value值赋值指定位置元素
             }            
         }
     
     }
 }
2. 索引器练习2
namespace _1作业
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //Dictionary<int, string> dic = new Dictionary<int, string>();
            //dic.Add(10, "ss");
            //dic[10] = "aa";
            //dic[10] = "aaa";
            //Console.WriteLine(dic[10]);



            //employee[22] = "fanfan";   // 员工年龄22,名字 fanfan
            //employee[22] = "luoluo"; // 员工年龄22,名字 luoluo
            //employee[23] = "Bob"; //// 员工年龄23,名字 Bob
            //employee["aa"] = 25;
            //employee["Bob"] = 29; //修改Bob的年龄为29


            //目的通过索引器进行访问对象里面数据 employee[0]
            //1 保证Employee存储多个数据,必须在类通过使用数组或者字典实现类的存储功能
            //2 保证类具备索引器属性 例如 :employee[0] => this[int index] 就是属性名;属性添加get 在写employee[0]代码get会触发;  set 在employee[0]=10代码执行set
            //3 确定索引器属性的类型
            Employee employee = new Employee();

            employee[22] = "fanfan";  //类似于添加,走set
            employee[23] = "迪迪";
            employee[24] = "宏宏";
            employee[24] = "力力"; // 如果现在有年龄重复的 进行修改

            Console.WriteLine(employee[22]+"????"); //获取22这个键对应的值
            Console.WriteLine(employee[23]);  //获取22这个键对应的值
            Console.WriteLine(employee[24]);
            Console.WriteLine(employee[25]);



            employee["aa"] = 25;
            employee["aa"] = 75;
            Console.WriteLine(employee["aa"]); //
            Console.WriteLine(employee["力力"]);//24
            Console.WriteLine(employee["迪迪"]);
        }
    }

    public class Employee
    {
        Dictionary<int, string> dic = new Dictionary<int, string>();//存储多个数据 
        //employee[22] = "fanfan";

        public string this[int index]
        {
            get
            {
                //index 相当于字典的键 取键对应的值
                Console.WriteLine(index+"sssss");
                //如果键存在返回对应的值,如果没存在返回null
                return dic.ContainsKey(index) ? dic[index] : null; 
            }
            set
            { //value就是给属性赋值的值   employee[22] = "fanfan"; "fanfan"
                Console.WriteLine(value+"---------");
                dic[index] = value;// 把字典里面值进行修改
            }
        }

        //employee["aa"] = 25 set 
        //cw(employee["aa"]) get  获取25
        public int this[string name]
        {
            get
            {
                //name是aa 其实就是姓名,根据姓名取年龄
                //foreach (var item in dic)
                //{
                //    if (item.Value == name)
                //    {
                //        return item.Key;
                //    }
                //}

                //v就是上面item
                // dic.FirstOrDefault(v => v.Value == name)   找到值等于name的键值对
                //.key 获取键

                // name="aa"    Dictionary<int, string> dic = new Dictionary<int, string>();//存储多个数据  key年龄 value名字

                return dic.FirstOrDefault(v => v.Value == name).Key;
            }
            set
            {
                //employee["aa"] = 25;
                //employee["aa"] = 75;


                //employee["aa"] = 25;
                //字典的键是唯一的,
                //根据name修改键    根据name修改年龄,
                if (dic.ContainsValue(name)) //如果之前有相同的名字 修改相同名字的年龄
                {
                    //先删除原来的 根据key删除
                    int key = this[name]; //this[name] 等同于走了get 获取key
                    dic.Remove(key);
                }
              
                //再添加新的
                dic.Add(value, name); // key 值

            }
        }



    }
3. 索引器练习3
 internal class Program
 {
     static void Main(string[] args)
     {
         DictionaryWrapper dictionaryWrapper = new DictionaryWrapper();
         dictionaryWrapper["dog"] = "狗";
         dictionaryWrapper["cat"] = "猫";
         dictionaryWrapper["elephant"] = "大象";
         dictionaryWrapper["dolphin"] = "海豚";
         dictionaryWrapper["dolphin1"] = "海豚1";


         Console.WriteLine("通过完整键访问:");
         Console.WriteLine("dog的值是:" + dictionaryWrapper["dog"]);    // 狗
         Console.WriteLine("cat的值是:" + dictionaryWrapper["cat"]);    // 猫
         Console.WriteLine("elephant的值是:" + dictionaryWrapper["elephant"]);  // 大象
         Console.WriteLine("dolphin的值是:" + dictionaryWrapper["dolphin"]);    // 海豚


         Console.WriteLine("以\"do\"开头的键的第一个值是:" + dictionaryWrapper["do", 0]);   // 狗
         Console.WriteLine("以\"do\"开头的键的第二个值是:" + dictionaryWrapper["do", 1]);
         Console.WriteLine("以\"do\"开头的键的第三个值是:" + dictionaryWrapper["do", 2]);


         
     }
 }

 //dictionaryWrapper["dog"] = "狗"

 //1 保证Employee存储多个数据,必须在类通过使用数组或者字典实现类的存储功能
 //2 保证类具备索引器属性 例如 :employee[0] => this[int index] 就是属性名;属性添加get 在写employee[0]代码get会触发;  set 在employee[0]=10代码执行set
 //3 确定索引器属性的类型
 public class DictionaryWrapper
 {
    
          
     public Dictionary<string,string> dic = new Dictionary<string,string>();

     //dictionaryWrapper["dog"] = "狗";
     public string this[string englisName]
     {
         get => dic[englisName];
         set => dic[englisName] = value;

     }
    // dictionaryWrapper["do", 0] // 
    public string this[string name,int index]
     {
         get
         {
             int num = -1; //dog
             //遍历所有的键,
             foreach (var item in dic.Keys)
             {
                 if (item.StartsWith("do"))//判断是否以do开头
                 {
                      num++;
                      if (num==index)
                      {
                         return dic[item];
                      }
                 }              
             }
             return null;

         }
     }    
 }

方法 1:LINQ 一行搞定(最推荐、最简单)

public string this[string name, int index]
{
    get
    {
        // 1. 取出所有 key 以 do 开头的值
        // 2. 跳过 index 个,取第一个
        return dic.Keys
                  .Where(k => k.StartsWith("do"))
                  .Select(k => dic[k])
                  .ElementAtOrDefault(index);
    }
}

方法 2:先把符合条件的放到 List 里(易懂)

public string this[string name, int index]
{
    get
    {
        List<string> list = new List<string>();

        foreach (var key in dic.Keys)
        {
            if (key.StartsWith("do"))
            {
                list.Add(dic[key]);
            }
        }

        // 超过范围返回 null
        if (index >= 0 && index < list.Count)
            return list[index];
        else
            return null;
    }
}

二、接口

接口(interface):主要为类提供这个规则,接口里面的成员都是未实现的,要实现接口里面的成员必须通过类或者结构体进行实现
当我们一个类实现(:)接口,必须实现接口里面所有的成员函数 和成员变量
接口不能实例化
接口可以实现多继承

 //接口的名称一般以I开头
 //书的接口
 interface IBook
 {
      int Id { get; set; }
      int  Name { get; set; }
      void F1();
 }

 //纸张的接口
 interface IPaper
 { 
     string color {  get; set; }
     void F2();
 }
 //:接口 叫实现
 //:类   叫继承
 //类可以实现多个接口
 class Book : IBook, IPaper
 {
     public int Id { get ; set ; }
     public int Name { get ; set ; }
     public string color { get ; set ; }

     public void F1()
     {
        
     }

     public void F2()
     {
        
     }
     public string StudentId { get; set; } //和IPeople的StudentId重名
 }

 interface IPeople
 {
     string StudentId {  get; set; }
 }

 class SmallBook : Book, IPeople
 {
    // SmallBook 继承了Book类, Book里面已经有了StudentId属性,子类可以通过继承关系把属性继承过     来, 也就是SmallBook已经了
   //  StudentId,所以Ipeople接口StudentId属性没必要实现,如果想实现可以new关键字进行修饰
     public  new string  StudentId { get; set; }
 }

三、多接口

namespace _04_多接口
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Test test = new Test();
            //  test.C

            IA t1=  new Test();
            t1.C = 12;
        }
    }
    interface IA
    {
        string A { get; set; }
        string B { get; set; }
        int C { get; set; }
        void Fn(int v);
    
    }
    interface IB
    {
        string B{ get; set; }
        string C { get; set; }
        string D { get; set; }
        void Fn(string  v);

    }
    class Test : IA, IB
    {
        public string A { get; set; }

//当一个类实现了多个接口的时候,如果多个接口有重名且类型也一样的属性,只需要实现一个即可
        public string B { get; set; }

//当一个类实现了多个接口的时候,如果多个接口拥有不同类型但是名称重复的属性,需要显式实现接口中的属性
//显式实现接口,不能添加访问修饰符,这个成员的访问,需要将对象标记为对应的接口类型
        int     IA.C { get; set; }
        string  IB.C { get; set; }
        public string D { get; set; }

        //因为方法可以重载,所有直接重载两个接口中的方法即可
        public void Fn(int v)
        {
        }public void Fn(string v)
        {
        }
    }
}
// 接口的多继承:如果一个类实现了接口A,接口A又实现了接口B,
// 在类里面必须得实现接口A和接口B里面的成员   
 interface IC
    {
       
        string B { get; set; }
    }

    interface IQ : IC
    {
        int C { get; set; }
    }

    class Test1 : IQ
    {
        public int C { get ; set ; }
        public string B { get; set; }
    }



四、接口与抽象类区别

一、相同点

  1. 都不能直接 new 实例化
  2. 都可以包含未实现的方法,必须交给子类 / 实现类去完成
  3. 都用来做规范 / 约束,让子类按照规则编写代码

二、不同点(超级清晰 5 条)

  1. 继承数量

    • 抽象类:只能继承 1 个(单继承)
    • 接口:可以实现多个(多实现)
  2. 方法实现

    • 抽象类:可以写普通方法(有实现)+ 抽象方法(无实现)
    • 接口:只能写未实现的方法 / 规范(C# 8.0 以后可写默认实现,但极少用)
  3. 成员内容

    • 抽象类:可以有 字段、属性、方法、构造方法
    • 接口:不能有字段、不能有构造方法,只能有方法、属性
  4. 访问修饰符

    • 抽象类:可以用 public/private/protected
    • 接口:只能是 public(不能写 private/protected)
  5. 重写方式

    • 抽象类:抽象方法必须用 override 重写
    • 接口:直接实现方法,不用 override
对比 接口 (Interface) 抽象类 (Abstract Class)
能不能写方法实现 ❌ 不能 ✅ 能
能不能有字段 ❌ 不能 ✅ 能
继承数量 ✅ 可以继承多个 ❌ 只能继承一个
构造方法 ❌ 没有 ✅ 有
访问修饰符 默认 public 可以用所有修饰符
用途 定义能力 / 规范 定义父子关系
关键词 interface abstract class

五、泛型

泛型就是 “万能模板”,让一段代码可以支持任意类型。

它允许我们在定义类、方法、接口、委托的时候,不指定具体类型(用一个占位符 <T> 代替), 等到使用的时候再指定具体类型(int /string/ 自定义类等)。

为什么要用泛型?(3 大好处)

1. 代码复用

不用给 int 写一个方法,给 string 写一个方法。 一套泛型代码全部搞定。

2. 类型安全

编译时就检查类型,不会传错类型导致运行报错。

3. 避免拆箱装箱(性能高)

比用 object 类型性能好很多。

1.泛型的 4 种常见写法

(1)泛型类
class 类名<T>
{
    T 变量;
}
MyClass<int> mc = new MyClass<int>();
mc.SetValue(100);

class MyClass<T>
{
    private T _value;

    public T GetValue()
    {
        return _value;
    }

    public void SetValue(T value)
    {
        _value = value;
    }
}
(2)泛型方法
返回值 方法名<T>(参数)
{
}
public static void Swap<T>(ref T a, ref T b)
{
    (a, b) = (b, a);
}
  //定义泛型方法 返回值是T1类型 传递俩个不同泛型,参数是泛型数组
  //T2[] b 泛型数组
  static T1 Test3<T1,T2>(T1 a, T2[] b)
  {
      return a;
  }

  //定义泛型方法:参数有俩个不同泛型 返回值void
  static void Test2<T1,T2>(T1 a,T2 b)
  {

  }
(3) 泛型接口
interface IMyInterface<T>
{
    void Add(T t);
}

namespace _6泛型类和泛型接口
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Calc2 c2 = new Calc2();
            Console.WriteLine(c2.Add(10, 20)); 
            Console.WriteLine(c2.Sub(10, 20)); 

            Calc3 c3 = new Calc3();
            Console.WriteLine(c3.Sub("a", "a"));
            Console.WriteLine(c3.Add("a", "a"));
            
            Calc1<int> c = new Calc1<int>();
            Console.WriteLine(c.Add(10, 20)); //0

            Calc1<string> c1 = new Calc1<string>();
            Console.WriteLine(c1.Sub("ss", "sss"));
        }
    }
    //泛型接口的话,在接口实现的时候 确定泛型接口的类型 把类型传递接口里面
    //例如 实现了Ical<int> 整型泛型接口,接口里面对应T 就是整型
    public class Calc2 : Ical<int>
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public int Sub(int a, int b)
        {
            return a - b;
        }
    }

    //泛型接口传递一个字符串类型,所以接口凡是T的都是字符串类型
    public class Calc3 : Ical<string>
    {
        public string Add(string a, string b)
        {
            return a + b;
        }

        public string Sub(string a, string b)
        {
            //"aa" - "a"= "a"
            //b 字符串类型 ,转成char类型
            if (a.Contains(b))
            {
                return a.Replace(Convert.ToChar(b) ,'x');
            }
            return a;
        }
    }
    // 定义一个类实现这个接口 
    //如果接口传入类型如果写成T,需要在类后面添加一个<T>,
    // 由类的<类型> 决定这接口传入的类型
    public class Calc1<T> : Ical<T>
    {
        public T Add(T a, T b)
        {
            //并不是所有类型实现了 + 运算
            return default(T);//返回对应类型默认类型
        }

        public T Sub(T a, T b)
        {
            return default(T);
        }
    }
}
namespace _6泛型类和泛型接口
{
    //泛型接口 接口名字后面添加<T> 定义方法的把<T>去掉

    internal interface Ical<T>
    {
        //定义俩个相加的方法 
        //弊端 只能实现俩个数字相加,不能其他类型的相加
        //int Add(int a, int b);

        //定义一个方法 允许任意的类型相加 ,返回值是任意类型
        T Add(T a, T b); //泛型的俩个参数相加的方法

        T Sub(T a, T b); // 泛型俩个参数相减的方法
    }
}
(4)泛型委托
delegate void MyDelegate<T>(T t);

2、泛型约束 (where

约束 = 给 T 加限制

  • where T : struct → T 必须是值类型(int、double、bool、enum、struct)

  • where T : class → T 必须是引用类型(string、数组、类、接口)

  • where T : new() → T 必须存在无参构造函数

  • where T : 类名 → T 必须是这个类或派生类

  • where T : 接口名 → T 必须是实现接口的类型 或者是实现了这个接口的类的派生类

 static void Main(string[] args)
 {
    //通过where添加泛型约束
     Test2(10);
     //Test2(new int[] {1}); 报错
}

 //1要求泛型只能是值类型
 static void Test2<T>(T a) where T : struct
 {
     Console.WriteLine("Test1");
 }
   static void Main(string[] args)
  {
      //Test3(10);报错
      Test3("ss");
  }

//2要求泛型只能是引用类型
 static void Test3<T>(T a) where T : class
 {
     Console.WriteLine("Test1");
 }
  static void Main(string[] args)
{  
    Test4(new People());
}

class People
{ 
    public People() 
    { 
    
    }
    public People(string b)
    {

    }

}

//3要求类里面必须有个不带参数构造函数
  static void Test4<T>(T a) where T : new()
  {
      Console.WriteLine("Test1");
  }
 static void Main(string[] args)
 {  
     Test5(new People());
     Test5(new Stu()); 
 }


 class People
 { 
     public People() 
     { 
     
     }
     public People(string b)
     {

     }
 
 }

 class Stu : People
 {

 }

 //4 要求泛型必须是类或者派生类型
 static void Test5<T>(T a) where T : People
 {
     Console.WriteLine("Test1");
 }
 static void Main(string[] args)
 {   
     Test6(new SS());
 }  

 interface IPeople
  {

  }
  class SS:IPeople
  {

  }

 //5 要求泛型必须是实现接口的类型 或者是实现了这个接口的类的派生类
  static void Test6<T>(T a) where T : IPeople
  {
      Console.WriteLine("Test1");
  }

六、委托

1、委托定义

语法:public  delegate void   委托名();

public delegate bool CallBack(int a);

规定一类方法:接收 1 个 int 参数,返回 bool,这类方法统一叫 CallBack 类型。

  • delegate:关键字 → 定义委托
  • CallBack委托名字(相当于类型名,和 int、string 一个级别)
  • bool:该类型方法必须返回布尔
  • int a:该类型方法必须传一个 int 入参

注意:

public delegate bool CallBack(int a);

   定义了一种新的方法类型,名字叫 CallBack

   这种类型的方法必须满足

  1. 参数是一个 int
  2. 返回值是 bool

        因为 CallBack 是【自定义委托】,不是泛型委托!只有系统自带的 Func / Action 才是泛型委托,才能写 <int, bool>

  • 自定义委托 CallBack:类型写死 → 不能用 CallBack<int,bool>
  • 系统泛型委托 Func:类型可填 → 必须用 Func<int,bool>
  • CallBack 等价于 Func<int, bool>,可以互相替换,效果完全一样!

2、委托实例化

namespace _3委托实例化
{
    internal class Program
    {
        //定义委托类型 接收F1类型的函数
        public delegate void MyDel1(int a, string b);

        //定义委托类型 接收Add类型的函数
        public delegate int MyDel2(int a, int b);

        static void Main(string[] args)
        {
            //第一种方法,根据委托类型定义委托类型的变量,参数就是一个函数,
            MyDel1 m1 = new MyDel1(F1);

            //可以直接调用F1方法
            F1(10, "sss");

            //通过委托类型的变量间接调用方法
            m1(10, "aaa");

            //第二种接收方法 直接赋值方法名
            MyDel1 m2 = F2;
            m2(10, "bbb");

            Console.WriteLine(m2.Invoke(10,"bbb")); //调用委托类型存储的函数


            MyDel2 m5 = null; //使用委托变量一般先判断是不是为null 如果不为空再去调用
                //if (m5 != null)
                //{
                //    m5(10, 99);
                //}

            //上面的条件简化区写
            //? 判断m5是不是null 如果不是null ,直接m5.Invoke(10, 20)调用
            m5?.Invoke(10, 20); 
        }
        public static void F1(int a, string b)
        {
            Console.WriteLine($"F1方法a的值为:{a},b的值为:{b}");
        }

        public static void F2(int a, string b)
        {
            Console.WriteLine($"F2方法a的值为:{a},b的值为:{b}");
        }
    }

3、多播委托

多播委托:可以通过+= 或者 -=,给一个委托变量绑定多个函数,在通过委托变量执行的方法时候,把委托变量保存所有的函数执行一遍

  • 普通委托:1 个变量 → 装 1 个 方法
  • 多播委托:1 个变量 → 装 N 个 方法
  •  用 += 绑定 ,用 -= 取消 ,调用一次,全部执行!
namespace _4多播委托
{
    internal class Program
    {
        public  delegate void MyDel(string s); //类型
        public static MyDel m8 {  get; set; } // 通过类型定义变量 m8  赋值一个函数
       
        static void Main(string[] args)
        {
             m8 = F1;
            F1("ss");//直接调用
            m8("aaa");//使用委托变量间接调用
            m8.Invoke("bbb");//使用invoke调用
            m8?.Invoke("ccc");// 先判断是否为空,不为空 执行方法

            //多播委托:可以通过+=或者-=,给一个委托变量绑定多个函数,在通过委托变量执行的方法时候,把委托变量保存所有的函数执行一遍
            MyDel m1 = new MyDel(F1); //绑定F1方法
            m1 += new MyDel(new Test().F2);//绑定了F2方法
            m1("周星驰");

            MyDel m2 = F1;
            m2 += new Test().F2;
            m2("吴孟达");

            m2 -= F1;//把原先绑定的F1的函数移除掉
            m2("元华");
            
            MyDel m3 = F1;
            m3 += new Test().F2; //new Test() 创建一个新的对象
            m3-= new Test().F2;   //new Test()  又是一个新对象,上下俩句对象不是同一个
            m3("梅艳芳");

            // 使用-=F2方法实现移除方法的目的 
            //1 使用同一个对象,把new Test() 赋值给一个变量
            MyDel m4 = F1;
            Test t1 =  new Test();
            m4 += t1.F2;
            m4 -= t1.F2;
            m4("张国荣");

            //2 绑定一个静态方法 不再去使用对象,
            MyDel m5 = F1;
            m4 += Test.F3;
            m4 -= Test.F3;
            m4("周润发");
        }
        static void F1(string s)
        {
            Console.WriteLine("这是F1的方法传递过来的参数是"+s);
        }
    }
    class Test 
    { 
        public void F2(string s)
        {
            Console.WriteLine("这是F2的方法传递过来的参数是" + s);
        }

        public static void F3(string s)
        {
            Console.WriteLine("这是F3的方法传递过来的参数是" + s);
        }
    }
}

4、内置委托

1. Func<int,bool> f1

Func<参数,返回值>有返回值委托

  • 第一个 int:调用 f1 时要传进去的参数类型
  • 第二个 boolf1 执行完必须返回布尔
  • f1 就是一个能接收 1 个 int、返回 bool 的方法变量
// 等价:f1是一个形如 bool 方法名(int n){...} 的方法

2. f1(arr[i])

把数组元素 arr[i] 当做实参传给委托方法:

bool result = f1(arr[i]);
  • arr[i] → 跑进外面传进来的判断逻辑
  • 返回 true:满足条件;false:不满足

Func<int,bool> f1:定义规则模板;

f1(arr[i]):拿当前元素套用规则判断。

int[] arr = {2,5,8};
// n=>n>4 就是赋值给f1的方法
MyFind1(arr, n => n > 4);   // bool f(int n) 的方法    f(n)=  n => n > 4

f1(2) → 2>4?false
f1(5) → 5>4?true → 返回5

1. f 就是:Func<int,bool> f

f 是委托变量,存一段判断代码,格式:

bool 方法(int v){ return ...; }

2. v=>v%2==0 → 赋值给 f

f = v => v % 2 == 0;
  • v:形参(随便起名 x、n 都行)
  • =>:Lambda 符号
  • v%2==0:判断是不是偶数

等价老式写法:

bool Test(int v)
{
    return v % 2 == 0;
}
f = Test;

3. 使用:f(5) / f(6)

  • f(5)5%2==0 → false
  • f(6)6%2==0 → true

一句话

v=>v%2==0 就是给委托 f 装一个 “判断偶数” 的规则,后面f(数值)就用这个规则判断。

1. f 是什么?

f 是一个方法盒子,它要装的是 **“一段代码逻辑”**。

2. 什么能装进 f 里?

只有带 => 的才能装进去:

v => v % 2 == 0

这代表: 给我一个数字 v,我返回 v 是不是偶数。

3. 什么不能装进 f 里?

v % 2 == 0  这只是一个结果(true 或 false),不是方法,不能给 f

Func<int, bool> f = v => v % 2 == 0;

所以  f = v => v % 2 == 0

namespace _5内置委托
{
    internal class Program
    {  
        static void Main(string[] args)
        {

            //调用F1方法
            F1(d1);
            void d1(int a1)
            {
                Console.WriteLine("a1的值"+a1);
            }
           // F1(v => Console.WriteLine("v的值" + v));
           
            Func<int, string> d2 = a1 => { return "ssss"; };
            F2(d2);
           // F2(v => "ssss");

            //最终的写法 lambda 表达式
            F3(v => true);
        }       

        //delegate void d1(); 无参数无返回值的委托
        //delegate string  d1(int a); 返回值为字符串,参数是int类型 
        //delegate bool d1(int a) int参数类型 返回值是bool 

        // 把内置的委托使用在方法的回调函数中
        // Action 无返回值的方法
        // Func<int,string> 返回值为字符串,参数是int类型  
        //predicate<int> int参数类型 返回值是bool 
        static void F1( Action<int> action)
        {
            action?.Invoke(10); //调用action
            Console.WriteLine("F1的方法");
        }
        static void F2(Func<int ,string> func)
        {
            Console.WriteLine(func?.Invoke(10));
            Console.WriteLine("F2的方法");
        }

        static void F3(Predicate<int> pre)
        {
            Console.WriteLine(pre(10));
            Console.WriteLine("F3的方法");
        }
    }
}

5、委托例子

namespace _6委托例子
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            Console.WriteLine(Array.Find(arr, v => v % 2 == 0)); //2
            Console.WriteLine(MyArray.MyFind1(arr, v => v % 3 == 0));//3
            Console.WriteLine(MyArray.MyFind2(arr, v => v % 3 == 0));//3
        }
    }
    public class MyArray
    {
        //对比Array的Find 封装一个自己MyFind1
        //f1返回值为bool的函数
        //MyFind1 找到满足f1函数的第一个元素
        public static int MyFind1(int[] arr,Func<int,bool> f1)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                if (f1(arr[i])) // 判断数组每一个元素使用f1调用之后结果是不是为true 
                {
                    return arr[i];
                }
            }

            return -1;
        }

        //使用delegate实现功能
        //1自定义委托类型 替换Func<> 
        public delegate bool CallBack(int a);

        //2 根据委托定义委托类型的变量 CallBack f1  ;  CallBack c1 = new CallBack(函数)

        //3 调用委托函数

        public static int MyFind2(int[] arr, CallBack f1)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                if (f1(arr[i]))
                {
                    return arr[i];
                }
            }
            return -1;
        }
    }
}

6、泛型委托

namespace _7泛型委托
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Find<int>(new int[] { 1, 2, 3 }, v => v % 2 == 0));
            Console.WriteLine(Find<string>(new string[] { "aa", "bb", "cc" }, v => v.StartsWith("a")));

            Console.WriteLine(Find1<string>(new string[] { "aa", "bb", "cc" }, v => v.StartsWith("b")));

        }
        //1定义泛型委托类型
        public delegate bool CallBack<T>(T v);

        //封装类似于Find的方法 要求传递任意类型的数组,传递泛型委托函数
        //字符串数组  委托函数参数就是字符串
        //整型数组  委托函数参数就是字整型

        //返回值 是根据传递数组类型
        public static T Find<T>(T[] arr,CallBack<T> c)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                if (c(arr[i]))
                {
                    return arr[i];
                }
            }
            return default(T); //T类型的默认值 
        }
        public static T Find1<T>(T[] arr, Predicate<T> c)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                if (c(arr[i]))
                {
                    return arr[i];
                }
            }
            return default(T); //T类型的默认值 
        }
    }
}

七、事件

定义事件:
        语法: public event  事件类型  事件名

                   public static event EventHandler MyClick;

                        event 关键字
                        EventHandler  事件的基类
                        MyClick 事件名

  事件:具体的一个操作,例如鼠标点击操作、鼠标移动等操作,事件基于委托实现。
  事件基于发布-订阅设计模式进行实现的;
  发布类: 是一个包含事件和委托定义的对象;
  订阅类:是一个接受事件并提供事件处理程序 ;

 internal class Program
 {
     //1定义一个事件
  
     public static event EventHandler MyClick;

     static void Main(string[] args)
     {        
         //2 给事件赋值
         //事件其实就是委托的实例 
         //EventHandler 要求函数必须有两个参数
         //  MyClick = new EventHandler((sender,e) => { });

         MyClick+= new EventHandler(F1);
         MyClick += (sender, e) =>
         {
             Console.WriteLine("lambda表达式");
         };
         //3调用
         MyClick(null,null);
     }
     static void F1(object sender,EventArgs s)
     {
         Console.WriteLine("F1方法");
     }
 }
  //订阅类:绑定事件(事件变量赋值使用+=函数进行赋值)
  internal class Program
  {
      static void Main(string[] args)
      {
          Calc c = new Calc();
          c.Finished += F1;//给事件绑定方法 事件变量赋值 订阅事件
        
          c.Add(10, 20);
          c.Add(30, 20);
      }
      //展示的俩个数字以及结果方法F1 打印a b 和
      static void F1(int a,int b,int c)
      {
          Console.WriteLine($"a的值为{a},b的值为{b},c的值为{c}");
      }
  }
  //发布类:定义事件对象和委托对象
  public class Calc
  {
      //public static event EventHandler MyClick; 不采用内置的EventHandler类型的事件
      //1定义委托类型  等同于一个enmu类型  int 
      public delegate void AddDel(int a, int b, int sum);

      //2 根据委托类型定义事件实例 等同于enmu类型一个实例  a
      public event AddDel Finished;
      public void  Add(int a,int b)
      {
          int sum = a + b;
          //3 执行事件
          Finished?.Invoke(a,b,sum);
      }
  }
  // 计算俩个数字相加的例子  展示打印计算俩个数字以及结果

更多推荐