C#高级编程9-第8章 委托、lamdba表达式和事件
委托、lamdba表达式和事件
1.引用方法
函数指针是一个指向内存位置的指针,不是类型安全的。无法判断实际指向。参数和返回类型也无从知晓。
.NET委托是类型安全的。定义了返回类型和参数类型,不仅包含方法引用,还可以包含多个方法引用。
2.委托
使用方法作为参数进行传递,必须把方法细节进行封装到一个新类型的对象中,即委托。
委托是一种特殊类型的对象。我们之前定义的对象都包含数据。而委托包含的是多个方法的地址。
声明委托
委托使用delegate声明。通过指定返回类型、签名以及参数类型进行创建。
创建委托的一个或多个实例,编译器将在后台创建表示该委托的一个类。
delegate void InitMethodInvoker(int x);
该委托方法无返回值,参数类型是int,每个实例都会有这个方法的引用。该委托类似于方法的定义,没有方法体。
委托可以使用访问修饰符进行修饰:
private delegate void InitMethodInvoker(int x);
定义委托后,创建它的实例,从而实现它的细节即方法体。
使用委托
class MathOperations
{
public static double MultiplayByTwo(double value)
{
return value * ;
} public static double Square(double value)
{
return value * value;
}
}
//这个类中定义了委托的实例
public delegate double DoubleOp(double x);//声明委托
static void Main(string[] args)
{
DoubleOp[] operations =
{
MathOperations.MultiplayByTwo,//指定委托实例方法
MathOperations.Square
};
for (int i = ; i < operations.Length; i++)
{
//将委托实例方法作为参数传递
ProcessAndDisplayNumber(operations[i], 2.0);//operations[i]是委托即方法参数
ProcessAndDisplayNumber(operations[i], );
}
} static void ProcessAndDisplayNumber(DoubleOp action,double value)
{
double result = action( value);//实现委托
Console.WriteLine("参数值value:" + value + ",结果值result:" + result);
}
通常情况下需要做安全措施。如果这个方法使用DoubleOp委托实例方法作为参数传递,如果传入null值,到了action( value)会出现异常。因此需要在方法里面加上判断
ProcessAndDisplayNumber(null, );
static void ProcessAndDisplayNumber(DoubleOp action,double value)
{
if(action!=null)
double result = action( value);//实现委托
Console.WriteLine("参数值value:" + value + ",结果值result:" + result);
}
系统委托
系统委托有4中:Action类、Func类、Predicate<T>、Comparison<T>委托
Action类的委托
- Action委托 封装一个方法,该方法不具有参数并且不返回值
Action<T>委托 封装一个方法,该方法只有一个参数并且不返回值
- Action<T1,T2>委托 封装一个方法,该方法具有两个参数并且不返回值
static void Main(string[] args)
{
#region Action<T>委托示例
//需求:打印出整型集合list的元素
List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
//将匿名方法分配给 Action<T> 委托实例
Action<int> concat1 = delegate(int i) { Console.WriteLine(i); };
list.ForEach(concat1);
//将 lambda 表达式分配给 Action<T> 委托实例
Action<int> concat2 = (i => Console.WriteLine(i));
list.ForEach(concat2);
Console.ReadKey();
#endregion
}
Func类的委托
- 1.Func(TResult)委托封装封装一个不具有参数但却返回 TResult 参数指定的类型值的方法
- Func(T,TResult)委托 封装一个具有一个参数并返回 TResult 参数指定的类型值的方法
- Func(T1,T2,TResult)委托 封装一个具有两个参数并返回 TResult 参数指定的类型值的方法
static void Main(string[] args)
{
#region Func<T,TResult>委托示例
//需求:查找整型集合list中大于3的所有元素组成的新集合,并打印出集合元素
List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
//将匿名方法分配给 Func<T,TResult> 委托实例
Func<int, bool> concat1 = delegate(int i) { return i > 3; };
var newlist1 = list.Where(concat1).ToList();
//将 Lambda 表达式分配给 Func<T,TResult> 委托实例
Func<int, bool> concat2 = i => i > 3;
var newlist2 = list.Where(concat2).ToList();
newlist1.ForEach(i => Console.WriteLine(i.ToString()));
newlist2.ForEach(i => Console.WriteLine(i.ToString()));
Console.ReadKey();
#endregion
}
Predicate<T>委托
表示定义一组条件并确定指定对象是否符合这些条件的方法
static void Main(string[] args)
{
#region Predicate<T>委托示例
//需求:查找整型集合list中大于3的所有元素组成的新集合,并打印出集合元素
List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
//将匿名方法分配给 Predicate<T> 委托实例
Predicate<int> concat1 = delegate(int i) { return i > 3; };
var newlist1 = list.FindAll(concat1);
//将 lambda 表达式分配给 Predicate<T> 委托实例
Predicate<int> concat2 = (c => c > 3);
var newlist2 = list.FindAll(concat2);
newlist1.ForEach(i => Console.WriteLine(i));
newlist2.ForEach(i => Console.WriteLine(i));
Console.ReadKey();
#endregion
}
Comparison<T>委托
表示比较同一类型的两个对象的方法
static void Main(string[] args)
{
#region Comparison<T>委托示例
//需求:将整型集合list中的所有元素倒序排列打印出来
List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
//将匿名方法分配给 Comparison<T> 委托实例
Comparison<int> concat1 = delegate(int i, int j) { return j - i; };
//将 lambda 表达式分配给 Comparison<T> 委托实例
Comparison<int> concat2 = (i, j) => j - i;
list.Sort(concat1);
list.ForEach(c => Console.WriteLine(c.ToString()));
list.Sort(concat2);
list.ForEach(c => Console.WriteLine(c.ToString()));
Console.ReadKey();
#endregion
}
BubbleSorter
说明了委托真正的意图,首先定义一个Employee类,类中定义静态方法CompareSalary
public static bool CompareSalary(Employee e1, Employee e2)
{
return e1.Salary < e2.Salary;
}
然后我们再定义一个类BubbleSorter,类中定义静态方法Sort
static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
{
bool swapped = true;
do
{
swapped = false;
for (int i = ; i < sortArray.Count - ; i++)
{
if (comparison(sortArray[i + ], sortArray[i]))
{
T temp = sortArray[i];
sortArray[i] = sortArray[i + ];
sortArray[i + ] = temp;
swapped = true;
}
}
} while (swapped);
}
接下来我们分析一下:
Func<T, T, bool>是系统定义的委托,该委托具有2个参数,一个返回值,委托参数类型T根据调用Sort方法时进行指定(BubbleSorter.Sort<Employee>(..,..,))。然后我们看看所有执行代码:
static void Main()
{
Employee[] employees =
{
new Employee("Bugs Bunny", ),
new Employee("Elmer Fudd", ),
new Employee("Daffy Duck", ),
new Employee("Wile Coyote", 1000000.38m),
new Employee("Foghorn Leghorn", ),
new Employee("RoadRunner", )
};
//将Employee.CompareSalary方法作为参数进行传递,记住Employee.CompareSalary是一个委托实例类型,它目前不属于Func<T, T, bool>的实例,但是
//它符合Func<T, T, bool>类型,因此可以作为Func<T, T, bool>的实例进行参数传递。
BubbleSorter.Sort(employees, Employee.CompareSalary);
foreach (var employee in employees)
{
Console.WriteLine(employee);
}
}
多播委托
委托可以包含多个方法,可以多次显式调用这个委托。
需要注意的是,多播委托需要连续的调用多个方法,并且委托的返回结构是void,否则就只能得到最后一个方法的结果。
可以使用“+=”或者“-=”添加和删除方法。
class MathOperations
{
public static void MultiplyByTwo(double value)
{
double result = value * ;
Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result);
} public static void Square(double value)
{
double result = value * value;
Console.WriteLine("Squaring: {0} gives {1}", value, result);
}
}
static void Main()
{
Action<double> operations = MathOperations.MultiplyByTwo;// value*2
operations += MathOperations.Square;//value*value ProcessAndDisplayNumber(operations, 2.0);
ProcessAndDisplayNumber(operations, 7.94);
ProcessAndDisplayNumber(operations, 1.414);
Console.WriteLine();
} static void ProcessAndDisplayNumber(Action<double> action, double value)
{
Console.WriteLine();
Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", value);
action(value); }
ProcessAndDisplayNumber called with value =
Multiplying by : gives
Squaring: gives ProcessAndDisplayNumber called with value = 7.94
Multiplying by : 7.94 gives 15.88
Squaring: 7.94 gives 63.0436 ProcessAndDisplayNumber called with value = 1.414
Multiplying by : 1.414 gives 2.828
Squaring: 1.414 gives 1.999396
虽然可以执行多个方法,但是如果执行到其中一个方法时失败了,那么后面的方法得不到执行。此时需要一个解决办法。
.NET中的系统委托定义了一个方法:GetInvocationList()通过这个方法能够得到所有需要执行的委托方法。然后我们迭代一下就可以处理其中一个方法失败,其他方法继续运行。
Action d1 = One;
d1 += Two; Delegate[] delegates = d1.GetInvocationList();
foreach (Action d in delegates)
{
try
{
d();
}
catch (Exception)
{
Console.WriteLine("Exception caught");
}
}
static void One()
{
Console.WriteLine("One");
throw new Exception("Error in one");
} static void Two()
{
Console.WriteLine("Two");
}
匿名方法
原来使用委托,我们需要定义委托的委托的方法实例。现在我们需要简化它的操作,直接使用delegate关键字声明并定义。
在后面使用lamdba表达式之后会进行再次简化工作。使委托得到了更加灵活广泛的使用。
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;
};
Console.WriteLine(anonDel("Start of string")); }
3.lamdba表达式
只要有委托的地方均可以使用lamdba表达式。声明委托的方法将变得更简单。
使用param =>代替了delegate(string param)的声明
string mid = ",middle part,";
Func<string, string> lambda = param =>
{
param += mid;
param += "and this was added to the string";
return param;
};
Console.WriteLine(lambda("start of string"));
结果:
start of string,middle part,and this was added to the string
参数
lamdba表达式多个参数进行定义声明
下面的委托(x, y)=>x*y是委托Func<double, double, double>的实例,
如果委托只有一个参数的话直接使用x=>y;就可以了;代表实现了Func<double,double>
如果委托有一个以上参数的话需要用括号包起来;=>指定了返回值。
如果需要在定义时指定参数的类型,也可以将参数的数据类型加上。
Func<double, double, double> twoParams = (x, y) => x * y;
Console.WriteLine(twoParams(, )); Func<double, double, double> twoParamsWithTypes = (double x, double y) => x * y;
Console.WriteLine(twoParamsWithTypes(, ));
多行代码
对于简单的(x, y) => x * y的表达式,编译器会给一条隐式的return返回语句。如果这个委托里面需要做的事情不仅仅是x*y这么简单呢,我们该如何定义?
string mid = ",middle part,";
Func<string, string> lambda = param =>
{
param += mid;
param += "and this was added to the string";
return param;
};
Console.WriteLine(lambda("start of string"));
通过大括号包起来做一些复杂的事情。
闭包
通过lambda表达式可以访问lambda表达式外部的变量,就是闭包。
someVal是lambda表达式外的变量,在表达式内部进行了访问。
int someVal = ;
Func<int, int> f = x => x + someVal;
实际上对于表达式 x => x + someVal;编译器给它定义了一个匿名类;类中定义了有参构造方法,外部变量作为构造方法的参数传入到这个类中进行访问的。
public class AnonymousClass
{
private int someVal;
public AnonymousClass(int someVal)//外部变量作为构造函数的参数。
{
this.someVal = someVal;
}
public int AnonymousMethod(int x)
{
return x + someVal;
}
}
foreach闭包
主要是说明闭包在C#4.0和C#5.0对这个的改变。
var values = new List<int>() { , , };
var funcs = new List<Func<int>>(); foreach(var val in values)
{
funcs.Add(() => val);
} foreach(var f in funcs)
{
Console.WriteLine(f);
}
在C#4.0中会输出30,30,30而在C#5.0中会输出10,20,30
我们回到C#高级编程9的数组中看到之前有说明foreach的原始逻辑:
foreach (var p in persons)
{
Console.WriteLine(p);
}
//通过IL中间语言生成后:
IEnumerator<Person> enumerator = persons.GetEnumerator();
while (enumerator)
{
Person p = enumerator.Current;
Console.WriteLine(p);
}
实际上在C#4.0中enumerator.Current是定义在循环外部的,每次迭代都使用这个值。循环结束该变量就是最后的值。
因此在C#4.0中需要将 funcs.Add(() => val);改为var v=val;funcs.Add(() => v);
4.事件
事件基于委托,为委托提供了发布和订阅机制。
事件发布程序和侦听器
首先我们看看这个信息:
交通工具: 巴士
乘客甲: 乘坐了 巴士
交通工具: 的士
乘客甲: 乘坐了 的士
乘客乙: 乘坐了 的士
交通工具: 地铁
乘客乙: 乘坐了 地铁
首先可以看出通过程序执行了“交通工具: 巴士”,然后执行了“乘客甲: 乘坐了 巴士”;
接下来看这块代码:
var dealer = new CarDealer();
var michael = new Consumer("乘客甲"); WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
dealer.NewCar("巴士");
CarDealer和Consumer分别是2个对象,
WeakEventManager 提供基本类中使用的事件管理 弱事件模式。 该管理器添加和移除的事件 (或回调) 也使用该模式的侦听器。
下面是AddHandler方法的定义:
public static void AddHandler(TEventSource source, string eventName, EventHandler<TEventArgs> handler);
source事件源;eventName是事件源对象里面的属性也就是事件名.handler是处理事件
执行的逻辑是这样的:
WeakEventManager.AddHandler做的事情是:
1)指定source对象中必须包含eventName属性,如果不存在该属性,会抛出异常。
2)为eventName属性定义了实现handler;也就是说 Func<string, EventArgs> sourceEvent = eventName => handler;
以这个为例:
WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
dealer.NewCar("巴士");
我们现在来想象一下CarDealer对象的定义:
1)首先必须存在一个类,类中必须有一个类型为EventHandler<TEventArgs>的属性,属性名必须是NewCarInfo,需要记住的是该属性是一个事件。
2)AddHandler方法为NewCarInfo属性指定了实现方法michael.NewCarIsHere,接下来就是进行调用NewCarInfo
3)我们定义一个方法NewCar,使用NewCar来调用NewCarInfo,那么如何调用NewCarInfo呢?请看下EventHandler<TEventArgs>需要传入哪些参数
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
4)由此我们得出了,再调用NewCarInfo方法时需要传入2个参数。sender是触发的事件对象,我们可以将dealer对象即NewCar方法中的this,还有一个就是TEventArgs e事件,这个事件也没有,这个事件是做什么用的呢,
主要是提供事件的一些信息。接下来我们定义这个类为CarInfoEventArgs,当然这个类必须继承TEventArgs ,否则不能作为NewCarInfo方法参数。然后我们提供一个Car的字符串属性,用于记录事件信息。
5)事件信息类也定义好了,我们需要调用NewCarInfo方法了,我们传入this还有new CarInfoEventArgs()对象。为了记录Car信息,我们将CarInfoEventArgs构造方法进行改造,传入Car参数。
6)然后我们就有了调用:
if (NewCarInfo != null)
{
NewCarInfo(this, new CarInfoEventArgs(car));
}
7)实际上到此为止,核心逻辑都写完了,但是还漏了一步。我们一直在说:NewCarInfo方法的调用和实现,但是NewCarInfo具体实现还没有。
8)我们再写一个类,类里面需要包含一个方法,方法必须符合EventHandler<TEventArgs>委托类型
public void NewCarIsHere(object sender, CarInfoEventArgs e)
{
Console.WriteLine("{0}: 乘坐了 {1}", name, e.Car);
}
9)我们已经有了sender和e的值即第6点中的: this和new CarInfoEventArgs(car)
10)现在可以调用了。不好意思现在可以揭露代码的真相了.如果没有理解上面说的含义,没有关系,把下面的代码放到程序里面,调试一下吧。你就明白了。
static void Main(string[] args)
{
var dealer = new CarDealer(); var michael = new Consumer("乘客甲");
WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere); dealer.NewCar("巴士");
}
运行
public class CarInfoEventArgs : EventArgs
{
public CarInfoEventArgs(string car)
{
this.Car = car;
} public string Car { get; private set; }
} public class CarDealer
{
public event EventHandler<CarInfoEventArgs> NewCarInfo;
public CarDealer()
{ } public void NewCar(string car)
{
Console.WriteLine("交通工具: {0}", car);
if (NewCarInfo != null)
{
NewCarInfo(this, new CarInfoEventArgs(car));
}
}
}
事件发布
public class Consumer
{
private string name; public Consumer(string name)
{
this.name = name;
} public void NewCarIsHere(object sender, CarInfoEventArgs e)
{
Console.WriteLine("{0}: 乘坐了 {1}", name, e.Car);
} }
事件侦听
现在结果出来了
交通工具: 巴士
乘客甲: 乘坐了 巴士
接下来修改一下运行代码:
static void Main(string[] args)
{
var dealer = new CarDealer(); var michael = new Consumer("乘客甲");
WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere); dealer.NewCar("巴士"); var sebastian = new Consumer("乘客乙");
WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", sebastian.NewCarIsHere); dealer.NewCar("的士"); WeakEventManager<CarDealer, CarInfoEventArgs>.RemoveHandler(dealer, "NewCarInfo", michael.NewCarIsHere); dealer.NewCar("地铁");
}
给NewCarInfo事件多添加了一个事件处理(事件的实现)sebastian.NewCarIsHere
事件还是原来的事件NewCarInfo,AddHandler()方法除了给NewCarInfo事件指定了事件处理,还可以为该事件添加多个事件处理。
RemoveHandler()方法用来移除所添加的事件处理。必须指定事件对象dealer,事件名,以及事件处理,如果提供的事件处理不存在该事件对象中,将不会移除。
弱事件
通过事件、直接连接到发布程序和侦听器。垃圾回收有问题,如:侦听器不再直接引用。发布程序仍有一个引用。垃圾回收器不能清空侦听器占用的内存。
这种强连接可以使用弱事件解决。使用WeakEventManager作为发布程序和侦听器的中介。我们已经理解了关于发布和侦听器,下面的就好理解了。
class Program
{
static void Main()
{
var dealer = new CarDealer(); var michael = new Consumer("Michael");
WeakCarInfoEventManager.AddListener(dealer, michael); dealer.NewCar("Mercedes"); var sebastian = new Consumer("Sebastian");
WeakCarInfoEventManager.AddListener(dealer, sebastian); dealer.NewCar("Ferrari"); WeakCarInfoEventManager.RemoveListener(dealer, michael); dealer.NewCar("Red Bull Racing");
}
}
程序入口
public class WeakCarInfoEventManager : WeakEventManager
{
public static void AddListener(object source, IWeakEventListener listener)
{
CurrentManager.ProtectedAddListener(source, listener);
} public static void RemoveListener(object source, IWeakEventListener listener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
} public static WeakCarInfoEventManager CurrentManager
{
get
{
WeakCarInfoEventManager manager = GetCurrentManager(typeof(WeakCarInfoEventManager)) as WeakCarInfoEventManager;
if (manager == null)
{
manager = new WeakCarInfoEventManager();
SetCurrentManager(typeof(WeakCarInfoEventManager), manager);
}
return manager;
}
} protected override void StartListening(object source)
{
(source as CarDealer).NewCarInfo += CarDealer_NewCarInfo;
} void CarDealer_NewCarInfo(object sender, CarInfoEventArgs e)
{
DeliverEvent(sender, e);
}
protected override void StopListening(object source)
{
(source as CarDealer).NewCarInfo -= CarDealer_NewCarInfo;
}
}
弱事件类
public class Consumer : IWeakEventListener
{
private string name; public Consumer(string name)
{
this.name = name;
} public void NewCarIsHere(object sender, CarInfoEventArgs e)
{
Console.WriteLine("{0}: car {1} is new", name, e.Car);
} bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
NewCarIsHere(sender, e as CarInfoEventArgs);
return true;
} }
侦听器
public class CarInfoEventArgs : EventArgs
{
public CarInfoEventArgs(string car)
{
this.Car = car;
} public string Car { get; private set; }
} public class CarDealer
{
public event EventHandler<CarInfoEventArgs> NewCarInfo; public CarDealer()
{ } public void NewCar(string car)
{
Console.WriteLine("CarDealer, new car {0}", car);
if (NewCarInfo != null)
{
NewCarInfo(this, new CarInfoEventArgs(car));
}
}
}
发布程序
执行过程:
注册事件>添加事件>发布程序>事件侦听>执行事件
/*
1)WeakCarInfoEventManager.AddListener
2)WeakCarInfoEventManager.StartListening(object source)
3)dealer.NewCar("Mercedes");
4)NewCarInfo(this, new CarInfoEventArgs(car))
5)WeakCarInfoEventManager.CarDealer_NewCarInfo(object sender, CarInfoEventArgs e)
6)Consumer.ReceiveWeakEvent
7)Consumer.NewCarIsHere
8)WeakCarInfoEventManager.CarDealer_NewCarInfo执行完成
*/
运行过程
@author duanlaibao
@help C# Advanced programming.Nine
@date 13:31:36
C#高级编程9-第8章 委托、lamdba表达式和事件的更多相关文章
- C#高级编程9 第17章 使用VS2013-C#特性
C#高级编程9 第17章 使用VS2013 编辑定位到 如果默认勾选了这项,请去掉勾选,因为勾选之后解决方案的目录会根据当前文件选中. 可以设置项目并行生成数 版本控制软件设置 所有文本编辑器行号显示 ...
- 读《C#高级编程》第1章问题
读<C#高级编程>第1章 .Net机构体系笔记 网红的话:爸爸说我将来会是一个牛逼的程序员,因为我有一个梦,虽然脑壳笨但是做事情很能坚持. 本章主要是了解.Net的结构,都是一些概念,并没 ...
- C#高级编程9 第18章 部署
C#高级编程9 第18章 部署 使用 XCopy 进行部署 本主题演示如何通过将应用程序文件从一台计算机复制到另一台计算机来部署应用程序. 1.将项目中生成的程序集复制到目标计算机,生成的程序集位于项 ...
- C#高级编程9 第16章 错误和异常
C#高级编程9 第16章 错误和异常 了解这章可以学会如何处理系统异常以及错误信息. System.Exception类是.NET运行库抛出的异常,可以继承它定义自己的异常类. try块代码包含的代码 ...
- 第8章 委托、Lamdba表达式和事件
本章内容: 委托 Lambda表达式 事件 8.1.3 简单的委托示例 首先定义一个类MathOperations,它有两个静态方法,对double类型的值执行两个操作. public cl ...
- C#高级编程笔记之第二章:核心C#
变量的初始化和作用域 C#的预定义数据类型 流控制 枚举 名称空间 预处理命令 C#编程的推荐规则和约定 变量的初始化和作用域 初始化 C#有两个方法可以一确保变量在使用前进行了初始化: 变量是字段, ...
- C#高级编程9 第14章 内存管理和指针
C#高级编程9 内存管理和指针 后台内存管理 1) 值数据类型 在处理器的虚拟内存中有一个区域,称为栈,栈存储变量的浅副本数据,通过进入变量的作用域划分区域,通过离开变量的作用域释放. 栈的指针指向栈 ...
- C#高级编程9 第11章 Linq
Linq 1.Linq概述 列表和实体 准备数据: public class Championship { public int Year { get; set; } public string Fi ...
- C#编程 委托 Lambda表达式和事件
委托 如果我们要把方法当做参数来传递的话,就要用到委托.简单来说委托是一个类型,这个类型可以赋值一个方法的引用. 声明委托 在C#中使用一个类分两个阶段,首选定义这个类,告诉编译器这个类由什么字段和方 ...
随机推荐
- 一步一步搭建 oracle 11gR2 rac+dg之grid安装(四)【转】
一步一步在RHEL6.5+VMware Workstation 10上搭建 oracle 11gR2 rac + dg 之grid安装 (四) 转自 一步一步搭建 oracle 11gR2 rac+d ...
- 十五、springboot集成定时任务(Scheduling Tasks)(二)之(线程配置)
配置类: /** * 定时任务线程配置 * */ @Configuration public class SchedulerConfig implements SchedulingConfigurer ...
- Flask: Quickstart解读
Windows 10家庭中文版,Python 3.6.4,Flask 1.0.2 从示例代码说起: from flask import Flask app = Flask(__name__) @app ...
- Python列表(list)
序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. 此外,Python已经内置确定序列的长度以及确定最大和最小的元素 ...
- 【鬼脸原创】谷歌扩展--知乎V2.0
目的: 用键盘替代鼠标,做一个安静刷知乎的美男(女)子! 功能: 功能 按键 说明 直接定位到搜索框 q 打开 首页 w 打开 话题 e 打开 发现 r 打开 消息 m 打开 ...
- 签名DLL
签名DLL 首先需要一个密钥文件,后缀为.snk 密钥文件使用sn.exe 创建: sn.exe /k MySingInKey.snk sn.exe 工具的具体使用,可以通过 sn.exe /h 或 ...
- Postman接口&压力测试
Postman接口与压力测试实例 Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件.它提供功能强大的 Web API & HTTP 请求调试. 1.环境变量和全局 ...
- gtk+学习笔记(七)
今天被一个文本框坑了,基本设置什么的都对,但是就是无法显示中文,按钮名称都可以显示中文,先介绍下文本框的基本函数吧. GtkWidget *gtk_text_view_new(void);新建一个文本 ...
- 树莓派GPIO控制RGB彩色LED灯
树莓派GPIO通过PWM来控制RGB彩色LED灯,可以显示任何我们想要的颜色. RGB模块简介 这个RGB彩色LED里其实有3个灯,分别是红灯.绿灯和蓝灯.控制这三个灯分别发出不同强度的光,混合起来就 ...
- BCD码
BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码,是用4位二进制数来表示1位十进制数中的0~9这10个数码,用一种使用二进制编码十进制的数字编码形式.BCD码这种 ...