【c#基础】9.索引器、接口、泛型、委托、事件
一、索引器
索引器:可以对对象添加一个 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; }
}
四、接口与抽象类区别
一、相同点
- 都不能直接 new 实例化
- 都可以包含未实现的方法,必须交给子类 / 实现类去完成
- 都用来做规范 / 约束,让子类按照规则编写代码
二、不同点(超级清晰 5 条)
继承数量
- 抽象类:只能继承 1 个(单继承)
- 接口:可以实现多个(多实现)
方法实现
- 抽象类:可以写普通方法(有实现)+ 抽象方法(无实现)
- 接口:只能写未实现的方法 / 规范(C# 8.0 以后可写默认实现,但极少用)
成员内容
- 抽象类:可以有 字段、属性、方法、构造方法
- 接口:不能有字段、不能有构造方法,只能有方法、属性
访问修饰符
- 抽象类:可以用
public/private/protected- 接口:只能是 public(不能写 private/protected)
重写方式
- 抽象类:抽象方法必须用 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
这种类型的方法必须满足:
- 参数是一个 int
- 返回值是 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时要传进去的参数类型- 第二个
bool:f1执行完必须返回布尔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→ 赋值给 ff = 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→ falsef(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);
}
}
// 计算俩个数字相加的例子 展示打印计算俩个数字以及结果
更多推荐

所有评论(0)