委托、lambda表达式和事件
委托
- 委托的使用时机:当要把方法传送给其他方法时
- 需要使用委托的示例:
- 启动线程和任务
- 通用库类
- 事件
声明委托
delegate void IntMethodInvoker(int x);
定义委托基本上是定义一个新类,所以可以在定义类的任何相同地方定义委托。也就是说,可以在另一个类的内部定义委托,也可以在任何类的外部定义,还可以在名称空间中把委托定义为顶层对象。
使用委托
private delegate string GetAString();
static void Main()
{
int x = 40;
GetAString firstStringMethod = new GetAString(x.ToString);
//也可以简化写法
GetAString firstStringMethod = x.ToString;
WriteLine($"String is {firstStringMethod()}");
// With firstStringMethod initialized to x.ToString(),
// the above statement is equivalent to saying
// Console.WriteLine("String is {0}", x.ToString());
}
调用上述方法名时,输入形式不能为x.ToString()(不要输入圆括号),也不能把它传递给委托变量。输入圆括号会调用一个方法,而调用x.ToString()方法会返回一个不能赋予委托变量的字符串对象。
==只能把方法的地址赋予委托变量。==
简单的委托示例
class MathOperations
{
public static double MultiplyByTwo(double value) => value * 2;
public static double Square(double value) => value * value;
}
delegate double DoubleOp(double x);
class Program
{
static void Main()
{
DoubleOp[] operations =
{
MathOperations.MultiplyByTwo,
MathOperations.Square
};
for (int i = 0; i < operations.Length; i++)
{
WriteLine($"Using operations[{i}]:");
ProcessAndDisplayNumber(operations[i], 2.0);
ProcessAndDisplayNumber(operations[i], 7.94);
ProcessAndDisplayNumber(operations[i], 1.414);
WriteLine();
}
}
static void ProcessAndDisplayNumber(DoubleOp action, double value)
{
double result = action(value);
WriteLine($"Value is {value}, result of operation is {result}");
}
}
//结果:
SimpleDelegate
Using operations[0]:
Value is 2, result of operation is 4
Value is 7.94, result of operation is 15.88
Value is 1.414, result of operation is 2.828
Using operations[1]:
Value is 2, result of operation is 4
Value is 7.94, result of operation is 63.0436
Value is 1.414, result of operation is 1.999396
Action和Func委托
Action委托表示引用一个void返回类型的方法。至多传入16中不同的参数类型。如:Action
<double,double> operations =
{
MathOperations.MultiplyByTwo,
MathOperations.Square
};
static void ProcessAndDisplayNumber(Func<double,double> action, double value)
{
double result = action(value);
WriteLine($"Value is {value}, result of operation is {result}");
}
BubbleSorter示例
class BubbleSorter
{
static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
{
bool swapped = true;
do
{
swapped = false;
for (int i = 0; i < sortArray.Count - 1; i++)
{
if (comparison(sortArray[i + 1], sortArray[i]))
{
T temp = sortArray[i];
sortArray[i] = sortArray[i + 1];
sortArray[i + 1] = temp;
swapped = true;
}
}
} while (swapped);
}
}
class Employee
{
public Employee(string name, decimal salary)
{
this.Name = name;
this.Salary = salary;
}
public string Name { get; }
public decimal Salary { get; private set; }
public override string ToString() => $"{Name}, {Salary :C}";
public static bool CompareSalary(Employee e1, Employee e2) =>
e1.Salary < e2.Salary;
}
class Program
{
static void Main()
{
Employee[] employees =
{
new Employee("Bugs Bunny", 20000),
new Employee("Elmer Fudd", 10000),
new Employee("Daffy Duck", 25000),
new Employee("Wile Coyote", 1000000.38m),
new Employee("Foghorn Leghorn", 23000),
new Employee("RoadRunner", 50000)
};
BubbleSorter.Sort(employees, Employee.CompareSalary);
foreach (var employee in employees)
{
WriteLine(employee);
}
}
}
多播委托
多播委托:包含多个方法的委托。
多播委托的签名为void。
class MathOperations
{
public static void MultiplyByTwo(double value)
{
double result = value * 2;
WriteLine($"Multiplying by 2: {value} gives {result}");
}
public static void Square(double value)
{
double result = value * value;
WriteLine($"Squaring: {value} gives {result}");
}
}
class Program
{
static void Main()
{
Action<double> operations = MathOperations.MultiplyByTwo;
operations += MathOperations.Square;
ProcessAndDisplayNumber(operations, 2.0);
ProcessAndDisplayNumber(operations, 7.94);
ProcessAndDisplayNumber(operations, 1.414);
WriteLine();
}
static void ProcessAndDisplayNumber(Action<double> action, double value)
{
WriteLine();
WriteLine($"ProcessAndDisplayNumber called with value = {value}");
action(value);
}
}
多播委托方法链的顺序未正式定义,所以应避免调用有依赖关系的方法,如果其中一个方法抛出异常,那么多播委托就会停止。
为了解决顺序问题,应自己迭代方法列表。
static void main()
{
Action d1 = one;
d1 += two;
Delegate[] delegates = d1.GetInvocationList();
foreach(Action d in delegates)
{
try
{
d();
}catch(Exception)
{
WriteLine("Exception caught");
}
}
}
匿名方法(已用lambda表达式代替)
class Program
{
static void Main()
{
string mid = ", middle part,";
Func<string, string> anonDel = delegate (string param)
{
param += mid;
param += " and this was added to the string.";
return param;
};
WriteLine(anonDel("Start of string"));
}
}
- 匿名方法不会使代码执行速度加快
- 可以降低代码复杂性
- 在匿名方法中不能使用跳转语句(break、goto、continue)跳转到匿名方法外部,反之亦然。
- 不能访问匿名方法外部使用的ref和out参数,但可以使用在匿名方法外部定义的其他变量。
lambda表达式
class Program
{
static void Main()
{
string mid = ", middle part,";
Func<string, string> anonDel = param =>
{
param += mid;
param += " and this was added to the string.";
return param;
};
WriteLine(anonDel("Start of string"));
}
}
=>的左边是需要的参数,右边是实现的代码。
参数
//只有一个参数写法如下
Func<string, string> oneParam = s => $"change uppercase {s.ToUpper()}";
WriteLine(oneParam("test"));
//多个参数写法如下
Func<double, double, double> twoParams = (x, y) => x * y;
WriteLine(twoParams(3, 2));
//为了方法起见,可以在括号写参数类型。如果编译器不能匹配重载后的版本,那么使用参数类型可以帮助找到匹配的委托
Func<double, double, double> twoParamsWithTypes = (double x, double y) => x * y;
WriteLine(twoParamsWithTypes(4, 2));
ProcessAndDisplayNumber(operations, 2.0);
ProcessAndDisplayNumber(operations, 7.94);
ProcessAndDisplayNumber(operations, 1.414);
WriteLine();
多行代码
如果lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,多条语句就需要花括号和return语句。
Func<double, double> square = x => x * x;
Func<string, string> anonDel = param =>
{
param += mid;
param += " and this was added to the string.";
return param;
};
闭包
闭包:lambda表达式可以访问lambda表达式块外部的变量。
在lambda表达式中修改闭包的值,可以在lambda表达式外部访问已改动的值。
对于lambda表达式x => x + someVal,编译器会创建一个匿名类,它有一个构造函数来传递外部变量。该构造函数取决于外部访问的变量数
public class AnonymousClass
{
private int someVal;
public AnonymousClass(int someVal)
{
this.someVal = someVal;
}
public int AnonymousMethod(int x) => x + someVal;
}
如果多线程使用闭包,就可能遇到并发冲突。最好仅给闭包使用不变的类型。这样可以确保不改变值,也不需要同步。
lambda表达式可以用于类型为委托的任意地方。类型是Expression 或Expression时,也可以使用lambda表达式,此时编译器会创建一个表达式树。
事件
事件发布程序
- 定义事件的简化记法:
public delegate void EventHanlder<TEventArgs>(object sender, TEventArgs e) where TEventArgs :EventArgs -
定义事件的长记法:
private EventHanlder<TEventArgs> newEvent;
public event EventHanlder<TEventArgs> NewEvent;
{
add
{
newEvent += value;
}
remove
{
newEvent -= value;
}
}如果不仅需要添加和删除事件处理程序,定义事件的长记法就很有用,例如,需要为多个线程访问添加同步操作。WPF控件使用长记法给事件添加冒泡和隧道功能。
与之前的多播委托一样,方法的调用顺序无法保证。为了更多地控制处理程序的调用,可以使用Delegate类的GetInvaocationList()方法,访问委托列表中的每一项,并独立地调用每个方法。
触发事件之前,需要检查事件是否为空
EventHanlder<TEventArgs> newEvent = NewEvent;
newEvent?.Invoke(this,new TEventArgs());
事件侦听器
var dealer = new CarDealer();
var daniel = new Consumer("Daniel");
//绑定事件
dealer.NewCarInfo += daniel.NewCarIsHere;
//开始调用
dealer.NewCar("Mercedes");
public class CarInfoEventArgs : EventArgs
{
//调用第三步
public CarInfoEventArgs(string car)
{
Car = car;
}
public string Car { get; }
}
public class CarDealer
{
public event EventHandler<CarInfoEventArgs> NewCarInfo;
public void NewCar(string car)
{
//调用第一步
WriteLine($"CarDealer, new car {car}");
//调用第二部,如果不为空走CarInfoEventArgs.CarInfoEventArgs()
NewCarInfo?.Invoke(this, new CarInfoEventArgs(car));
}
}
public class Consumer
{
private string _name;
public Consumer(string name)
{
_name = name;
}
//调用第四步(完毕)
public void NewCarIsHere(object sender, CarInfoEventArgs e)
{
//sender类型为Delegates.CarDealer
WriteLine($"{_name}: car {e.Car} is new");
}
}
弱事件
通过弱事件模式解决(在垃圾回收时,如果不再直接饮用侦听器,发布程序就仍有一个饮用。垃圾回收期不能清空侦听器占用的内存。),使用WeakEventManager作为发布程序和侦听器之间的中介。
public class Consumer : IWeakEventListener
{
private string _name;
public Consumer(string name)
{
this._name = name;
}
public void NewCarIsHere(object sender, CarInfoEventArgs e)
{
WriteLine($"{_name}: car {e.Car} is new");
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
NewCarIsHere(sender, e as CarInfoEventArgs);
return true;
}
}
static void Main()
{
var dealer = new CarDealer();
var daniel = new Consumer("Daniel");
WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer,"NewCarInfo", daniel.NewCarIsHere);
dealer.NewCar("Mercedes");
}