//定义委托内容------委托可以传值
delegate void MyFun();
public delegate int MyFun2(int a);
public delegate void MyDel();//声明一个自定义委托
class Program
{
static void Main(string[] args)
{
//两种声明方式
MyDel say1 = SayHi;// 隐式方法组转换
MyDel say2 = new MyDel(SayHi);//显式委托构造
//使用委托
say1();
say2();
}
方法赋值给委托须知
而是:当你把一个方法赋值给委托时,你只写方法名(不带括号和实参),编译器会自动检查签名是否匹配
cs复制代码
public delegate int Comparison<T>(T x, T y);
Comparison<ShopItem> comp = SortShopItems; // ← 没有 ()
shopItemsList.Sort(SortShopItems); // ← 没有 ()
static int SortShopItems(ShopItem a, ShopItem b)
{
// ...
}
class Test
{
public Action action;
// 作为参数传递时
public void Dosomething(int a, Action fun)
{
Console.WriteLine(a);
fun();
}
public Action GetFun()
{
return null;
}
}
---主函数----
Test t = new Test();
1、参数传递------这里一般需要声明函数名字才能传递,但是匿名函数可以直接传入
t.Dosomething(100, delegate ()
{
Console.WriteLine("匿名委托执行了");
});
作为返回值
cs复制代码
class Test
{
public Action action;
public Action GetFun()
{
return Test1;//1、正常来讲如果需要返回函数,必须有这函数声明
return delegate()
{
console.writeline("返回匿名函数")//2、匿名函数则不需要声明
}
}
public void Test1()
}
----主函数----
Test t = new Test();
Action ac=t.GetFun()//3、调用该函数,返回匿名函数赋值给ac
ac()//4、调用该匿名函数
//5、也可以返回了直接调用---
//t.GetFun()得到匿名函数,再加一个()调用该匿名函数
t.GetFun()()
class Test
{
public event Action action;
public Test()
{
int value = 10;
// 这里就形成了闭包
// 因为 当构造函数执行完毕时 其中申明的临时变量value的生命周期被改变了
action = () =>
{
Console.WriteLine(value);
};
}
}
Test t = new Test(); // 构造函数执行完毕,value 按理"死了"
t.action(); // 但这里依然输出:10
value就在Test类构造结束后被释放,但是因为被包裹进了匿名函数,所以生命周期就发生了改变。
cs复制代码
public Test()
{
int value = 10;
// ... 其他代码
} //← 构造函数结束,局部变量 `value` 被释放(出栈)
public Test()
{
int value = 10;
action = () => Console.WriteLine(value); // ← 捕获了 value!
}
那什么时候才会被释放呢?
其实什么时候都不会被释放,除非你把它置空------即手动将action=null
遍历匿名函数的闭包
cs复制代码
public class Test
{
public Action action;
public Test()
{
for (int i = 0; i < 5; i++)
{
action += () =>
{
Console.WriteLine(i);
};
}
}
}
internal static class Program
{
private static void Main(string[] args)
{
Test test = new Test();
test.action.Invoke();//输出结果5个5
}
}
执行顺序:
构造函数开始执行
进入 for 循环
i 是一个局部变量,初始为 0
每次循环:添加一个 Lambda 到 action
第 1 次:i = 0 → 添加 () => Console.WriteLine(i)
第 2 次:i = 1 → 再添加一个 () => Console.WriteLine(i)
...
第 5 次:i = 4 → 添加第 5 个 Lambda
⚠️ 但注意:此时这些 Lambda 并没有执行!只是被存储起来。
循环结束条件
当 i = 4 时,执行完循环体后,i++ → i = 5
然后检查 i < 5?→ 5 < 5 为 false → 退出循环
此时 i 的最终值是 5
构造函数结束
Test test = new Test(); 完成
调用 test.action.Invoke()
此时才真正执行那 5 个 Lambda
每个 Lambda 都去读取 当前的 i 值
而 i 现在已经是 5(并且因为闭包,它没有被销毁!)
List排序
系统自带的排序方法
cs复制代码
static void Main(string[] args)
{
Console.WriteLine("List排序");
#region 知识点一 List自带排序方法
List<int> list = new List<int>();
list.Add(3);
list.Add(2);
list.Add(6);
list.Add(1);
list.Add(4);
list.Add(5);
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
// list提供了排序方法
list.Sort();
Console.WriteLine("****************");
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
#endregion
}
排序前:
3 2 6 1 4 5
排序后:
1 2 3 4 5 6
Sort函数是Unity自带排序函数,可以将列表中的数据升序排序
int为什么能被排序,因为int的定义如下,有实现 IComparable<T> 接口,List<T>.Sort() 方法要求泛型类型 T 必须满足该条件
class ShopItem//1、这里没有那个接口,所以直接使用Sort函数排序会报错
{
public int id;
public ShopItem(int id)
{
this.id = id;
}
}
但是sort是有重载的:
cs复制代码
public void Sort(Comparison<T> comparison);//这里委托函数排序用这一个
public void Sort(int index, int count, IComparer<T>? comparer);
public void Sort();
//点开后发现是该委托类型,它表示:一个接受两个相同类型的参数(T x, T y),返回一个整数的方法。
public delegate int Comparison<T>(T x, T y);
整体代码如下
cs复制代码
class ShopItem
{
public int id;
public ShopItem(int id)
{
this.id = id;
}
}
class Program
{
static void Main(string[] args)
{
List<ShopItem> shopItemsList = new List<ShopItem>();
shopItemsList.Add(new ShopItem(3));
shopItemsList.Add(new ShopItem(5));
shopItemsList.Add(new ShopItem(6));
shopItemsList.Add(new ShopItem(2));
shopItemsList.Add(new ShopItem(1));
shopItemsList.Add(new ShopItem(4));
1、通过传入函数的排序方法
shopItemsList.Sort(SortShopItems);
2、或者通过lambda表达式或者匿名函数和三目运算符的简便写法省去声明函数
shopItemsList.Sort((a, b) =>{return a.id > b.id ? 1 : -1;});
for (int i = 0; i < shopItemsList.Count; i++)
{
Console.WriteLine(shopItemsList[i].id);
}
//打印结果为 1 2 3 4 5 6
}
//Sort规定的委托函数写法
//(委托相当于是函数的容器。这里相当于将SortShopItems函数放入委托容器中)
static int SortShopItems(ShopItem a,ShopItem b)
{
if (a.id>b.id)
{
return 1;
}
else
{
return -1;
}
}
}
delegate T TestOut<out T>();
class Father { }
class Son:Father { }
class Program
{
static void Main(string[] args)
{
1、协变 子类对象可以当作父类对象来使用,这里的T类型是Son类型(out T只能用于返回值)
并且赋值给了os
TestOut<Son> os = () =>
{
return new Son();
};
TestOut<Father> of = os;
Father f = of(); //实际返回的是os里转的函数 返回的是Son
}
}
你把一个 返回 Son 的函数(os) ,赋值给了一个 类型为 TestOut<Father> 的变量 of。
class Father { }
class Son:Father { }
//⭕in修饰的泛型 只能作为参数
delegate void TestIn<in T>(T t);
// 看起来像是 father → son,明明是传父类,但是你传子类,不和谐的
//泛型T自动 传入Father类
TestIn<Father> iF = (value) =>
{
// 这里 value 是 Father 类型
Console.WriteLine("收到一个 Father 对象");
};
TestIn<Son> iS = iF; // ← 关键!逆变赋值
iS(new Son()); // 实际上调用的是 iF
iF 的定义
cs复制代码
TestIn<Father> iF = (value) => { ... };
它能接受任何 Father 或其子类(如 Son)作为参数
因为 方法参数支持多态 :你可以把 Son 当作 Father 传进去
iS = iF 发生了什么?
编译器允许这个赋值,因为 TestIn<in T> 是逆变的
调用 iS(new Son())
可以理解为:创建一个Son的实例化,然后将该对象传入参数到TestIn<Father> iF = (Father value) => { ... }
*
(或通过 iS(new Son()) 间接调用)时,确实发生了从 Son 到 Father 的类型转换
额外思考:为何要多一步
在协变中:Father f = os();似乎可以用
在逆变中:IF(new Son());也能用,
TestOut<Father> of = os;
TestIn<Son> ins = inf;
为什么要分别多出这一步?
cs复制代码
delegate T TestOut<out T>();
delegate void TestIn<in T>(T t);
class Father { }
class Son:Father { }
class Program
{
static void Main(string[] args)
{
//协变 父类总能被子类替代
TestOut<Son> os = () =>
{
return new Son();
};
TestOut<Father> of = os;
Father f = of(); //实际返回的是os里转的函数 返回的是Son
//逆变 父类总能被子类替代
//看起来像是father------>son 明明是传父类 但却传子类 不和谐的
TestIn<Father> inf = (value) =>
{
};
TestIn<Son> ins = inf;
ins(new Son()); //实际上调用的是inf
Console.WriteLine("Hello World!");
}
}
在单个作用域内,你不需要额外的赋值步骤。这些代码完全正确,也能编译运行
场景1:协变 - 为什么需要 of = os?
假设你有一个方法,它需要一个 TestOut<Father> 类型的委
复制代码
void ProcessFactory(TestOut<Father> factory)
{
Father f = factory(); // 调用委托
Console.WriteLine(f.GetType());
}