开源Asp.Net Core小型社区系统

源码地址:Github

前言

盼星星盼月亮,Asp.Net Core终于发布啦!!

Asp.Net发布时我还在上初中,没有赶上。但是Asp.Net Core我从beta版本便一直关注。最初项目名叫Asp.Net VNext,然后改名叫Asp.Net 5。最煎熬的是RC1发布后,官方继续发布了改名RC2延期的通告。这期间我已经做了一些demo项目,但是由于beta到RC2之间涉及到大量API的改动,包括dnx->dotnet cli,包括各种命名空间和工具名称的改动等等,因此这部分demo都已删掉。5月份,Github Asp.Net Core更新路线图,确定RC2于5月中旬发布,同时确定RC2会作为最终发布的版本基础。那段时间我疯狂的关注着Github,即使在国外度蜜月,也会在晚上蹭Wifi关注着动态(这里提一下,有空看一下各个项目的issue,可以积累很多知识。同时很多小道消息都可以在members的回复中看出来)。好在接下来没有再次跳票,开源、跨平台、高性能的Asp.Net Core终于来啦!

小型社区系统

首先看下项目截图:

项目布局参考了CNodeJS 前端采用了Bootstrap,数据库访问用了EntityFramework Core,同时自己用Middleware实现了一个简单的身份认证功能

目前完成的功能:注册,登录,发帖,回帖,收藏,置顶,精华等功能。

项目地址:GitHub

如何运行:

1. 首先安装基础环境

2. clone或者下载项目,先设置连接字符串,然后还原数据库,最后运行即可

详细流程请点击上方连接查看项目主页

开发感受

1. 对于初学者,Asp.Net Core的入门门槛还是挺高的。

没有了WebForm,无法再拖拖控件就完成一个Hello World Page。

MVC和WebApi合二为一,那么至少对这2种技术应该有些基础了解。

处理HTTP请求从传统的Handler、Page变成了Middleware,如果不熟悉nodejs(express)的话又是个新鲜事物。

搭建一个web项目,首先就用到依赖注入容器,又有多少初学者接触过依赖注入呢?

2. 对于.Net开发者,还有很多东西要学。

新的TagHelper和ViewComponent,看来是要培养起面向组建编程的习惯了。

前端可以方便的集成bower, gulp等,那么NodeJS, npm, bower, gulp等等都是需要学的。

project.json里面的东西涉及到编译、发布、部署等等一系列配置,再结合dotnet命令,可以很简单的实现自动化,想起来是不是很激动?

新的EntityFramework Core Migration,直接基于命令生成和更新数据库,看起来是不是很酷?

整个AspNet Core Framework都开源了,基础源码难道不想去看看?

最最最重要的是跨平台!现在我们再也没法逃避Linux啦,大家赶紧装虚拟机,从最基本的ls开始linux之旅吧!

3. 对于Asp.Net Core,还有很长的路要走

性能:从官方的性能测试看出,目前Asp.Net Core可以超过NodeJS,但是比JAVA的Netty还是差了太多(这个测试看起来还是RC1的版本)。首先我觉得大家应该培养起异步编程的好习惯,这篇文章讲述了异步编程是如何提升并发效率的;其次只能寄希望于微软继续提升性能,或者有第三方高性能web框架出现。

框架:Asp.Net Core从出生起就声明了只是.Net Framework的子集,但是部分基础框架的缺失还是带来了很大的不便。最最不方便的就是System.Drawing。

第三方库:作为一个婴儿,Asp.Net Core才刚出生,又经历跳票,因此这方面资源少得可怜。几大热门项目:Dapper,AutoMapper,Nlog等倒是很早就开始支持了。

开发人员流失:谁敢说身边没有从.Net转Java,转Android,转IOS的??

后记

昨天加班到3点,今天早上继续上班,头都是晕的。个人技术不好,见解不够,以上都是自己的想法,希望大家多多交流,一起为.Net社区出力!!

Delegates, Events, and Anonymous Methods 委托、事件与匿名方法

译者注:委托、事件和匿名方法等在C#编程中有广泛运用,也有很多资料和书籍对它们做过大量介绍,但在我接触的人群中仍有很多人对它们还不甚了解,甚至惧怕。我希望这篇博文能够把这些东西说清讲透,也希望有此需要的园友在阅读之后能够获得对它们的深刻理解,并在今后的编程生涯中熟练地运用它们。还希望这篇博文成为介绍委托的经典的技术文章。

本文的主体内容译自《Introducing Visual C# 2010》(Adam Freeman著,Apress出版)一书的第10章。在译文中用“译者注”对原文作了一些补充说明,以使读者对相关内容有更清醒的认识或更明确的概念。

这是一篇篇幅不短的文章,如能认真阅读,相信一定会有所收获,并从此不再惧怕!

Delegates are special types that encapsulate a method, similar to function pointers found in other programming languages. Delegates have a number of uses in C#, but you are most likely to encounter a special type of delegate—the event. Events make notifying interested parties simple and flexible, and I’ll explain the convention for their use later in the chapter.
委托是封装方法的特殊类型,它类似于其它编程语言中的函数指针。委托在C#中有大量运用,但你最可能遇到的是一种特殊的委托类型 — 事件。事件使得对相关部件的通知变得简单而灵活,本章后面将解释其使用约定。

I’ll also explain the Func and Action types that let you use delegates in a more convenient form and that are used extensively in some of the latest C# language features, such as parallel programming. We’ll finish up this chapter with a look at anonymous methods and lambda expressions, two C# features that let us implement delegates without having to define methods in our classes. Table 10-1 provides the summary for this chapter
我也会解释Func和Action类型,它们让你以更方便的形式使用委托,而且在一些最新的C#特性中也有广泛使用,如并行编程。本章最后考察匿名方法和lambda表达式,这是让我们不必在类中定义方法就可以使用委托的两个C#特性。表10-1提供了本章概要。

以上这两段文字告诉我们:委托是一种封装方法的特殊类型。事件是特殊形式的委托,用以实现对相关部件的通知。FuncAction是C#的两个特殊类型,使我们能够更方便地使用委托。匿名方法Lambda表达式是C#的两个特性,让我们不必定义方法就可以使用委托。 — 译者注

Using Delegates
使用委托

A delegate is a special C# type that represents a method signature. Methods are discussed in Chapter 9, and the signature is the combination of the return type and the type and order of the method parameters. Listing 10-1 contains an example of a delegate.
委托delegate)是表示方法签名的一种特殊的C#类型。方法在第9章讨论过,而方法签名是方法的返回类型和方法参数的类型及其顺序的组合。清单10-1是一个委托示例。

以上是委托的概念定义。很多人都知道委托是用来关联方法的,但未能强烈意识到,委托更主要的是定义和使用一种类型。既然是一种类型,委托的使用便与类的使用具有类似性。因此,关于委托的使用通常应当包含这样几个环节:定义委托、创建委托对象、委托对象实例化、执行或调用委托。

另外要特别注意的是,关于委托的名词是混淆的。委托定义、委托对象、委托实例、以及委托调用等,有时不作明确的区分,都笼统地叫做委托。因此,在委托的使用过程中,必须从概念上分清什么是委托定义、委托对象、委托实例、以及什么是执行或调用委托。 — 译者注

Listing 10-1. Defining a Delegate Type
清单10-1. 定义一个委托类型

public delegate int PerformCalc(int x, int y);

There are five parts to the delegate in Listing 10-1, and they are illustrated in Figure 10-1.
清单10-1所示的委托有五个部分,它们如图10-1所示。

IntrC10-11. Access Modifier — 访问修饰符 2. Delegate Keyword — delegate关键字 3. Result Type — 结果类型 4. Delegate Name — 委托名 5. Parameters — 参数

Figure 10-1. The anatomy of a delegate
图10-1. 一个委托的剖析

The first two parts of a delegate are simple. First, all delegates require the delegate keyword. Second, delegates, like all types, can have access modifiers. See Chapter 6 for a description of how these modifiers apply to classes; they have the same effect on delegates.
委托的前两个部分很简单。首先,所有委托都需要delegate关键字。其次,像所有类型一样,委托可以有访问修饰符。参见第6章如何把这些修饰符运用于类的描述,它们在委托上有同样的效果。

The delegate name is the name by which we will refer to the type we have created. This is equivalent to the class name. The name of the delegate type in Listing 10-1 is PerformCalc.
委托名是用来指向已创建的这个类型的名称。它等同于类名。清单10-1中的委托类型名是PerformCalc。

The remaining parts of the delegate specify the kind of method that instances of this delegate can represent. In Listing 10-1, instances of the delegate can represent methods that return an int and that have two int parameters.
该委托的其余部分指明了这个委托的实例可以代表的方法的种类。在清单10-1中,委托的实例可以代表返回一个int(整数)且有两个int参数的所有方法。

As we look at each part of the delegate in Listing 10-1, it is important to bear in mind that when we define a new delegate, we are defining a new type. What we are saying is, “Here is a new type that can be used to refer to a specific kind of method.” Delegates can be hard to understand, and if you find yourself getting lost in this chapter, you should come back to the previous sentence. You can define a new delegate type in the same places as you can create a new class—in a namespace, class, or struct. Once we have defined a new delegate type, we can create an instance of it and initialize it with a value. Listing 10-2 contains a demonstration.
在我们考察清单10-1中委托的各个部分时,重要的是记住:定义一个新委托时,实际是在定义一个新类型。就好像在说:“这是一个新类型,它可以用来指向一类特定的方法”。委托可能难以理解,但如果在本章中发现自己迷失了方向,你应该回想上面这句话。就像可以定义一个新类一样,你可以在定义类的那些地方定义一个新的委托类型 — 在命名空间、类、或结构中。一旦定义了一个新的委托类型,就可以创建它的实例,并用一个值对它初始化。清单10-2是一个演示。

Listing 10-2. Defining a Delegate Field
清单10-2. 定义一个委托字段

// 定义一个委托
public delegate int PerformCalc(int x, int y);
class Calculator {
    // 委托类型字段,用以创建委托对象
    PerformCalc perfCalc;
    // 无访问修饰符的字段意为private(私有)
    // 构造器
    public Calculator() {
        // 实例化委托对象。
        // 对委托对象进行实例化的办法是,用一个方法名对委托对象进行赋值。
        // 于是,以下语句的含义为,perfCalc委托是对CalculateProduct方法的引用
        perfCalc = CalculateProduct;
    }
    // 属性,用以暴露委托对象
    public PerformCalc CalcDelegate {
        get { return perfCalc; }
    }
    // 方法,与委托类型具有相同的方法签名,用以对委托对象实例化
    private int CalculateProduct(int num1, int num2) {
        return num1 * num2;
    }
}

The Calculator class in Listing 10-2 has a field called perfCalc that is of the type of delegate we defined in Listing 10-1. This has created a field that can be used to represent a method that matches the delegate, in other words, a method that returns an int and has two int parameters. The Calculator class contains a method called CalculateProduct that matches that description, and in the Calculator constructor, I assign a value to the delegate field by using the name of the matching method. The definition of the field and the assignment of a value are shown in bold.
清单10-2中的Calculator类有一个叫做perfCalc的字段,它的类型是清单10-1中所定义的委托类型。它创建了一个字段,可以用来表示与委托匹配的方法,即,一个返回int并有两个int参数的方法。Calculator类有一个叫做CalculateProduct的方法与这个描述相匹配,而且在Calculator构造器中,通过使用这个匹配的方法名给这个委托字段赋了一个值。这个字段的定义和赋值以黑体显示。

The Calculator class in Listing 10-2 also contains a public property that returns an instance of the delegate type. The accessor in the property returns the value assigned to the delegate field.
清单10-2中的Calculator类还有一个public属性,它返回委托类型的一个实例。这个属性的访问器(指属性的getter块 — 译者注)返回赋给委托字段的值。

Now we have a new delegate type, PerformCalc, instances of it can be used to represent methods that return an int and that have two int parameters. We have a Calculator class that has a private field of the PerformCalc type and that has been assigned the CalculateMethod and a public property that returns the value of the delegate field. Listing 10-3 demonstrates how to use the delegate.
现在,我们有了一个新的委托类型PerformCalc。它的实例可以用来表示返回一个int并有两个int参数的方法。有一个Calculator类,它有一个PerformCalc类型的private(私有)字段,并把CalculateMethod(应当是CalculateProduct — 译者注)赋给了它。还有一个public属性,它返回该委托字段的值。清单10-3演示了如何使用这个委托。

Listing 10-3. Using a Delegate Obtained Through a Property
清单10-3. 使用通过属性获得的委托

class Listing_03 {
    static void Main(string[] args) {
        Calculator calc = new Calculator();
        // get the delegate
        // 获取委托
        PerformCalc del = calc.CalcDelegate; 
        // invoke the delegate to get a result
        // 调用该委托以获得结果
        int result = del(10, 20); 
        // print out the result
        // 打印结果
        Console.WriteLine("Result: {0}", result); 
        // wait for input before exiting
        // 退出之前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

A new instance of the Calculator class is created, and the CalcDelegate property is used to assign a value to a local variable of the PerformCalc delegate type; this means that the del variable contains a reference to the CalculateProduct method in the Calculator object. I invoke the delegate with the following statement:
上述代码创建了Calculator类的一个新实例,用CalcDelegate属性把一个值赋给了PerformCalc委托类型的一个局部变量,这意味着del变量是对Calculator对象中CalculateProduct方法的引用。用以下语句调用这个委托:

int result = del(10, 20);

This statement passes the parameters 10 and 20 to the method assigned to the del variable, which means that the CalculateProduct method in the Calculator object is called. The result from the delegated method is assigned to the local result variable, just as would happen with a regular method call.
这条语句把参数10和20传递给赋给del变量的方法,这意味着调用Calculator对象中的CalculateProduct方法。从这个委托方法而来的结果被赋值给局部变量result,这就像调用一个常规方法所发生的情况一样。(可见,调用委托就像执行常规的方法一样 — 译者注)

总结上述委托编程过程,可以从概念上形成以下几个名词:

  1. 委托定义:委托定义的作用是创建一个能够封装一类方法的类型。
  2. 委托字段:在委托编程中需要有一个委托字段,该字段可以用来创建委托对象。也可以把这个委托字段看成为是一个委托类型的变量(简称为委托变量)。于是,随时可以用一个方法对这个委托变量进行赋值。
  3. 委托实例:这是对委托字段的实例化,以形成委托对象。实例化的办法是用一个具有相同签名的方法名对委托变量进行赋值。实例化的作用是把委托字段与实例化方法关联在一起,形成委托对象。因此,无论何时,调用委托就是执行其实例化方法。
  4. 委托属性:可以用委托属性对外暴露委托对象。
  5. 执行/调用委托:执行或调用委托实际上是执行委托对象的实例化方法,执行/调用委托与执行规则的方法一样(送入方法参数、接收返回结果)。 — 译者注

The reason that I created a new Calculator object is that I wanted to delegate an instance method, and you can do that only once you have an instance to work with. If you want to delegate a static method, then you can do so by using the class name; you can see an example of delegating a static method in Listing 10-4 later in the chapter.
创建一个新的Calculator对象的原因是想委托一个实例方法,而且你只要这样做一次,就有了一个用来进行工作的实例。如果想委托一个静态方法,那么可以用这个类名来做,本章稍后可以看到委托一个静态方法的示例。

You can also use generic types with delegates. If we wanted a generic version of the delegate type defined in Listing 10-1, we could define the following:
也可以使用委托的泛型类型。如果想定义类似于清单10-1所示的委托类型的泛型类型,可以这样定义:

public delegate T PerformCalc<T>(T x, T y);

Then to create the delegate field in the Calculator class, we would use the following:
然后创建Calculator类中的委托字段,像这样:

class Calculator {
    PerformCalc<int> perfCalc;
    ...

In this way, we can define a general-purpose delegate and provide the type parameters as needed when we define the delegate fields or variables. Generic types are described in Chapter 15.
通过这种方式,我们可以定义一个通用目的的委托,并在定义委托字段或变量时,提供必要的类型参数。泛型类型在第15章描述。

There are a couple of points to note about all the examples so far. The first is that we passed around a method as we would a regular variable, invoking it only when we needed. The other is that the class that called the delegated method had no direct relationship to the method being invoked. We delegated a private method hidden away inside the Calculator class that the Listing_03 class wouldn’t otherwise be able to access.
对于上述这些例子,有两点需要注意。第一点是我们像传递常规的变量一样来传递一个方法,只在需要的时候调用它(可见,我们可以像对变量赋值一样,把一个方法赋给一个委托,还可以像变量一样对委托进行传递。于是,通过委托使得对方法的传递变得十分灵活 — 译者注)。另一点是调用委托方法的类与被请求的方法没有直接的关系(从而实现了方法的使用与方法的具体实现之间的分离。于是,方法体可以被重构或被替换 — 译者注)。我们委托了在Calculator类中被隐藏起来的一个私有方法,这个方法因而在Listing_03类中是不能访问的。

The examples have shown how to use delegates but didn’t really explain why you might find them useful. In the following sections, I’ll show you ways to use delegates that simplify common coding patterns and demonstrate some useful C# features.
这些例子演示了如何使用委托,但并未解释为什么它们是有用的。在以下小节中,将演示使用委托的方式,以达到简化常规的编码模式,并演示一些有用的C#特性。

Using Delegates for Callbacks
用委托进行回调

You can use delegates to create callbacks, where one object is notified when something of interest happens in another object. Listing 10-4 contains an example of a simple callback to notify an interested class when a calculation is performed.
可以用委托来创建回调callback),即,当在一个对象中发生某个关心的事情时,通知另一个对象。清单10-4是一个简单回调的示例,以便在计算完成时通知一个感兴趣的类。

Listing 10-4. Using a Delegate for a Callback
清单10-4. 使用委托进行回调

using System; 
// 委托字义
delegate void NotifyCalculation(int x, int y, int result); 
class Calculator {
    // 委托字段
    NotifyCalculation calcListener; 
    // 构造器,用参数给委托字段赋值(委托实例化) — 译者注
    public Calculator(NotifyCalculation listener) {
        calcListener = listener;
    }
    // 方法,计算两数乘积,在其中通过委托向另一对象发送通知 — 译者注
    public int CalculateProduct(int num1, int num2) {
        // perform the calculation
        // 执行计算
        int result = num1 * num2; 
        // notify the delegate that we have performed a calc
        // 向委托发送通知,说明已经执行了一个计算
        calcListener(num1, num2, result); 
        // return the result
        // 返回结果
        return result;
    }
}
// 接收通知的类 — 译者注
class CalculationListener {
    // 与委托的方法签名对应的方法。
    // 注意,这是一个静态方法,因此不需要在这个类中创建委托字段并实例化,
    // 可以直接通过类名返回该方法的一个实例,如Calculation.CalculationPrinter — 译者注
    public static void CalculationPrinter(int x, int y, int result) {
        Console.WriteLine("Calculation Notification: {0} x {1} = {2}",
                        x, y, result);
    }
}
// 主程序
class Listing_04 {
    static void Main(string[] args) {
        // create a new Calculator, passing in the printer method
        // 创建一个新的Calculator,在其中传递printer方法
        // 于是把一个方法对象注入到了Calculator对象的calc之中形成了一个委托实例 — 译者注
        Calculator calc = new Calculator(CalculationListener.CalculationPrinter);
        // perform some calculations
        // 执行一些计算
        // 注意,每执行一次计算,在计算体里都会回调委托向CalculationListener发送一个通知
        // 通知的作用导致执行CalculationListener中的printer方法 — 译者注
        calc.CalculateProduct(10, 20);
        calc.CalculateProduct(2, 3);
        calc.CalculateProduct(20, 1); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

回调是事件的基础,请通过上述清单真正理解委托回调:首先把一个接收通知的方法注入到一个对象(通知源对象,也即发出通知的对象)之中(上例中的黑体语句),以形成通知源中的委托实例。然后在通知源对象的某个方法中,通过执行这个委托实例,以便回过头来调用(回调)这个被委托方法,从而实现向目标对象(或目标方法)发送通知的目的。因此,所谓委托回调是指通过委托实现回调 — 译者注

The delegate type in this example is called NotifyCalculation and has parameters for the two numbers that have been used for the calculation and the result that was computed. The Calculator class in this example has a constructor argument that takes an instance of the delegate type, which is then invoked inside the CalculateProduct method.
此例中的委托类型叫做NotifyCalculation,其参数是用于计算的两个数字和计算所得的结果。这个例子中的Calculator类有一个构造器参数,它是委托类型的一个实例,在CalculatreProduct方法中对这个实例进行调用。

The Listing_04 class creates a new instance of Calculator and passes a reference to the static CalculationListener.CalculationPrinter method as the constructor parameter. The delegate is called each time the Calculator.CalculateProduct method is invoked, printing out a notification of the calculation that has been performed. Compiling and running the code in Listing 10-4 produces the following result:
Listing_04类创建了一个新的Calculator实例,并把对静态方法CalculationListener.CalculationPrinter的引用作为构造器参数进行传递。每次请求Calculator.CalculateProduct方法都会调用这个委托,打印出一个计算已被执行的通知,编译并运行清单10-4中的代码产生以下结果:

Calculation Notification: 10 x 20 = 200
Calculation Notification: 2 x 3 = 6
Calculation Notification: 20 x 1 = 20
Press enter to finish(按回车键结束)

Using delegates in callbacks means that the source of the notifications doesn’t need to know anything about the class that receives them, allowing the notification receiver to be refactored or replaced without the source having to be modified at all. As you’ll see in the “Delegating Selectively” section later in the chapter, we can select a delegate at runtime, which provides us with even greater flexibility.
在回调中使用委托,意味着通知源不需要知道通知接收者的任何信息,这允许通知接受者被重构或被替换,而源根本不需要做任何修改。正如你将在稍后的“选择性委托”小节中所看到的,可以在运行时选择一个委托,这为我们提供了更大的灵活性。

Multicasting with Delegates
委托多播

When performing callbacks, you will often need to cater for multiple listeners, rather than the single listener shown in Listing 10-4. The delegate type uses custom + and – operators that let you combine several method references together into a single delegate and invoke them in one go, known as multicasting. Custom operators are discussed in Chapter 8. Listing 10-5 contains an example of a multicasting delegate callback.
在执行回调时,通常需要迎合多个侦听器(通知接收者 — 译者注),而不是如清单10-4所示的一个单一的侦听器。委托类型采用 + 和 – 操作符,允许你把对几个方法的引用结合在一起,形成一个单一的委托,并一次性地调用它们,这称为多播multicasting)(因此,多播是一次性调用委托,达到向多个目标发送通知的目的 — 译者注)。自定义操作符在第8章讨论过。清单10-5是一个多播委托回调的例子。

Listing 10-5. Using Delegate Multicasting
清单10-5. 使用委托多播

using System; 
// 委托定义
delegate void NotifyCalculation(int x, int y, int result); 
class Calculator {
    // 委托字段
    NotifyCalculation calcListener; 
    // 方法,增加委托
    public void AddListener(NotifyCalculation listener) {
        calcListener += listener;
    }
    // 方法,去除委托
    public void RemoveListener(NotifyCalculation listener) {
        calcListener -= listener;
    }
    // 方法,执行乘积计算
    public int CalculateProduct(int num1, int num2) {
        // perform the calculation
        // 执行计算
        int result = num1 * num2; 
        // notify the delegate that we have performed a calc
        // 通知委托,告知已执行了一个计算
        calcListener(num1, num2, result); 
        // return the result
        // 返回结果
        return result;
    }
}
// 侦听器类
class CalculationListener {
    // 字段,表示侦听器id
    private string idString; 
    // 构造器,设置侦听器id
    public CalculationListener(string id) {
        idString = id;
    }
    // 与委托签名对应的方法,调用委托时将执行此方法
    public void CalculationPrinter(int x, int y, int result) {
        Console.WriteLine("{0}: Notification: {1} x {2} = {3}",
                    idString, x, y, result);
    }
}
// 另一个侦听器类
class AlternateListener {
    public static void CalculationCallback(int x, int y, int result) {
        Console.WriteLine("Callback: {0} x {1} = {2}",
                    x, y, result);
    }
}
// 主程序
class Listing_05 {
    static void Main(string[] args) {
        // create a new Calculator
        // 创建一个新计算器
        Calculator calc = new Calculator();
        // create and add listeners
        // 创建并添加侦听器,这称为订阅通知 — 译者注
        calc.AddListener(new CalculationListener("List1").CalculationPrinter);
        calc.AddListener(new CalculationListener("List2").CalculationPrinter);
        calc.AddListener(AlternateListener.CalculationCallback); 
        // perform a calculation
        // 执行一个计算
        calc.CalculateProduct(10, 20); 
        // remove a listener
        // 移去一个侦听器,这称为退订通知 — 译者注
        calc.RemoveListener(AlternateListener.CalculationCallback); 
        // perform a calculation
        // 执行一个计算
        calc.CalculateProduct(10, 30); 
        // wait for input before exiting
        // 退出之前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The Calculator class in this example defines two methods that register and unregister callback delegates using the += and -= operators. There are two classes that contain methods that match the delegate signature, CalculationListener and AlternateListener, and the Listing_05 class registers and unregisters the methods as delegates with the Calculator object. You can see that you use a multicast delegate just as you would a single delegate. Compiling and running the code in Listing 10-5 produces the following results:
此例中的Calculator类定义了两个方法,用 += 和 -= 操作符注册和注销回调委托。有两个类含有与委托签名匹配的方法,CalculationListener和AlternateListener,而Listing_05类以Calculator对象来注册和注销委托方法。可以看出,使用多播委托就像使用单个委托一样。编译并运行清单10-5代码会产生如下结果:

List1: Notification: 10 x 20 = 200
List2: Notification: 10 x 20 = 200
Callback: 10 x 20 = 200
List1: Notification: 10 x 30 = 300
List2: Notification: 10 x 30 = 300
Press enter to finish

可以通过以下描述体会委托多播的价值:假设我们要做一个多国语言翻译机,把一国文字翻译成多个国家的文字。于是可以把英-汉、英-日、英-俄、英-德等多个翻译方法组合成一个委托多播,然后通过一次性委托调用得到全部翻译结果 — 译者注

Delegating Selectively
选择性委托

One of the benefits of being able to pass delegates around as variables is to apply delegates selectively, such that we create a delegate that is tailored to a given situation. Listing 10-6 contains a simple example.
能够把委托像变量一样传递的一个好处是可以有选择地运用委托,这样,我们可以针对一个给定的情况来创建委托。清单10-6是一个简单的例子。

Listing 10-6. Creating Anonymous Delegates Based on Parameter Value
清单10-6. 创建基于参数值的匿名委托

using System; 
// 委托定义
delegate int PerformCalc(int x, int y); 
// 计算器类
class Calculator {
    public enum Modes {
        Normal,
        Iterative
    };
    public PerformCalc GetDelegate(Modes mode) {
        if (mode == Modes.Normal) {
            return CalculateNormally;
        } else {
            return CalculateIteratively;
        }
    }
    // 与委托签名一致的方法,乘积
    private int CalculateNormally(int x, int y) {
        return x * y;
    }
    // 与委托签名一致的方法,累加
    private int CalculateIteratively(int x, int y) {
        int result = 0;
        for (int i = 0; i < x; i++) {
            result += y;
        }
        return result;
    }
}
// 主程序
class Listing_06 {
    static void Main(string[] args) {
        // create a new Calculator
        // 创建一个新Calculator
        Calculator calc = new Calculator();
        // get a delegate
        // 获取一个委托
        PerformCalc del = calc.GetDelegate(Calculator.Modes.Normal); 
        // use the delegate
        // 使用该委托
        Console.WriteLine("Normal product: {0}", del(10, 20)); 
        // get a delegate
        // 获取一个委托
        del = calc.GetDelegate(Calculator.Modes.Iterative); 
        // use the delegate
        // 使用该委托
        Console.WriteLine("Iterative product: {0}", del(10, 20)); 
        // wait for input before exiting
        // 退出之前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The Calculator class in Listing 10-6 has a GetDelegate method that returns an delegate based on the parameter value, selected from an enum. If the parameter is the Normal enum value, the delegate returned by the method uses the standard C# multiplication operator, but if the value is Iterative, then the method returns a delegate that performs multiplication as an iterative series of additions.
清单10-6中的Calculator类有一个GetDelegate方法,它返回一个基于参数值的、从一个enum(枚举)选择的委托。如果参数是Normal枚举值,由方法返回的委托使用标准的C#乘法运算符,但如果该值是Iterative,那么该方法返回另一种委托,它像反复累加那样执行乘法。

Interrogating Delegates
质询委托

The base type for all delegates is System.Delegate, and we can use the members of this class to find out which methods a delegate will invoke on our behalf. Listing 10-7 contains an example.
所有委托的基类型都是System.Delegate,而且可以使用该类的成员为我们找出一个委托所调用的方法。清单10-7是一个示例。

Listing 10-7. Interrogating Delegate Types
清单10-7. 质询委托类型

using System; 
delegate int PerformCalc(int x, int y); 
class Calculator {
    public int CalculateSum(int x, int y) {
        return x + y;
    }
}
class AlternateCalculator {
    public int CalculateProduct(int x, int y) {
        return x * y;
    }
}
class Listing_07 {
    static void Main(string[] args) {
        // create a delegate variable
        // 创建委托变量
        PerformCalc del = new Calculator().CalculateSum; 
        // combine with another method
        // 组合另一个方法
        del += new AlternateCalculator().CalculateProduct; 
        // Interrogate the delegate
        // 质询委托
        Delegate[] inlist = del.GetInvocationList();
        foreach (Delegate d in inlist) {
            Console.WriteLine("Target: {0}", d.Target);
            Console.WriteLine("Method: {0}", d.Method);
        }
        // wait for input before exiting
        // 退出之前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The Listing_07 class creates a new delegate variable and uses it to combine methods from the Calculator and AlternateCalculator classes. I use the GetInvocationList method on the delegate variable, which returns an array of System.Delegate objects. I enumerate the contents of the array with a foreach loop and print out the value of the Target and Method properties for each Delegate object. (C# arrays are described in Chapter 13.) Table 10-2 describes the Target and Method properties.
Listing_07类创建了一个新的委托变量,并用它把Calculator和AlternateCalculator的方法组合起来。这里使用了委托变量的GetInvocationList方法,它返回一个System.Delegate对象的数组。用foreach循环枚举了该数组的内容,并打印出每个Delegate对象的Target和Method属性(C#数组在第13章描述)。表10-2描述了Target和Method属性。

Table 10-2. The System.Delegate Properties
表10-2. System.Delegate属性
Property
属性
Description
描述
Target Returns the object that the delegate will use to invoke the method or null if the delegate method is static.
返回委托用来调用方法的对象(目标方法的父类 — 译者注),若委托方法是static(静态的),则返回null。
Method Returns a System.Reflection.MethodInfo that describes the method that will be invoked by the delegate.
返回一个System.Reflection.MethodInfo(方法信息),它描述由委托调用的方法(目标方法本身的信息 — 译者注)。

Compiling and running the code in Listing 10-7 produces the following results:
编译并运行清单10-7代码将产生以下结果:

Target: Calculator
Method: Int32 CalculateSum(Int32, Int32)
Target: AlternateCalculator
Method: Int32 CalculateProduct(Int32, Int32)
Press enter to finish

从程序设计角度讲,选择性委托与质询委托属于两个相反方向的委托处理。利用选择性委托,我们可以根据某些条件有选择地创建委托;而通过质询委托,我们可以根据委托的一些信息有选择地去做某些事情。 — 译者注

Using Events
使用事件

Events are specialized delegates designed to simplify the callback model we saw earlier in the chapter. There can be a problem when you use a delegate type as a field, where one object interferes with another. Listing 10-8 contains a demonstration.
事件是设计用来简化前述回调模型的特殊委托(这是事件的定义,即,事件是执行回调(或发送通知)的一种特殊形式的委托 — 译者注)。在将委托用作为字段时,可能存在一个问题:一个对象会干扰另一个对象。清单10-8是一个演示。

Listing 10-8. One Type Modifying a Delegate Supplied by Another Type
清单10-8. 一个类型修改了由另一个类型提供的委托

using System; 
// 委托
delegate void NotifyCalculation(int x, int y, int result); 
// 类,计算器
class Calculator {
    // 委托字段
    public static NotifyCalculation CalculationPerformed; 
    // 方法,计算乘积
    public static int CalculateProduct(int num1, int num2) {
        // perform the calculation
        // 执行计算,计算乘积
        int result = num1 * num2; 
        // notify any listeners
        // 通知各个侦听器
        CalculationPerformed(num1, num2, result); 
        // return the result
        // 返回结果
        return result;
    }
}
// 类,一个邪恶的类
class NefariousClass {
    // 委托字段
    private NotifyCalculation orig; 
    // 构造器
    public NefariousClass() {
        // get a reference to the existing listener
        // 获取对现有侦听器的一个引用
        orig = Calculator.CalculationPerformed; 
        // set a new listener for Calculator
        // 对Calculator设置一个新的侦听器(将其指向了下面的那个扭曲的方法 — 译者注)
        Calculator.CalculationPerformed = HandleNotifyCalculation;
    }
    // 方法,一个扭曲的方法。在这个方法中做了两件事:
    // (1)调用原委托谎报结果;(2)自己打印出正确结果 — 译者注
    public void HandleNotifyCalculation(int x, int y, int result) {
        // lie to the original listener
        // 谎报原侦听器
        // 把不良信息送入了原委托方法,这里送入的是加法结果 — 译者注
        orig(x, y, x + y); 
        // print out the details of the real calculation
        // 打印实际计算的细节
        Console.WriteLine("NefariousClass: {0} x {1} = {2}",
                x, y, result);
    }
}
class Listing_08 {
    static void Main(string[] args) {
        // set a listener for the Calculator class
        // 设置对Calculator类的一个侦听器
        Calculator.CalculationPerformed = StandardHandleResult; 
        // create an instance of the Nefarious class
        // 创建Nefarious类的一个实例
        // 注意,在创建这个实例时,原侦听器已经被修改,
        // 而指向了NefariousClass类的HandleNotifyCalculation方法 — 译者注
        NefariousClass nc = new NefariousClass();
        // perform a calculation
        // 执行一个计算。
        // 此时在CalculateProduct方法中执行委托回调时,
        // 实际执行的已经是HandleNotifyCalculation方法了 — 译者注
        Calculator.CalculateProduct(20, 72); 
        // wait for input before exiting
        // 退出之前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
    private static void StandardHandleResult(int x, int y, int result) {
        Console.WriteLine("Good Class: {0} x {1} = {2}", x, y, result);
    }
}

In this example, the Listing_08 class contains a method that matches the delegate type used for the Calculator.CalculationPerformed field. This method is used to process callbacks from the Calculator class. The idea is that the anonymous method will be called each time a calculation is performed by the Calculator class, just as in some of the earlier examples.
在这个例子中,Listing_08类含有一个用于Calculator.CalculationPerformed字段的委托类型相匹配的方法。该方法用于处理Calculator类的回调。其思想是Calculator类每执行一次计算,都调用这个方法(原文中说这是一个匿名方法,其实不是 — 译者注),就像前面那些例子一样。

The Listing_08 class also creates a new NefariousClass object, and the fun begins. The NefariousClass constructor assigns a new method to the Calculator delegate field, displacing the original. This method then feeds bad information to the original value of the delegate field. If we compile and run the code in Listing 10-9, we get the following results:
Listing_08类也创建了一个新的NefariousClass对象,于是开玩笑的事便开始了。NefariousrClass构造器把一个新方法赋给了Calcualtor的委托字段,替换了原有方法。然后这个方法(指已接管的方法HandleNotifyCalculation — 译者注)把不良信息送入了原委托字段。如果编译并运行清单10-9,会得到以下结果:

Good Class: 20 x 72 = 92
NefariousClass: 20 x 72 = 1440
Press enter to finish

The original method is invoked each time a calculation is performed, but NefariousClass has inserted itself in the way and changes the details of the calculation that is reported. And the problems don’t stop there—because the delegate field is public, any object can invoke the delegate as it wants, simulating callbacks even though no calculation has been performed.
每次执行一个计算时都会请求原方法,但NefariousClass阻挡了这一过程,并修改了所报告的计算细节。问题不仅于此 — 由于委托字段是public的,因此任何对象都可以在需要时请求这个委托,甚至在计算尚未执行时就模拟回调。

This example demonstrates deliberate interference, but most of the problems with public delegate fields arise because of sloppy programming, where an object makes an explicit assignment using = to set the value of the delegate field instead of using += to combine delegates. You could take steps to avoid this problem—make the delegate field private and implement methods that enforce checks to ensure that objects are not interfering with each other—but the C# event feature takes care of this for you. Defining an event is just like defining a delegate field, with the addition of the event keyword. So, here’s our delegate field from Listing 10-9:
这个示例演示了有意的干扰,但public委托字段的大多数问题都是由于草率编程所引发的(所以,在委托编程中,委托字段的访问修饰是重要的 — 译者注),在这些场合中,对象都是用 = 来设置委托字段的值,以形成一个明确的赋值,而不是用 += 来组合委托。可以采取一些步骤来避免这类问题 — 让委托字段为private的,并实现强制检查,以确保对象不会相互干扰 — 不过,C#的事件可以帮你照顾这些事。定义事件就像定义委托字段一样,只要附加一个event关键字即可。因此,在以下是清单10-9的委托字段:

class Calculator {
    public static NotifyCalculation CalculationPerformed;

becomes the following:
把它改成下面这样:

class Calculator {
    public static event NotifyCalculation CalculationPerformed;

注意,event关键字是附加在委托字段上的,因此,可以把事件看成是一种特殊形式的委托对象,是经过event修饰的一种委托对象,它让委托对象的使用和操作受到一定的限制和约束。通过使用事件,便不会出现上述一个对象干扰另一个对象之类的问题 — 译者注

When you make a delegate into an event, the class that contains the field can still invoke the delegate and make full use of the type members and operators. Every other object can use only the += and -= operators to add and subtract methods. It is no longer possible to interrogate the delegate or replace the methods assigned to the delegate as I did in Listing 10-8. Although you can make any delegate into an event, there is strong convention in C# to use a certain pattern for events known as the EventHandler pattern, which makes it easier for others to use your events in their code. I describe the pattern in the following sections.
当你把一个委托变成一个事件时,包含该字段的类仍然能够调用委托,并充分利用类型成员和操作符。每一个其它对象都只能用 += 和 -= 操作符来添加和移去方法。这就不再能像清单10-8所做的那样去质询委托,或替换赋给委托的方法。虽然,你可以让任何委托成为事件,但C#有强行的约定,它要求使用事件的特定模式,这称为EventHandler模式,它使其他人更易于在他们的代码中使用你的事件(可见,EventHandler模式是一种编写事件的约定模式,该模式使大家能以统一的方式编写和使用事件,便于事件编程,也便于事件互用 — 译者注)。以下几小节描述这个模式。

注:由于事件是用来实现委托回调的,为了更好地理解以下的事件编程模式,这里对委托回调作以下回顾和总结:

  1. 委托回调可以实现由一个对象(通知源)向外部发送通知的目的。
  2. 通知源需要有一个委托字段和一个发出通知的方法。委托字段用以接收外部方法的注入,形成一个委托实例。发出通知的方法通过执行委托实例,实现向外发送通知的目的。
  3. 在通知源的外部,需要有一个可以注入通知源的方法(接收通知的方法),以便把它注入到通知源形成委托实例,并在通知源发出通知时接收通知,而且在接收到通知时作相应的处理工作。接收通知的方法有时也叫侦听器,意即在侦听到(接收到)通知时,对通知做出响应。
  4. 在实现通知的编程中(主程序中),需要将接收通知的方法注入到通知源中,这称为通知的订阅。

如果把上述委托回调对应到事件场景,应当有以下环节:

  1. 事件是用来实现委托回调的。因此,事件的目的是为了从一个对象(事件源)向外发送事件通知,以便外部对象在接收到通知时进行事件处理。
  2. 事件源需要定义一个事件句柄(事件字段)和一个发送事件通知的方法。事件句柄用以接收事件侦听器(处理事件的方法)的注入,以形成事件委托对象。发送事件通知的方法通过执行事件委托,向外部发送事件通知,就好像向外部通告:“某个事件已经发生啦”。
  3. 事件侦听器是另一个实体中可以作为侦听器注入到事件源中的事件处理方法,以便在接收到事件通知时,对所发生的事件做出响应。
  4. 在实现事件的编程中(主程序中),需要将侦听器注入到事件源中,这通常称为事件订阅

由上可见,事件的本质与委托回调是一致的,但为了克服前述委托回调所具有的干扰,以及让事件有统一的编程和使用方式,对事件的编程需采用特定的编程模式,这个模式称为EventHandler事件编程模式。 — 译者注

Defining and Publishing EventHandler Pattern Events
定义并发布EventHandler模式的事件

The first step in defining an event is to derive a class from the System.EventArgs class, which contains properties and fields to contain any custom data that you want to pass when you invoke the event delegate; the name of this class should end with EventArgs. Listing 10-9 contains an example for the calculation notification from earlier examples.
定义事件的第一步是从System.EventArgs类(事件参数类)派生一个类,这个类需要包含在调用事件委托时希望传递的所有自定义数据的各种字段和属性,这个类的名称应当以EventArgs结尾。清单10-9是针对前面例子中计算通知的一个示例。

Listing 10-9. A Custom EventArgs Implementation
清单10-9. 一个自定义EventArgs实现

class CalculationEventArgs : EventArgs {
    // 定义事件委托的所有参数字段。
    // 注意,这些字段都是private的 — 译者注
    private int x, y, result; 
    // 构造器
    public CalculationEventArgs(int num1, int num2, int resultVal) {
        x = num1;
        y = num2;
        result = resultVal;
    }
    // 以下是与字段对应的属性。注意,这些属性都只有getter块,即都是只读的 — 译者注
    public int X {
        get { return x; }
    }
    public int Y {
        get { return y; }
    }
    public int Result {
        get { return result; }
    }
}

You can include any fields and properties that you need to express information about your event, but you should make sure that the fields cannot be modified. This is because the same EventArgs object will be passed to each of the subscribers of your event, and a poorly or maliciously coded recipient could change the information that subsequent listeners receive. The CalculationEventArgs class derives from EventArgs and defines fields for the details of our calculation and a set of read-only properties to provide access to the field values. You can get more information about properties in Chapter 8 and more information about fields in Chapter 7.
你可以包括需要表示事件信息的任何字段和属性,但要确保字段不能被修改(即,是只读的 — 译者注)。这是因为,同样的EventArgs对象(你所定义的这些字段和属性 — 译者注)将被传递给该事件的每个订户,无知或恶意的代码接收者可能会修改随后的侦听器所接收的信息。这个CalculationEventArgs类派生于EventArgs,且为计算细节定义了一组字段和只读属性,以提供对字段值的访问。你可以从第8章了解更多关于属性、从第7章了解更多关于字段的信息。

以上表明,事件编程的第一步需要定义一个派生于System.EventArgs类的事件参数类。这个类的名称按约定要以EventArgs结尾。在这个类中,应当为事件的所有参数定义相应的字段和属性 — 译者注

The next step is to define an event in your class. You don’t have to define a delegate for events (although as I showed earlier you certainly can), because you can use the generic EventHandler delegate, which is part of the System namespace. Listing 10-10 demonstrates the definition of an event.
下一步是在你的类(事件源类 — 译者注)中定义一个事件。你不必为事件定义一个委托(虽然我在前面已表明这是可以的),因为你可以使用泛型的EventHandler委托,它位于System命名空间中。清单10-10是一个事件定义。

Listing 10-10. Defining an Event Using the Genetic EventHandler Delegate
清单10-10. 用泛型EventHandler委托定义一个事件

class Calculator {
    public event EventHandler<CalculationEventArgs> CalculationPerformedEvent;
    ...
}

To define the event using the generic EventHandler delegate, you use your custom EventArgs class as the type parameter, as shown in the listing. The convention is that the name of the event should end with the word Event. You can learn more about generic types and generic type parameters in Chapter 15.
为了用泛型EventHandler委托来定义事件,你要以自定义的EventArgs类作为其类型参数,如清单所示。其约定是,事件的名称应当以单词Event结尾。第15章可以了解更多关于泛型类型及泛型类型参数的内容。

请把这个EventHandler理解为事件句柄,它是一种泛型委托类型,是用来定义事件(字段)的。用这个事件句柄定义事件可以将委托的定义和创建事件委托字段这两步工作合并成一步,即,此时不需要再定义事件的委托了。定义这个事件委托的作用是用它来接收外部注入的事件侦听器(事件处理方法) — 译者注

The convention dictates that you put the code to invoke your event delegate in a method whose name starts with On concatenated with the event name, less the word event. So, for example, since we have defined an event called CalculationPerformedEvent, the method would be called OnCalculationPerformed. This method should make a copy of the event to avoid a race condition (race conditions arise in parallel programming and are explained in later in this book) and ensure that the event has subscribers by ensuring that the event field is not null. Listing 10-11 shows the Calculator class updated to use the event pattern fully.
该约定指明,为了调用这个事件委托,你会把代码放到一个方法中,该方法的名称以On开头,后跟去掉单词event的事件名称。举例来说,由于我们定义了一个名称为CalculationPerformedEvent的事件,该方法要叫做OnCalculationPerformed。这个方法将形成该事件的一份拷贝,以避免竞争条件(竞争条件会出现在并行编程中,本书的后面会加以解释),并通过保证事件字段非空的办法来确保该事件已有订户。清单10-11展示了经过修改的Calculator类,以完全使用这种事件模式。

Listing 10-11. Implementing the EventArgs Pattern
清单10-11. 实现EventArgs模式

class Calculator {
    // 定义事件
    public event EventHandler<CalculationEventArgs> CalculationPerformedEvent; 
    public int CalculateProduct(int num1, int num2) {
        // perform the calculation
        // 执行计算
        int result = num1 * num2; 
        // publish the event
        // 发布事件,意即执行事件委托,向外部发出事件通知 — 译者注
        OnCalculationPerformed(new CalculationEventArgs(num1, num2, result)); 
        // return the result
        // 返回结果
        return result;
    }
    // 在发布事件时调用的方法
    private void OnCalculationPerformed(CalculationEventArgs args) {
        // make a copy of the event
        // 形成事件的一份拷贝
        EventHandler<CalculationEventArgs> handler = CalculationPerformedEvent; 
        // check to see we have subscribers
        // 查看事件订户
        if (handler != null) {
            handler(this, args);
        }
    }
}

You can see that the CalculateProduct method creates a new instance of the CalculationEventArgs class and uses it to call the OnCalculationPerformed method, which then copies the event, checks to see that it isn’t null, and invokes it.
可以看出,CalculateProduct方法创建了CalculationEventArgs类的一个新实例,并用它来调用OnCalculationPerformed方法,该方法然后拷贝事件,检查如果不为null,便调用它。

事件编程的第二步是创建事件源类,这需要做三件事:

  1. 用EventHandler定义事件委托。EventHandler的参数是事件编程第一步中创建的事件参数类。该事件委托的名称按约定要以Event结尾。该事件委托的作用是用来接收外部注入的事件处理方法,以形成一个实例化的事件委托对象。
  2. 编写发送事件通知的方法。在这个方法中,通过执行OnXXX方法实现向外发送通知的目的。按约定,OnXXX方法名中的XXX是上一步的事件名称去掉单词Event的部分。送给OnXXX方法的参数是事件参数类的一个实例。
  3. 编写OnXXX方法。在这个方法中需形成事件的一份拷贝,并判断该事件有无订户,若有则执行事件。

以上三步工作如上述清单代码的黑体所示 — 译者注

Subscribing to events is just like using a delegate, with the exception that the subscriber is limited to using the += and -= operators. Listing 10-12 shows a class that uses the events defined in the previous examples.
订阅事件就像使用委托一样,只是把订阅者限制到只能使用 += 和 -= 操作符。清单10-12展示了使用前述示例所定义的事件的一个类。

Listing 10-12. Subscribing to Events
清单10-12. 订阅事件

class Listing_12 {
    static void Main(string[] args) {
        // create a new instance of the Calculator class
        // 创建Calculator类的新实例
        Calculator calc = new Calculator();
        // subscribe to the event in the Calculator class
        // 订阅Calculator类中的事件(将一个外部方法注入到事件源 — 译者注)
        calc.CalculationPerformedEvent += HandleEvent; 
        // perform a calculation
        // 执行计算
        calc.CalculateProduct(20, 72); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
    // 处理事件方法
    // 作为侦听器可以被注入到事件源中去,
    // 并在接收到事件源发出的通知时,对该事件进行处理(响应) — 译者注
    static void HandleEvent(object sender, CalculationEventArgs e) {
        Console.WriteLine("Good Class: {0} x {1} = {2}", e.X, e.Y, e.Result);
    }
}

由上可见,要实现事件的订阅与退订,需要做两件事:

  1. 编写事件处理方法:事件处理方法也叫侦听器。事件处理方法是用来注入到事件源、以接收事件通知、并对通知做出响应的方法。注意,事件处理方法的参数有特殊的要求。
  2. 订阅与退订事件:在应用程序(主程序)中,将事件处理方法注入到事件源对象中(订阅)。注意,只能以 += 或 -= 操作符进行事件的订阅或退订,否则会抛出异常。

事件处理方法的编程,以及事件的订阅与退订如上述清单的黑体所示 — 译者注

You must ensure that the method you are going to use to handle events is not publically accessible; otherwise, you are still liable to encounter problems with other classes, as demonstrated by Listing 10-13.
必须确保打算用来处理事件的方法不是公开可访问的(即,事件处理方法即上述说明中的事件侦听器方法,该方法的访问修饰符应当不是public的 — 译者注),否则,与其它类一起使用时,仍会遇到问题,如清单10-13所示。

Listing 10-13. Removing Another Delegate from an Event
清单10-13. 从一个事件中移去另一个委托

using System; 
class CalculationEventArgs : EventArgs {
    private int x, y, result; 
    public CalculationEventArgs(int num1, int num2, int resultVal) {
        x = num1;
        y = num2;
        result = resultVal;
    }
    public int X {
        get { return x; }
    }
    public int Y {
        get { return y; }
    }
    public int Result {
        get { return result; }
    }
}
class Calculator {
    public event EventHandler<CalculationEventArgs> CalculationPerformedEvent; 
    public int CalculateProduct(int num1, int num2) {
        // perform the calculation
        // 执行计算
        int result = num1 * num2; 
        // publish the event
        // 公布事件
        OnCalculationPerformed(new CalculationEventArgs(num1, num2, result)); 
        // return the result
        // 返回结果
        return result;
    }
    private void OnCalculationPerformed(CalculationEventArgs args) {
        // make a copy of the event
        // 形成事件的一份拷贝
        EventHandler<CalculationEventArgs> handler = CalculationPerformedEvent; 
        // check to see we have subscribers
        // 检查是否有订户
        if (handler != null) {
            handler(this, args);
        }
    }
}
class NefariousClass {
    public NefariousClass(Calculator calc) {
        // add a new listener for Calculator
        // 添加一个新的Calculator侦听器
        calc.CalculationPerformedEvent += HandleNotifyCalculation; 
        // unsubscribe someone else's event handler
        // 退订其他人的事件处理器
        calc.CalculationPerformedEvent -= Listing_13.HandleEvent;
    }
    public void HandleNotifyCalculation(object sender, CalculationEventArgs e) {
        // print out the details of the real calculation
        // 打印出实际计算的细节
        Console.WriteLine("NefariousClass: {0} x {1} = {2}",
                e.X, e.Y, e.Result);
    }
}
class Listing_13 {
    static void Main(string[] args) {
        // create a new instance of the Calculator class
        // 创建Calculator类的新实例
        Calculator calc = new Calculator();
        // subscribe to the event in the calaculator class
        // 订阅Calculator类中的事件
        calc.CalculationPerformedEvent += HandleEvent; 
        // create an instance of NefariousClass
        // 创建NefariousClass实例
        NefariousClass nef = new NefariousClass(calc); 
        // perform a calculation
        // 执行计算
        calc.CalculateProduct(20, 72); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
    public static void HandleEvent(object sender, CalculationEventArgs e) {
        Console.WriteLine("Good Class: {0} x {1} = {2}", e.X, e.Y, e.Result);
    }
}

The NefariousClass constructor in the example adds a new listener to the Calculator event, but it also removes the listener added by the Listing_13 class. It can do this because the Listing_13.HandleEvent method is public and therefore can be accessed outside of its containing class. Changing the access modifier on the HandleEvent class to a more restrictive setting would prevent this from happening.
上例中NefariousClass的构造器把一个新的侦听器加到Calculator事件,但它也移去了Listing_13类加入的侦听器。这确实是能够做到的,因为Listing_13.HandleEvent方法是public的,因而可以在它的容器类的外部访问它。把HandleEvent类的访问修饰符改为更严格的设置将可以阻止这一情况的发生。

Creating Nongeneric Events
创建非泛型事件

Another approach to events is to use the nongeneric version of EventHandler. This is more like using a delegate in that you have to define the event/delegate type. This approach predates the introduction of generic types in C#, but I have included it because it is still widely used. Listing 10-14 shows the Calculator example implemented without generic support.
事件的另一种办法是使用EventHandler的非泛型版本(这是事件的另一种编程模式 — 译者注)。这更像以事件/委托类型的方式来使用委托。这种办法在C#中要早于泛型类型的引入,我在这里包含此内容是因为它仍有广泛的运用。清单10-14演示了不用泛型支持的Calculator示例实现。

Listing 10-14. Implementing Events Without Generic Types
清单10-14. 不用泛型类型实现事件

using System; 
delegate void CalculationPerformedEventHandler(object sender, CalculationEventArgs args); 
class CalculationEventArgs : EventArgs {
    private int x, y, result; 
    public CalculationEventArgs(int num1, int num2, int resultVal) {
        x = num1;
        y = num2;
        result = resultVal;
    }
    public int X {
        get { return x; }
    }
    public int Y {
        get { return y; }
    }
    public int Result {
        get { return result; }
    }
}
class Calculator {
    public event CalculationPerformedEventHandler CalculationPerformedEvent; 
    public int CalculateProduct(int num1, int num2) {
        // perform the calculation
        // 执行计算
        int result = num1 * num2; 
        // publish the event
        // 公布事件
        OnCalculationPerformed(new CalculationEventArgs(num1, num2, result)); 
        // return the result
        // 返回结果
        return result;
    }
    private void OnCalculationPerformed(CalculationEventArgs args) {
        // make a copy of the event
        // 形成事件拷贝
        CalculationPerformedEventHandler handler = CalculationPerformedEvent; 
        // check to see we have subscribers
        // 检查是否已有订户
        if (handler != null) {
            handler(this, args);
        }
    }
}
class Listing_14 {
    static void Main(string[] args) {
        // create a new instance of the Calculator class
        // 创建Calculator类的新实例
        Calculator calc = new Calculator();
        // subscribe to the event in the calaculator class
        // 订阅Calculator类中的事件
        calc.CalculationPerformedEvent += HandleEvent; 
        // perform a calculation
        // 执行计算
        calc.CalculateProduct(20, 72); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
    static void HandleEvent(object sender, CalculationEventArgs e) {
        Console.WriteLine("Good Class: {0} x {1} = {2}", e.X, e.Y, e.Result);
    }
}

There isn’t much to say about this example; it is very similar to the generic event listings but has an additional delegate definition.
这个例子无需多说,除了有一个附加的委托定义之外,它与前述泛型事件完全类似。

Creating Events Without Custom Data
创建无自定义数据的事件

If you don’t need to pass custom data as part of your event, then you can use EventArgs directly, as shown in Listing 10-15.
如果不需要把自定义数据作为事件的一部分进行传递,那么可以直接使用EventArgs,如果清单10-15所示。

Listing 10-15. Creating and Using Events with No Custom Data
清单10-15. 创建和使用无自定义数据的事件

using System; 
class Calculator {
    public event EventHandler CalculationPerformedEvent;
    public int CalculateProduct(int num1, int num2) {
        // perform the calculation
        // 执行计算
        int result = num1 * num2; 
        // publish the event
        // 公布事件
        OnCalculationPerformed();
        // return the result
        // 返回结果
        return result;
    }
    private void OnCalculationPerformed() {
        // make a copy of the event
        // 形成事件拷贝
        EventHandler handler = CalculationPerformedEvent; 
        // check to see we have subscribers
        // 检查是否已有订户
        if (handler != null) {
            handler(this, EventArgs.Empty); 
        }
    }
}
class Listing_15 {
    static void Main(string[] args) {
        // create a new instance of the Calculator class
        // 创建Calculator类新实例
        Calculator calc = new Calculator();
        // subscribe to the event in the calaculator class
        // 订阅Calculator类中的事件
        calc.CalculationPerformedEvent += HandleEvent; 
        // perform a calculation
        // 执行计算
        calc.CalculateProduct(20, 72); 
        // wait for input before exiting
        // 退出之前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
    static void HandleEvent(object sender, EventArgs e) {
        Console.WriteLine("Event Received");
    }
}

There is no need to define a custom delegate if you don’t need custom data. When defining the event, you simply use the EventHandler type, as follows:
如果不需要自定义数据,没必要定义一个自定义委托。当定义事件时,你只要简单地使用EventHandler类型,如下所示:

public event EventHandler CalculationPerformedEvent;

The event still requires two arguments to invoke it, but you can use the static EventArgs.Empty property to get a reference to a ready-made EventArgs instance that has no custom data. You can see this in the OnCalculationPerformed method of the Calculator class, which has been updated to remove the method parameters.
该事件仍然需要两个参数去调用它,但你可以使用静态的EventArgs.Empty属性,以获得对已形成的无自定义数据的EventArgs实例的引用。你可以在Calculator类的OnCalculationPerformed方法中看到这种情况,已对其作了修改,去除了方法参数。

由上可见,创建无自定义数据的事件,不需要创建事件参数类 — 译者注

Applying Modifiers to Events
对事件运用修饰符

You can control access to your events using the public, protected, internal, and private keywords. See Chapter 7 for details of these keywords and the effect they have on fields.
你可以用public、protected、internal和private关键字来控制对事件的访问。参阅第7章关于这些关键字的细节,以及它们运用于字段所具有的效应。

You should not use the virtual modifier on events. If you do, it is possible that your events will not be delivered properly. If you want to override an event from a base class, then you should mark the OnXXX method as virtual and override the method in the derived class. Listing 10-16 provides a demonstration. You can find more details and examples of overriding methods in Chapter 9.
你不应该在事件上使用virtual修饰符。如果这么做,你的事件可能不会被适当地投递。如果你想重写基类的事件,那么你应当将OnXXX方法标记为virtual,并在派生类中重写该方法。清单101-6提供了一个演示。在第9章中你会看到重写方法的更多细节和示例。

Listing 10-16. Deriving Event Implementations
清单10-16. 派生事件实现

// 基类
class Calculator {
    public event EventHandler<CalculationEventArgs> CalculationPerformedEvent; 
    public int CalculateProduct(int num1, int num2) {
        // perform the calculation
        // 执行计算
        int result = num1 * num2; 
        // publish the event
        // 公布事件
        OnCalculationPerformed(new CalculationEventArgs(num1, num2, result)); 
        // return the result
        // 返回结果
        return result;
    }
    protected virtual void OnCalculationPerformed(CalculationEventArgs args) {
        // make a copy of the event
        // 形成事件拷贝
        EventHandler<CalculationEventArgs> handler = CalculationPerformedEvent; 
        // check to see we have subscribers
        // 确认已有订户
        if (handler != null) {
            handler(this, args);
        }
    }
}
// 派生类
class DerivedCalc : Calculator {
    protected override void OnCalculationPerformed(CalculationEventArgs args) {
        // perform custom logic here
        // 以下执行自定义逻辑
        // call the base method
        // 调用基方法
        base.OnCalculationPerformed(args);
    }
}

In this example, the DerivedCalc class overrides the OnCalculationPerformed method, which has been marked as virtual in the base Calculator class. The overridden method can perform modifications to the custom EventArgs implementation (or perform any other required task) before calling the base OnCalculationPerformed method to publish the event.
在这个例子中,DerivedCalc类重写了OnCalculationPerformed方法,它在基Calculator类中已被标记为virtual了。重写的方法可以在调用基OnCalculationPerformed方法来公布事件之前,对自定义的EventArgs实现进行修改(或执行任何其它所需的任务)。

Using Action and Func Delegates
使用Action和Func委托

The Func and Action classes are special types that allow you to use delegates without having to specify a custom delegate type. They are used throughout the .NET Framework. For example, you will see examples of both when we look at parallel programming.
Func和Action类是特殊的类型,它们允许你在不必指定自定义委托类型的情况下,去使用委托。在整个.NET框架中都可以使用它们。例如,在我们考察并行计算时,你也会看到这两个类的示例。

Func和Action是.NET的两个特殊类型,在整个.NET平台中都可以使用它们。这两个类型实际上是委托类型,可以用来创建委托对象。因此,由于它们是类型,我们便可以创建Func和Action类型的变量;又由于它们可以用来创建委托对象,于是直接用方法名对这些变量进行赋值,便可以形成实例化的委托对象,而不必进行委托定义 — 译者注

Using Action Delegates
使用Action委托

Action delegates encapsulate methods that do not return results. In other words, they can be used only with methods that are defined with the void keyword. The simplest Action delegate is the System.Action class, which is used for methods that have no parameters (and no results). Listing 10-17 contains an example.
Action委托封装不返回结果的方法(Action是可以用来委托无返回类型的方法,意即,可以用无返回类型的方法名对Action类型的变量进行赋值 — 译者注)。换句话说,这种委托只能用于以void关键字定义的那些方法。最简单的void委托是System.Action类,它用于无参数(且无结果)的方法。清单10-17含有一个例子。

Listing 10-17. Using the Basic Action Delegate
清单10-17. 使用基本的Action委托

using System; 
class Calculator {
    public void CalculateProduct() {
        // perform the calculation
        // 执行计算
        int result = 10 * 20; 
        // print out a message with the result
        // 用结果印出消息
        Console.WriteLine("Result: {0}", result);
    }
}
class Listing_17 {
    static void Main(string[] args) {
        // create a new instance of Calculator
        // 创建Calculator新实例
        Calculator calc = new Calculator();
        // create an action and assign a method
        // 创建一个Action并赋予一个方法
        // 以下语句创建了一个Action类型的变量,并用一个方法名对其实例化,形成了一个委托对象 — 译者注
        Action act = calc.CalculateProduct; 
        // invoke the method via the Action
        // 通过这个Action调用该方法
        act();
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The key statement in Listing 10-17 is shown in bold; it creates a new local Action variable and assigns the CalculateProduct method from an instance of Calculator as the value. The Action is then invoked, just as a regular delegate would be. Compiling and running the code in Listing 10-17 products the following results:
清单10-17中的关键语句以黑体显示,它创建了一个新的Action局部变量,并把Calculator实例的CalculateProduct方法作为值赋给了它。然后这个Action被调用,就像规则的委托那样。编译并运行清单10-17会产生以下结果:

Result: 200
Press enter to finish

We didn’t have to define a custom delegate in this example. The System.Action type handled everything for us. There are 17 different Action implementations available. Starting with the one used in Listing 10-17, each adds a new generic parameter. This is not as confusing as it may sound; you just create the generic implementation that matches the number of parameters the target method required. Listing 10-18 contains an example that uses two parameters.
在这个例子中,我们不必定义一个自定义委托(即,不必进行委托定义 — 译者注)。System.Action类型为我们处理所有事情。有17个不同的Action实现可用。从清单10-17所用的一个开始,每一个都添加了一个新的泛型参数。实际情况并不像听上去这样让人困扰,你只要创建与目标方法所需要的参数数目匹配的泛型实现。清单1-18包含了一个使用两个参数的示例。

Listing 10-18. Using a Generic Action Delegate
清单10-18. 使用泛型的Action委托

using System; 
class Calculator {
    public void CalculateProduct(int x, int y) {
        // perform the calculation
        // 执行计算
        int result = x * y; 
        // print out a message with the result
        // 用result打印出消息
        Console.WriteLine("Result: {0}", result);
    }
}
class Listing_18 {
    static void Main(string[] args) {
        // create a new instance of Calculator
        // 创建Calculator新实例
        Calculator calc = new Calculator();
        // create an action and assign a method
        // 创建一个action,并赋予一个方法
        Action<int, int> act = calc.CalculateProduct; 
        // invoke the method via the Action
        // 通过Action调用该方法
        act(10, 20);
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

In this example, the method I want to delegate has two int parameters, so I used the Action<int, int> delegate (the parameter types for an Action need not all be the same). If I had wanted to delegate a method with five parameters, then I would have used the Action<T1, T2, T3, T4, T5> type and filled in the type parameters of the Action type to match the parameter types of the delegated method.
在这个例子中,我想委托的方法有两个int参数,因此,我使用了Action<int, int>委托(Action的参数类型并不需要是相同的)。如果我想委托一个具有五个参数的方法,那么,我会使用Action<T1, T2, T3, T4, T5>类型,并填充这个Action类型的类型参数,以匹配被委托方法的参数类型。

Using Func Delegates
使用Func委托

System.Func delegates are just like Action delegates, except that they can return results. The simplest Func implementation has no parameters. Listing 10-19 contains an example; you can see the similarities to the Action examples.
System.Func委托除了可以返回结果以外,它与Action委托完全相同。最简单的Func实现没有参数。清单10-19包含一个示例,你可以看出它与Action示例的类似性。

Listing 10-19. A Simple Func Example
清单10-19. 一个简单的Func示例

using System; 
class Calculator {
    public int CalculateProduct() {
        // perform the calculation
        // 执行计算
        return 10 * 20;
    }
}
class Listing_19 {
    static void Main(string[] args) {
        // create a new instance of Calculator
        // 创建Calculator新实例
        Calculator calc = new Calculator();
        // create a Func and assign a method
        // 创建一个Func,并赋予一个方法
        Func<int> act = calc.CalculateProduct; 
        // invoke the method via the Action(应当是Func)
        // 通过Func调用方法
        int result = act();
        // print out the result
        // 印出结果
        Console.WriteLine("Result: {0}", result);
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The last generic type for System.Func is the result type for the delegate. So, in Listing 10-19, the Func<int> I used had no parameters but returned an int. Just like Action, there are 17 Func implementations with an increasing number of parameters, each of which can be of a different type. Listing 10-20 demonstrates using Func with two int parameters.
System.Func的最后一个泛型类型是委托的结果类型。因此,在清单10-19中,我所使用的Func<int>没有参数,但返回int。就像Action一样,也有17个带有不同参数数目的Func实现,每个参数都可以是不同的类型。清单10-20演示了使用两个int参数的Func。

Listing 10-20. Using a Func with Parameters
清单10-20. 使用带有参数的Func

using System; 
class Calculator {
    public int CalculateProduct(int x, int y) {
        // perform the calculation
        // 执行计算
        return x * y;
    }
}
class Listing_20 {
    static void Main(string[] args) {
        // create a new instance of Calculator
        // 创建Calculation新实例
        Calculator calc = new Calculator();
        // create a Func and assign a method
        // 创建Func并赋予一个方法
        Func<int, int, int> act = calc.CalculateProduct;
        // invoke the method via the Action Func
        // 通过这个Func调用该方法
        int result = act(10, 20);
        // print out the result
        // 印出结果
        Console.WriteLine("Result: {0}", result); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

感谢微软工程师,为我们提供了这么好用的Action和Func。有了它们,我们使用委托简直太方便了 — 译者注

Anonymous Methods
匿名方法

All of the examples so far in this chapter have used named methods, that is, methods that exist in classes and have a method identifier. C# also supports anonymous methods, which allow you to implement a delegate without defining a method. Listing 10-21 contains an anonymous method.
本章目前为止所演示的示例都使用了命名的方法,即,在类中存在且有方法修饰符的方法。C#也支持匿名方法,它让你能够不必定义方法就可以实现委托。清单10-21含有一个匿名方法。

Listing 10-21. An Anonymous Method
清单10-21. 匿名方法

using System; 
class Listing_21 {
    static void Main(string[] args) {
        // create a new Calculator
        // 创建一个新的Calculator
        Calculator calc = new Calculator();
        // create a delegate with an anonymous method
        // 用匿名方法创建一个委托
        calc.CalculationPerformedEvent += delegate(object sender, CalculationEventArgs e) {
            Console.WriteLine("Anonymous Calc: {0} x {1} = {2}", e.X, e.Y, e.Result);
        };
        // perform a calculation
        // 执行计算
        calc.CalculateProduct(20, 40); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The class in Listing 10-21 works with the Calculator and CalculationEventArgs classes defining in Listing 10-9. I have omitted them from this listing for brevity. You can see the anonymous method in bold; it is also illustrated in Figure 10-2.
清单10-21中的类使用了清单10-9中定义的Calculator和CalculationEventArgs类。出于简化,我忽略了它们。你可以看到以黑体表示的匿名方法,也如图10-2所示。

IntrC10-2图中:Delegate Keyword: 委托关键字;Parameters:参数;Code Statements:代码语句

Figure 10-2. The anatomy of an anonymous method
图10-2. 一个匿名方法的剖析

An anonymous method has a number of similarities to a named method. You can see the parameters and code block in Figure 10-2 are just like those you would get in a regular method. There is no method identifier (because there is no method name), and you must use the delegate keyword when defining an anonymous method.
匿名方法与命名方法有许多相似性。你可以看出,图10-2中的参数和代码块与你要在规则方法中得到的一样。没有方法修饰符(因为没有方法名),而且,在定义匿名方法时,你必须使用delegate关键字。

Using an anonymous method as a delegate is just like using a named method, as you can see from Listing 10-21. I used the += operator to add the anonymous method to the event in the Calculator class.
将匿名方法用作为委托与使用命名方法一样,正如你从清单10-21所看到的,我使用了+=操作符把匿名方法加到了Calculator类中的事件。

When the event invokes the delegate, the statements in the anonymous method body are executed, just as would happen for a regular method.
当事件调用委托时,匿名方法体中的语句被执行,就像规则方法所发生的情况一样。

If we compile and run the code in Listing 10-21, we get the following results:
如果编译并运行清单10-21代码,会得到以下结果:

Anonymous Calc: 20 x 40 = 800
Press enter to finish

Anonymous methods work very well with Func and Action delegates, allowing you to define a delegate without needing to create a delegate type or implement a named method. When implementing a delegate that returns a result, simply use the return keyword as you would for a named method, as demonstrated by Listing 10-22.
匿名方法可以与Func和Action委托很好地一起使用,这允许你定义一个委托,而不需要创建委托类型或实现命名方法。当实现返回一个结果的委托时,简单地使用return关键字,就像使用命名方法一样,如清单10-22所示。

Listing 10-22. An Anonymous Method That Returns a Result
清单10-22. 返回一个结果的匿名方法

using System; 
class Listing_22 {
    static void Main(string[] args) {
        // define a func and implement it with an anonymous method
        // 定义一个func,并用一个匿名方法实现它
        Func<int, int, int> productFunction = delegate(int x, int y) {
            return x * y;
        };
        // invoke the func and get a result
        // 调用这个func,并得到一个结果
        int result = productFunction(10, 20); 
        // print out the result
        // 印出结果
        Console.WriteLine("Result: {0}", result); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The bold statement in Listing 10-22 defines a Func that returns an int result and that has two int parameters. The implementation of this delegate is provided by an anonymous method that returns the product of the two parameters values. You invoke the delegate in the normal way, as though it were a method. Compiling and running the code in Listing 10-22 produces the following results:
清单10-22中的黑体语句定义了一个返回int结果且有两个int参数的Func。这个委托的实现是由一个匿名方法实现的,它返回两个参数值的积。用常规的方式调用这个委托,就像它是一个方法一样。编译并运行清单10-22代码产生以下结果:

Result: 200
Press enter to finish

Capturing Outer Variables
捕捉外部变量

Anonymous methods do more than reduce the number of methods in your class; they are also able to access the local variables that are defined in the containing methods, known as outer variables. Listing 10-23 provides a demonstration.
匿名方法的作用还不止能减少类中的方法数,它们也能访问容纳方法(指容纳该匿名方法的容器 — 译者注)中定义的局部变量,这称为外部变量(outer variables)。清单10-23提供了一个演示。

Listing 10-23. Accessing Outer Variables
清单10-23. 访问外部变量

using System; 
class Calculator {
    Func<int, int, int> calcFunction; 
    public Calculator(Func<int, int, int> function) {
        calcFunction = function;
    }
    public int PerformCalculation(int x, int y) {
        return calcFunction(x, y);
    }
}
class Listing_23 {
    static void Main(string[] args) {
        // define a local variable
        // 定义一个局部变量
        int calculationCount = 0;
        // define and implement a Func
        // 定义并实现一个Func
        Func<int, int, int> productFunc = delegate(int x, int y) {
            // increment the outer variables
            // 递增外部变量
            calculationCount++;
            // calculate and return the result
        // 计算并返回结果
            return x * y;
        };
        // create a new instance of Calculator
        // 创建Calculator新实例
        Calculator calc = new Calculator(productFunc);
        // perform several calculations
        // 执行一些计算
        for (int i = 0; i < 5; i++) {
            Console.WriteLine("Result {0} = {1}", i, calc.PerformCalculation(i, i));
        }
        // print out the value of the outer variable
        // 印出外部变量的值
        Console.WriteLine("calculationCount: {0}", calculationCount); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The Calculator class in this example takes a Func<int, int, int> as a constructor parameter and invokes the delegate when the PerformCalculation method is called. The Listing_23 class defines a matching Func using an anonymous method. This is passed to the constructor of the Calculator instance.
这个例子中的Calculator类以Func<int, int, int>作为构造器参数,并在调用PerformCalculation方法时调用该委托。Listing_23类用一个匿名方法定义了一个匹配的Func。它被传递给Calculator实例的构造器。

The anonymous method increments the calculationCount variable each time that it is invoked, even though the variable is defined outside of the anonymous method and even though the Func is invoked by an entirely different method in an entirely different object. Compiling and running the code in Listing 10-23 produces the following result:
匿名方法每次被调用时都递增了calculationCount变量,即使这个变量是在匿名方法的外部定义的,甚至Func也是由完全不同的对象中完全不同的方法来调用的。编译并运行清单10-23代码产生以下结果:

Result 0 = 0
Result 1 = 1
Result 2 = 4
Result 3 = 9
Result 4 = 16
calculationCount: 5
Press enter to finish

Variables that are accessed outside the anonymous method are called captured variables. The calculationCount variable in Listing 10-23 was captured by the anonymous method. An anonymous method that captures variables is called a closure.
匿名方法外部被访问的变量称为被捕捉captured)变量(因此,匿名方法体外部的那些变量称为外部变量,在匿名方法体中所访问的那个外部变量称为被捕捉变量 — 译者注)。清单10-23中的calculationCount变量是由匿名方法来捕捉的。一个要捕捉变量的匿名方法称为是一个闭包closure)。

Captured variables are evaluated when the delegate is invoked, which means that you should take care when making assumptions about the value of a variable. Listing 10-24 provides a demonstration.
在委托被调用时,会求取被捕捉变量的值,这意味着,在假设一个变量的值时要小心。清单10-24提供了一个演示。

Listing 10-24. Evaluating Captured Variables
清单10-24. 求取被捕捉变量的值

using System; 
class Listing_24 {
    static void Main(string[] args) {
        // define a variable that will be captured
        // 定义被捕捉变量
        string message = "Hello World";
        // define an anonymous method that will capture
        // the local variables
        // 定义一个将捕捉这个局部变量的匿名方法
        Action printMessage = delegate() {
            Console.WriteLine("Message: {0}", message);
        };
        // modify one of the local vaiables
        // 修改局部变量
        message = "Howdy!"; 
        // invoke the delegate
        // 调用委托
        printMessage();
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

When the anonymous method is defined, the value of the message variable is Hello World. But defining an anonymous method doesn’t capture the variables it references—that doesn’t happen until the delegate is invoked, by which time the value of the message variable has changed. Compiling and running the code in Listing 10-24 produces the following result:
在定义匿名方法时,message变量的值是Hello World。但在定义匿名方法时并不捕捉它所引用的变量 — 直到委托被调用时才会发生,此时message变量的值已经变化了。编译并运行清单10-24代码产生以下结果:

Message: Howdy!
Press enter to finish

Lambda Expressions
Lambda表达式

Lambda expressions have largely replaced anonymous methods since they were introduced in C# 3.0. They have much the same functionality as an anonymous method but are slightly more convenient to use. Listing 10-25 contains an anonymous method and an equivalent lambda expression.
Lambda表达式已经广泛代替了匿名方法,因为它是从C# 3.0开始引入的。Lambda表达式与匿名方法有很多同样的功能,但更便于使用。清单10-25包含了一个匿名方法和一个等效的Lambda表达式。

Listing 10-25. Comparing an Anonymous Method with a Lambda Expression
清单10-25. 匿名方法与Lambda表达式比较

using System; 
class Listing_25 {
    static void Main(string[] args) {
        // implement an anonymous method that multiplies ints
        // 实现一个int数相乘的匿名方法
        Func<int, int, int> anonFunc = delegate(int x, int y) {
            return x * y;
        };
        // do the same thing with a lambda expression
        // lambda表达式做同样的事情
        Func<int, int, int> lambaFunc = (x, y) => {
            return x * y;
        };
        // invoke the delegates
        // 调用委托
        Console.WriteLine("Anonymous Method Result: {0}", anonFunc(10, 10));
        Console.WriteLine("Lambda Expression Result: {0}", lambaFunc(10, 10)); 
        // wait for input before exiting
        // 退出前等待输入
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

The lambda expression in Listing 10-25 is shown in bold. There are only three parts to a lambda expression, and they are illustrated in Figure 10-3.
清单10-25中的lambda表达式以黑体显示。一个lambda表达式只有三个部分,它们如图10-3所示。

IntrC10-3图中:Parameters:参数,Lambda Operator:Lambda操作符,Code Statements:代码语句

Figure 10-3. The anatomy of a lambda expression
图10-3. Lambda表达式剖析

Although the format of a lambda expression looks a little odd, the basic premise is the same as for anonymous methods. Parameters in a lambda expression are specified without their types—the types are inferred.
虽然lambda表达式看上去有点古怪,但基本前提与匿名方法是相同的。Lambda表达式的参数不指定类型 — 其类型通过推断。

The code statements are just like for a method, named or anonymous. You access the parameters by the names they have been specified by. In the case of Listing 10-25, the parameters x and y are multiplied together, and the result is returned.
其代码语句与命名或匿名方法同样。通过被指定的参数名称来访问参数。在清单10-25的示例中,参数x和y相乘,并返回结果。

You can omit the braces and the return keyword if there is a single operation in a lambda expression so that the lambda expression in Listing 10-25 can also be written as follows:
如果在lambda表达式中只有一个单一的操作,你可以忽略花括号和return关键字,因此,清单10-25的lambda表达式也可以写成如下形式:

(x, y) => x * y;

The lambda operator (=>) is, as you might expect, required for lambda operators. It is often described as the goes to operator. For the expression in Listing 10-25, we can say that the parameters x and y go to the product of x and y.
lambda操作符(=>),正如你可能想到的,是必需的。通常把它说成是“进入go to)”操作符(感觉把 => 操作符说成“送入”更恰当些,意即将参数送入方法体 — 译者注)。对于清单10-25中的表达式,我们可以说成,x和y参数进入x和y的乘积。

Compiling and running the code in Listing 10-25 produces the following results:
编译并运行清单10-25代码产生以下结果:

Anonymous Method Result: 100
Lambda Expression Result: 100
Press enter to finish

The C# compiler is pretty good at inferring the types of parameters for lambda expressions, but on occasion the compiler won’t be able to work it out, and you will you need to explicitly tell the compiler what they are. Here is an example:
C#编译器十分擅长推断lambda表达式的参数类型,但偶尔也会有编译器不能推断的情况,此时你需要明确地告诉编译器,它们是什么。以下是一个示例:

(int x, int y) => x * y;

I have used lambda expressions extensively in software projects and for other C# books, and I have found only a small number of instances where the compiler couldn’t figure things out implicitly, but when you do encounter one of those situations (or if you just prefer explicit typing), then it is good to know how to do it.
我在软件项目以及其它C#书籍中广泛使用了lambda表达式。而且我发现,只有很少情况下才会出现编译器不能推断的情况。当你遇到这种情况时(或者你更喜欢明确指定类型),那么,最好知道如何做这种事。

Summary
小结

In this chapter, you have how delegates can be used to encapsulate references to static and instance methods and passed around like regular types. You have seen the limitations of delegates when they are shared between objects and how events address this shortcoming.
在本章中,你知道了如何把委托用于封装对静态和实例化方法的引用,并像规则类型一样进行传递。你也看到了,对象之间共享委托所具有的局限性,以及事件是如何解决这一缺陷的。

The convention for using events is widely adopted, and I recommend that you follow it in your own code. Not only does it make your code easier for other programmers to use, but it also helps you create events that can be overridden reliably in derived classes.
使用事件的约定被广泛采纳,而且我建议你在代码中遵循它。不仅它使你的代码更易于被其他程序员使用,而且也有助于你创建能够在派生类中可靠地进行重写的事件。

We looked briefly at the System.Func and System.Action types, both of which allow us to work with delegates without creating custom delegate types, and we looked at anonymous methods and lambda expressions, which allow us to implement delegates without having to define methods. We learned how anonymous methods and lambda expressions capture local variables and when those variables are evaluated. We’ll see a lot more of Func, Action, and lambda expressions when we explore some of the advanced C# language features such as LINQ and parallel programming.
我们简要地考查了System.Func和System.Action类型,它们都能让我们不必创建自定义委托类型就可以使用委托。我们也考查了匿名方法和lambda表达式,它们让我们能够不必定义方法就可以实现委托。我们了解了匿名方法和lambda表达式如何捕捉局部变量,以及评估这些变量的时间。在我们考察C#的高级语言特性,如LINQ和并行编程时,会看到更多Func、Action和lambda表达式。

这是一篇有技术含量、甚至可以说是有学术价值的文章!请大家多给推荐

将ASP.NET Core应用程序部署至生产环境中(CentOS7)

这段时间在使用Rabbit RPC重构公司的一套系统(微信相关),而最近相关检验(逻辑测试、压力测试)已经完成,接近部署至线上生产环境从而捣鼓了ASP.NET Core应用程序在CentOS上的部署方案,今天就跟大家分享一下如何将ASP.NET Core应用程序以生产的标准部署在CentOS上。

环境说明

服务器系统:CentOS 7.2.1511

相关工具:Xshel、Xftp

服务器软件软件:.netcore、nginx、supervisor、policycoreutils-python

准备你的ASP.NET Core应用程序

首先将你的应用程序以便携的模式进行发布。

ps:这边我使用一个空的Web项目来进行演示,因为本篇主要介绍生产环境的部署,与应用无关。

命令为:dotnet publish –c release

具体的可以看:拥抱.NET Core,如何开发跨平台的应用并部署至Ubuntu运行,这篇博文介绍了以便携与自宿主方式发布web应用。

image

确保这份发布应用可以在windows上运行,以减少后续的问题。

image

为什么不用自宿主的方式进行部署?

自宿主的发布方式进行部署会简单很多,为什么生产环境要使用便携的方式进行发布呢?

原因1:性能比便携式的低(主)。

原因2:微软给出的建议(次)。

口说无凭,有图有真相。

image

image

参考地址:https://docs.microsoft.com/zh-cn/dotnet/articles/core/app-types

so,既然是用于生产环境的,当然我们要追求更高的性能。

安装CentOS7

这个就不细说了,网上教程很多,这边我使用了Hyper-V来虚拟化了CentOS7。

安装.NET Core SDK for CentOS7。

sudo yum install libunwind libicu(安装libicu依赖)

image

curl -sSL -o dotnet.tar.gz https://go.microsoft.com/fwlink/?LinkID=809131(下载sdk压缩包)

sudo mkdir -p /opt/dotnet && sudo tar zxf dotnet.tar.gz -C /opt/dotnet(解压缩)

sudo ln -s /opt/dotnet/dotnet /usr/local/bin(创建链接)

image

输入 dotnet –info 来查看是否安装成功

image

如果可以执行则表明.NET Core SDK安装成功。

参考资料:https://www.microsoft.com/net/core#centos

部署ASP.NET Core应用程序

上传之前发布的文件夹至/home/wwwroot/。

这边我使用了Xftp进行文件的上传。

image

image

检查是否能够运行

命令:dotnet /home/wwwroot/WebApplication1/WebApplication1.dll

image

如果出现这些信息则表示成功运行。

这时候我们是无法访问到这个页面的,这时候我们需要部署一个web容器来进行转发。

配置Nginx

安装Nginx

curl -o  nginx.rpm http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

image

rpm -ivh nginx.rpm

yum install nginx

image

安装成功!

输入:systemctl start nginx 来启动nginx。

输入:systemctl enable nginx 来设置nginx的开机启动(linux宕机、重启会自动运行nginx不需要连上去输入命令)。

配置防火墙

命令:firewall-cmd –zone=public –add-port=80/tcp –permanent(开放80端口)

命令:systemctl restart firewalld(重启防火墙以使配置即时生效)

测试nginx是否可以访问。

image

配置nginx对ASP.NET Core应用的转发

修改 /etc/nginx/conf.d/default.conf 文件。

将文件内容替换为

server {
listen 80;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

上传至CentOS进行覆盖。

执行:nginx –s reload 使其即时生效

运行ASP.NET Core应用程序

image

命令:dotnet /home/wwwroot/WebApplication1/WebApplication1.dll

这时候再次尝试访问。

image

想哭的心都有。。。经过后续了解,这个问题是由于SELinux保护机制所导致,我们需要将nginx添加至SELinux的白名单。

接下来我们通过一些命令解决这个问题。。

yum install policycoreutils-python

sudo cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M mynginx

sudo semodule -i mynginx.pp

image

再次尝试访问。

image

至此基本完成了部署。

配置守护服务(Supervisor)

目前存在三个问题

问题1:ASP.NET Core应用程序运行在shell之中,如果关闭shell则会发现ASP.NET Core应用被关闭,从而导致应用无法访问,这种情况当然是我们不想遇到的,而且生产环境对这种情况是零容忍的。

问题2:如果ASP.NET Core进程意外终止那么需要人为连进shell进行再次启动,往往这种操作都不够及时。

问题3:如果服务器宕机或需要重启我们则还是需要连入shell进行启动。

为了解决这个问题,我们需要有一个程序来监听ASP.NET Core 应用程序的状况。在应用程序停止运行的时候立即重新启动。这边我们用到了Supervisor这个工具,Supervisor使用Python开发的。

安装Supervisor

yum install python-setuptools

easy_install supervisor

配置Supervisor

mkdir /etc/supervisor

echo_supervisord_conf > /etc/supervisor/supervisord.conf

修改supervisord.conf文件,将文件尾部的配置

image

修改为

image

ps:如果服务已启动,修改配置文件可用“supervisorctl reload”命令来使其生效

配置对ASP.NET Core应用的守护

创建一个 WebApplication1.conf文件,内容大致如下

[program:WebApplication1]
command=dotnet WebApplication1.dll ; 运行程序的命令
directory=/home/wwwroot/WebApplication1/ ; 命令执行的目录
autorestart=true ; 程序意外退出是否自动重启
stderr_logfile=/var/log/WebApplication1.err.log ; 错误日志文件
stdout_logfile=/var/log/WebApplication1.out.log ; 输出日志文件
environment=ASPNETCORE_ENVIRONMENT=Production ; 进程环境变量
user=root ; 进程执行的用户身份
stopsignal=INT

将文件拷贝至:“/etc/supervisor/conf.d/WebApplication1.conf”下

运行supervisord,查看是否生效

supervisord -c /etc/supervisor/supervisord.conf

ps -ef | grep WebApplication1

image

如果存在dotnet WebApplication1.dll 进程则代表运行成功,这时候在使用浏览器进行访问。

image

至此关于ASP.NET Core应用程序的守护即配置完成。

配置Supervisor开机启动

新建一个“supervisord.service”文件

# dservice for systemd (CentOS 7.0+)
# by ET-CS (https://github.com/ET-CS)
[Unit]
Description=Supervisor daemon

[Service]
Type=forking
ExecStart=/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
ExecStop=/usr/bin/supervisorctl shutdown
ExecReload=/usr/bin/supervisorctl reload
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

将文件拷贝至:“/usr/lib/systemd/system/supervisord.service”

执行命令:systemctl enable supervisord

image

执行命令:systemctl is-enabled supervisord #来验证是否为开机启动

image

测试

GIF

 


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,转载文章之后须在文章页面明显位置给出作者和原文连接,谢谢。

.Net Core[译文]

文章概览:

什么是.Net Core平台?

.Net Core是一个模块化的,跨平台的,开源实现的.Net Framework,它可以在Windows设备上运行,也可以在Linux和OS X上运行。不同于传统的.Net Framework(特点:庞大的,系统级的,是一个只能跑在Windows上的运行时环境),你可以用.Net Core创造以多平台为目标的组件化的库和应用程序,并且.Net Core可以与应用程序一起部署。

.Net Core特性

让我们从宏观上看看.Net Core的特性

跨平台支持

.NET Core 应用程序可以同时跑在32位和64位的Windows平台上,也可以跑在OS X和Linux上。

相反的,基于传统.Net Framework编写的应用程序只能跑在Windows上。注意的是UWP通用应用程序借助.Net Core的实现,也只能跑在Windows桌面程序,平板和Windows phone手机上。

开放源代码

.Net Core的RunTime(CoreCLR)和基础类库都是开放源代码的,另外.Net Core的源代码都是免费获得的,这些意味着:

  • 设计注释,功能规格,还有特定实现文档都是公开的
  • .Net Core的代码评审(Code review)是公开的
  • 您可以通过使用GitHub的提供的功能,将bug公开,对源代码提出您的建议,而且你可以提交新特性的请求,或者你自己写的代码都可以提交。
  • 你也可以下载所有的源代码在你的机器上编译。Runtime与基础类库,还有一些工具可以在任何平台上编译。

改善的Console App

传统的.Net Framework可以在Windows上创建运行创建控制台apps,.Net Core同样可以,但是.Net Core改善了控制应用程序:

  • 跨平台:控制台程序可以跑在Windows,OS X和Linux
  • 原生编译:将托管程序的好处与原生C/C++应用程序的性能相结合

安装简单

因为.Net Core不是一个操作系统的组件,现在安装:

  • 不需要管理员权限
  • 不需要触碰系统组件了,也就是说不需要向操作系统的系统目录和注册表中写文件了
  • 复制一些文件到目标计算机更简单了,或者将原生framework编译进你的app。

这样,新的开发者不到一分钟就可以入门.Net了,包括下载Framework和tools。可以从这里

部署简单

作为运维人员,去部署一个开发部门给你的.Net应用程序的时候,你首先要做的就是去服务器检查.Net framework版本是否符合要求,如果不符合就需要安装一个能运行这个程序的基础版本,这些会给运维人员带来很大的困扰
相比之下,。Net core有两种发布部署方式:

Portable Apps

在部署一个portable app的时候,你除了.Net Core库之外,只需要部署你的程序和它的依赖就可以。
在目标机器上为了将Portable Apps跑起来需要安装.Net Core。你不需要提前去考虑你的app支持哪个平台,因为.Net Core就是一个独立的组件。
在.Net Core里,Portable Apps是默认的程序类型。

Self-contained Apps

Self-contained apps包含了所有的依赖,.Net Core runtime也会作为软件的一部分.由于你的APP中内置了.Net Core,所以不管你要部署的那台机器上安装没安装.Net Core都可以运行,而且就算之前的机子上有人安装了.Net Core,你的.Net Core类库也是与别人的.Net Core类库隔离开的。
这种方式的前提得是你的程序里内置了对应平台的.Net Core,比如你要在OS X上部署,你的APP里需要内置OS X对应的.Net Core,如果你内置的.Net Core是Linux版本的,那你就不能在OS X上边部署,这就要求你必须先考虑好,你的应用程序面向的平台。

.Net Core组件(Components)

和传统的.Net Framework非常像,.Net Core由一个叫CoreCLR的公共语言运行时(Common language runtime)组成。和.Net Framework一样,.Net Core中关键的也是类库。.Net Core关键的是CoreFX,这是一个模块化的类库集,而非单一的.Net framework类库。这样就可以你的程序需要什么库就加载什么库,不需要的不会加载。

公共语言运行时(The Common Language Runtime)

.Net Core中的公共语言运行时——CoreCLR是一个轻量级的运行时,提供了好多和传统.Net Framework的运行时相同的服务。这些相同的服务包含:

  • 一个垃圾回收器,它提供了内存自动管理。垃圾回收器按需分配和释放内存;你不必通过程序去做这些。不像C++,需要自己去管理操作内存的分配和释放。.Net Core也用了和.Net Framework相同的垃圾回收器,更多信息请访问Garbage Collection
  • 一个just-in-time(JIT)编译器,编译IL或者.Net中间语言(intermediate language)到机器码。在某些架构中,JIT编译器支持SIMD硬件加速。
  • 一个异常处理机制,允许你通过try/catch语句处理异常。

类库(The Class Library)

.Net Core的类库与.Net Framework的类库除了有一处主要的不同点之外,其余的非常相似。
不同在于:
传统的.Net Framework有很多类库是属于操作系统的一部分,并且它是通过Windows 自带的Windows update更新。
在.Net Coe中,它是按照功能组织的模块的个人库。

Microsoft.NetCore.App 被包含在runtime里边了,它包含了开发所有APP基本的类型,这些类型包括:

  • 基础类型,比如bool类型,签名与不签名的整型,浮点型和char结构
  • String类型:在.Net Core中,一个字符串是UTF-16编码单元的序列。.Net Core还包括了许多编码类型,这些编码类型可以允许你将UTF-16编码字符串转换成其他编码的字节数组,例如:你可以用UTF8Encoding class将.Net Core的string字符串转换成utf-8编码的字节数组,用来表示Linux上的string字符串。
  • 任务类型,例如TaskTask,用来支持异步编程。
  • 基本的线程类型
  • Console Class,用来支持开发console apps.

另外的一些库,需要通过Nuget包来安装

.Net Core工具(.Net Core Tools)

.Net Core包含了一个跨平台的命令行SDK,名字叫做.Net Core CLI(Command-Line Interface).这个CLI是编写.Net Core应用程序的一组对Unix友好的工具。它让C#编译器和Nuget包管理工具变得抽象,变得抽象的意思是你感觉不到编译器和包管理工具的存在,应用程序就编写好了。他同样可以与.Net原生工具紧紧集成在一起来产生高性能的原生app和库。
CLI带来的好处是开发者可以不用安装大型的IDE就可以编译和测试他们的代码,这在不是自己的电脑或者生产服务器上是极好的。visual studio code与visual studio在底层都是用的CLI,你可以根据你的需要选择不同的IDE.比如你可以直接通过文本器来使用CLI,或者你可以用IDE开发,编辑器内部调用CLI.

大多数情况下,你直接使用.Net Core CLI就是通过给dotnet.exe 提供参数,下边是dotnet.exe可以使用的命令:

  • dotnet --help:显示关于.Net Core CLI命令行的信息
  • dotnet new:初始化一个C#项目
  • dotnet new --lang F#:初始化一个F#项目
  • dotnet restore:为你的app还原所有的依赖
  • dotnet build:编写一个.Net Core app
  • dotnet publish:发布一个portable或者self-contained app。(查看【部署简单】章节)
  • dotnet run:从源代码中运行app
  • dotnet pack:在你的app中创建一个Nuget包

    dotnet.exe有扩展模型,允许你添加额外的命令。

语言支持和开发环境(Language Support and Development Environments)

.Net Core是语言无关的:任何以.Net Core为目标的语言都是可以用来开发.Net Core应用程序的,通过不同的编程语言开发的app,用其中的一种语言即可无缝地访问类型与成员。

当前,你可以用下边的两种语言的任意一种开发:

我们打算在未来支持更多的语言。
你有多种开发环境可以选择用来编写app,包括:

.NET Core and the .NET Framework

为了更好的感知.Net Core是什么,将它与.Net framework相比较:

.Net Core

包含CoreCLR,一个可提供基础服务的轻量级运行时(runtime),尤其自动内存管理,垃圾回收器,还有一个基础的类型库。

包含了CoreFx,一套个人模块化组装,你可以安装需要将其添加到你的app中,与.Net Framework 4.x不同,.Net Framework 4.x总是使整个.Net Framework类库可用,.Net Core只需选择你想要的。例如,如果你正在开发一款基于矢量的应用,你可以下载System.Numerics.Vectors包,而不是需要一个很大类库的花销,这样可以显著的减少你app的体积和他的依赖项。

适用于各种各样的现代应用程序,对内存和储存有限制的小型设备起作用

可以利用若干的技术来开发应用,比如asp.net core
开发web应用,Windows communication Foundation(WCF)

开发与现有的WCF服务相关联的应用,workflow foundation(WF)构建工作流。

可以变成app本地。换句话说就是.Net Core版本可以紧紧与你的app相结合,这可以减轻好多版本问题。

.NET Framework 4.x

包含公共语言运行时(CLR),一个相当大的运行时,可以提供内存管理,隔离应用程序域(application domain),大量的应用程序服务。

包含了.Net Framework类库,这个类库包含了成千上万个的类与成员,不仅非常大而且又是一个整体,不论你的app用了单个类型或者他们的成员(或者大多数app利用了一小部分函数),他们都是始终加载并且可以随时访问的。

适用于传统的Windows桌面应用程序,包括Windows forms(winforms)和Windows Presentation Foundation(WPF)应用程序,可以运用许多技术来开发应用程序,例如,ASP.NET和ASP.NET Web Form构建web应用程序,Windows Communication Foundation (WCF)构建包含soap的服务,Workflow Foundation (WF), 构建工作流。

在一个给定系统中全局可用。换句话说,即使一个app中包含了一个特定版本的.Net Framework安装器,但是假如安装器发现它不存在还是会安装完整的.Net framework,并且会独立于app维护。这就会产生版本问题,特别地是一个app遭遇了一个没有预想的版本,或者一个app跑在了之前没有开发的.Net Framework版本上。
从Windows8开始,.Net Framework作为操作系统的一个组件安装,并且通过Windows update升级。对于不同的Windows里边内置不同的.Net Framework版本,更多的信息请访问.NET Framework System Requirements.

总结:

虽然.Net Framework 4.6.2预览版和.Net Core是面向不同的平台,代表着不同的app开发和部署方法,但是他们都遵守 .Net标准1.5。这意外着他们彼此能够提供一个高度的兼容性与统一行为。尤其:

  • 有经验的.Net 开发者想要开发不同设备和平台的应用程序的时候,可以很容易适应.Net Core的开发。
  • .Net Core开发者可以很容易的过渡到用.Net Framework开发Windows桌面程序、平板和手机的方式上。
  • 用.Net Framework或者.Net Core写的类库可以很容易的在另外一个平台工作。

.Net Core具体实现(implementations)

许多的开发技术依赖.Net Core的可定制实现。当你用这些技术开发Apps的时候,你也许不会意识你是在利用.Net Core的好处:

  • ASP.Net Core.ASP.Net Core是一个模块化的asp.Net版本,它结合了ASP.NET MVC and ASP.NET Web API.它可以同时运行在.Net Framework与.Net Core上边,它被设计用来构建高性能的云和微型服务;在.Net Framework中,它不是打算作为asp.net的替代者的。有关更多的ASP.Net Core信息,请访问Introduction to ASP.NET Core.
  • .NET Native。对于用C#和Visual Basic编写的Universal Windows Platform (UWP)应用程序,.NET Native是一个编译和部署技术。.Net Native将Apps编译成原生代码,静态资源文件放入应用程序集中,这些都是.Net Core和另外一些第三方的正在使用的代码。有关更多.Net Native的信息请访问Compiling Apps with .NET Native.
  • Universal Windows Platform (UWP) apps。Universal Windows Platform允许你构建一个运行在Windows桌面,Windows平板设备,Windows phone手机上的app。这些应用可以上传到Windows store中。UWP 应用程序通过.Net Native为他们各自的平台编译原生代码,有关更多的信息,请访问 Get started with Windows apps

原文:https://dotnet.github.io/docs/getting-started/what-is-dotnet.html

Signalr实现消息推送

一、前言

大多数系统里面好像都有获取消息的功能,但这些消息来源都不是实时的,比如你开两个浏览器,用两个不同的账号登录,用一个账号给另外一个账号发送消息,然而并不会实时收到消息,必须要自己手动F5刷新一下页面才会显示自己的消息,这样感觉用户体验不太好。之前看了Learning hard关于Signalr的文章,刚好自己项目中有用到获取实时消息的功能,然而我们项目中就是用js代码setinterval方法进行1秒刷新读取数据的,这样严重给服务器端添加负担,影响系统性能!所以自己稍微研究了一下,下面是自己的一些理解,如果有不对的地方,请大家加以斧正!

二、实现原理

下面谈一下自己对Signalr的理解,Signalr可以进行远程分布式实时通信,都是使用远程代理来实现,其中有两大内部对象,第一个是Persisten Connection,用于客户端和服务器端的持久连接,第二个是Hub(集线器)对象,主要用于信息交互,将服务器端的数据推送(push)至客户端,大致原理如下:

1、客户端建立与服务器端的连接

2、客户端调用服务器端的方法

3、服务器端通过客户端发送的请求,响应数据,再将数据推送至客户端

三、Signalr实现消息推送

具体操作实现如下:

1、创建一个应用程序,我这里创建的是MVC应用程序

2、引用相关组件,右键引用》选择管理Nuget程序包

3、搜索Signalr,如图所示:

点击安装,在应用程序的Scripts文件夹里面会自动生成两个js文件,如图所示:

4、添加集成器类

5、注册signalr/hubs,在Startup.cs里面添加如下代码

6、新建控制器MessageController,然后在控制器里面新建两个视图方法SendMessage和ReceiveMessage,为了让效果看起来更直观,一个页面用于发送消息,一个页面用于接收消息,如图所示:

7、在我们刚刚新建的集成器类MyHub类里面添加代码:

(特别说明一下,这里的InsertMsg方法主要是将客户端发送的消息信息保存到数据库里面,便于消息读取,为了快速创建数据库表,我采用的code first方法来创建的,至于你想用什么方式创建表,那都是可以的。)

 

复制代码
namespace Signalr.Models 
{
    [HubName("MyHub")]
    public class MyHub : Hub
    {
        MessageDbContext _db = new MessageDbContext();
        public void Send(string title, string message)
        {
            this.InsertMsg(title, message);
            // 调用所有客户端的sendMessage方法
            Clients.All.sendMessage(message);
        }

        private void InsertMsg(string title, string message)
        {
            Message msg = new Message();
            msg.Title = title;
            msg.MsgContent = message;
            _db.Messages.Add(msg);
            _db.SaveChanges();
        }
    }
}
复制代码

表结构如图所示:

8、控制器MessageController后台代码

复制代码
public class MessageController : Controller
    {
      private MessageDbContext _db = new MessageDbContext();
        public ActionResult SendMessage()
        {
            return View();
        }

        public ActionResult ReceiveMessage()
        {
            return View();
        }

        [HttpPost]
        public JsonResult MsgCount()  
        {
            var count = this._db.Messages.Where(p=>p.IsRead==0).Count();
          return Json(new {count=count},JsonRequestBehavior.AllowGet);
        }
    }
复制代码

 

9、前端页面代码(SendMessage.cshtml)

复制代码
@{
    ViewBag.Title = "发送消息";
}
<title>发送消息</title>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
<script src="~/signalr/hubs"></script>
<script type="text/javascript">
    $(function () {
        // 引用自动生成的集线器代理
        var chat = $.connection.MyHub;
        // 定义服务器端调用的客户端sendMessage来显示新消息
        chat.client.sendMessage = function (title, message) {
            // 向页面发送接收的消息
            sendMsg();
        };
        // 集成器连接开始
        $.connection.hub.start().done(function () {
            sendMsg();
            // 服务连接完成,给发送按钮注册单击事件
            $('#sendmessage').click(function () {
                // 调用服务器端集线器的Send方法
                chat.server.send($("#title").val(), $('#message').val());
            });
        });
    });

    function sendMsg() {
        var options = {
            url: '/Message/MsgCount',
            type: 'post',
            success: function (data) {
                $("#count").html(data.count);
            }
        };
        $.ajax(options);
    }
</script>


<h2>
    发送消息
</h2>
<div>
    <label>我的消息:</label>
    <span style=" color:red; font-size:30px;" id="count"></span>条
</div>
<p>
    <div>
        标题:
        <input type="text" id="title" />
    </div>
    <br /><br />
    <div>
        内容:
        <textarea id="message" rows="4" cols="30"></textarea>
    </div>
    <br /><br />
    <div>
        <input type="button" id="sendmessage" value="发送" />
    </div>
</p>
复制代码

10、前端页面代码(ReceiveMessage.cshtml)

复制代码
@{
    ViewBag.Title = "接受消息";
}
<title>接受消息</title>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
<script src="~/signalr/hubs"></script>
<script type="text/javascript">
    $(function () {
        // 引用自动生成的集线器代理
        var chat = $.connection.MyHub;
        // 定义服务器端调用的客户端sendMessage来显示新消息
        chat.client.sendMessage = function (title, message) {
            // 向页面发送接收的消息
            MsgCount();
            var html = "<div>标题:" + title + "消息内容:" + message + "</div>";
            $("#msgcontent").after(html);
        };
        // 集成器连接开始
        $.connection.hub.start().done(function () {
            MsgCount();
        });
    });
    function MsgCount() {
        var options = {
            url: '/Message/MsgCount',
            type: 'post',
            async:false,
            data: { title: $("#title").val(), msgcontent: $("#sendmessage").val() },
            success: function (data) {
                $("#count").html(data.count);
            }
        };
        $.ajax(options);
    }
</script>


<h2>
    接收消息
</h2>

<div>
    <label>我的消息:</label>
    <span style=" color: red; font-size: 30px;  margin-right:10px;" id="count"></span>条
    <br />
    <br />
    <div id="msgcontent"></div>
</div>
复制代码

 

好了,大功告成,可能你有点疑问的是这个js文件引用地方在哪里

不防我们运行页面,按F12查看一下,它会自动在这里生成一个js文件,我们只要在页面中引用这个路径即可

四、页面效果(见证奇迹的时刻到了,哈哈哈~~~)

为了让页面效果更为直观,我这里用IE打开SendMessage.cshtml页面,用Google打开ReceiveMessage.cshtml页面。

 

权责申明

作者:SportSky 出处: http://www.cnblogs.com/sportsky/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧

.NET Core 使用Dapper 操作MySQL

.NET Core 使用Dapper 操作MySQL 数据库, .NET Core 使用Dapper。

目前官方没有出.NET Core MySQL  驱动,但是已经有第三方进行改动封装出.NET Core MySQL Connector 预览版。

Dapper 也已经出了 .NET Core 预览版。

Dapper dot net 是一个轻量型的ORM,但是性能很强大。

有了.NET Core MySQL Connector  我们可以直接使用ADO.NET 操作数据库。

目前EF Core 暂时不支持MySQL, 本篇主要讲解使用Dapper 操作 MySQL。

第三方 MySQL Connector: https://github.com/SapientGuardian/mysql-connector-net-netstandard

Dapper: https://github.com/StackExchange/dapper-dot-net

 

新建项目

新建一个.NET Core控制台应用程序 NETCoreMySQL

添加引用

使用 NuGet 控制台添加

Install-Package SapientGuardian.MySql.Data -Pre

Install-Package Dapper -Pre

 

MySQL 增删查改

在MySQL里面新建一个测试库 及表

测试所用脚本:

复制代码
CREATE DATABASE `test` 

CREATE TABLE `user` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `UserName` varchar(255) DEFAULT NULL,
  `Url` varchar(255) DEFAULT NULL,
  `Age` int(11) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
复制代码

新建一个User 类

复制代码
    public class User
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Url { get; set; }
        public int Age { get; set; }
    }
复制代码

下面来操作MySQL 增删改查

复制代码
        public static void Main(string[] args)
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            MySqlConnection con = new MySqlConnection("server=127.0.0.1;database=test;uid=root;pwd=;charset='gbk'");
            //新增数据
            con.Execute("insert into user values(null, '测试', 'http://www.cnblogs.com/linezero/', 18)");
            //新增数据返回自增id
            var id=con.QueryFirst<int>("insert into user values(null, 'linezero', 'http://www.cnblogs.com/linezero/', 18);select last_insert_id();");
            //修改数据
            con.Execute("update user set UserName = 'linezero123' where Id = @Id", new { Id = id });
            //查询数据
            var list=con.Query<User>("select * from user");
            foreach (var item in list)
            {
                Console.WriteLine($"用户名:{item.UserName} 链接:{item.Url}");
            }
            //删除数据
            con.Execute("delete from user where Id = @Id", new { Id = id });
            Console.WriteLine("删除数据后的结果");
            list = con.Query<User>("select * from user");
            foreach (var item in list)
            {
                Console.WriteLine($"用户名:{item.UserName} 链接:{item.Url}");
            }
            Console.ReadKey();
        }
复制代码

简单使用Dapper,更多功能可以查看官方文档。

执行效果:

 

GitHub :https://github.com/linezero/Blog/tree/master/NETCoreMySQL

Xamarin.Forms入门学习路线

Xamarin 介绍

Xamarin是一套跨平台解决方案,目的是使用C#语言创造原生的iOS,Android,Mac和Windows应用。

Xamarin的三个优势:

  1. Xamarin App拥有原生App的性能,因为最后生成的App中是使用的原生的控件和原生的API,所以它的体验和效率与原生App相近。
  2. 使用熟悉的C#语法,在Objective-C,Swift或者Java中能做的任何事情都可以用C#做到。除此之外,C#还有强大的IDE智能提示,lambdas语法,更自然的异步语法(Task、Async),NuGet快速获取组件。
  3. 在不同的平台上使用同样的语言还具有共享代码的优势,各个平台大约可以共享75%的APIs和数据结构代码。如果使用Xamarin.Forms来创建UI几乎可以共享100%的代码。

最终的思想,共享代码

说白了,Xamarin宣称的最大的优势就是在三个平台上使用同一种语言来共享代码,总体说来有三种技术实现:

  1. Shared Projects:可以在里面添加供三个平台公用的代码,图片和多媒体文件等,代码部分可使用#if __ANDROID__等条件编译符来指定哪一部分会编译输出到特定平台中。
  2. Portable Class Libraries(PCLs):使用更多的还是PCLs,PCLs库直接就能被各个平台所引用,一些流行的库如SQLite,Json.NET,ReactiveUI都支持PCL。
  3. Xamarin.Forms:支持你用C#代码来创建在三个平台上共享的UI界面,总共可以使用超过40个控件,它们都会在运行时映射为原生控件。

共享代码的关系就如下图:

Xamarin 安装指南

工欲善其事,必先利其器。Xamarin的安装过程参考简书上的一篇文章,内容很齐全很详细:http://www.jianshu.com/p/c67c14b3110c

由于墙的原因,从官网下载的安装包无法直接安装,可以通过安装包中解析出配置文件,从中获取下载路径:
Windows下载路径
Mac下载路径

Windows下的大体流程如下:

  1. Visual Studio肯定是需要的,推荐VS2013
  2. 安装jdk,修改环境变量
  3. 安装Android SDK,需要修改为国内镜像
  4. 安装NDK
  5. 安装GTK
  6. 安装Xamarin.VisualStudio
  7. 安装XamarinStudio(可选)

注意6和7的版本号很重要,必须要跟Mac端相匹配,跟破解补丁的版本也需要匹配。如果以后升级,通常只需要更新6和7就可以了。

关于Android模拟器,之前折腾过不少,最后推荐一款专用于游戏玩家的Andorid模拟器,海马玩模拟器,它的性能很好很流畅,不过游戏模拟器屏幕默认是横屏的,第一次要手动改成竖屏。

Mac下的大体流程:
如果只考虑用Mac开发iOS程序,不考虑在Mac下开发Android程序,那么大体流程如下:

  1. 安装MonoFramework
  2. 安装monotouch
  3. 安装XamarinStudio

需要注意三者之间的版本一一对应。

关于商业证书,Xamarin的价格是很昂贵的:


上面看到的价格只是针对单用户单设备平台,通常我们使用Xamarin都希望至少能用于Android和iOS两个平台,所以价格还得乘以2。

安装完毕后如果没有购买商业证书,那么可以按照上面那篇文章来破解试用,如果使用的版本号在3.11之前,那么只需要完成离线破解,IDE不需要登陆Xamarin账号,如果版本号在3.11之后,而且要编译iOS(目的是为了连接Mac端的BuildHost,如果是在Mac上开发编译iOS则不需要),那么还需要完成在线破解,具体破解流程文章里有,大体流程如下,最后提醒一下试用完了别忘了购买官方的商业授权。

离线破解流程

  1. 软件读取机器特征码;
  2. 将特征码通过邮件发给破解者,等待他回复授权证书,不付费证书有效期1个月,付费20元证书有效期10年;
  3. 将证书和对应版本的破解文件拷贝到指定目录。

在线破解流程

  1. 邮件申请开通在线服务
  2. 修改host的IP地址
  3. 导入SSL证书
  4. 登陆Xamarin账号

Xamarin.Forms 程序结构


程序的目录结构大致就可以参考这个图,最顶上一层表示三个特定平台的工程,第二层表示一个PCL或者SAP工程,通常也是Forms所在的工程,然后引用两个核心库Xamarin.Forms.Core和Xamarin.Forms.Xaml,然后特定平台的工程还要引用两个特定平台的库,这个特定平台的库可以让程序集使用特定平台的API。

Xamarin.Forms 官方Demo

Xamarin提供了很多学习用的Demo,地址是:https://developer.xamarin.com/samples-all/。不过官网的网速确实太慢,在GitHub上还有更多更全的Forms的Demo:https://github.com/xamarin/xamarin-forms-samples
其中我认为几个比较重要的Demo可以学习一下:

Xamarin.Forms官方文档

Xamarin官方提供了一套很全的在线学习指南,地址是:https://developer.xamarin.com/guides/xamarin-forms/getting-started/,这份指南目录结构良好,便于快速查看,从怎样开始第一个程序到后面怎样到商城发布一应俱全。
还有一个学习途径就是官网教材,可以免费下载离线版:https://developer.xamarin.com/guides/xamarin-forms/creating-mobile-apps-xamarin-forms/,教材的随书Demo地址:https://github.com/xamarin/xamarin-forms-book-preview-2。这本教材支持Forms1.3以上,并且章节一直在保持更新,截至2016/02/25已发布到24章,Demo的核心库已更新到2.0并且加入了UWP工程。
如果说在线学习指南可以帮助你快速入门,那么这本教程可以帮助你更细化的理解Forms程序。
下面我将24章的官方教材的目录做个简单介绍,后面有时间也会对重要的几章做个更详尽的剖析:

  1. How Does Xamarin.Forms Fit In?(Forms适合什么场景)
  2. Anatomy of an App(剖析一个FormsApp)
  3. Deeper into Text(深入文本)
  4. Scrolling the Stack(滚动面板)
  5. Dealing with Sizes(处理尺寸大小)
  6. Button Clicks(按钮点击)
  7. XAML vs. Code(创建UI的两种方式)
  8. Code and XAML in Harmony(XAML和代码的协调合作)
  9. Platform-Specific API Calls(平台特定的API调用)
  10. XAML Markup Extensions(XAML扩展标记语言介绍)
  11. The Bindable Infrastructure(绑定的基础知识)
  12. Styles(样式)
  13. Bitmaps(位图)
  14. Absolute Layout(绝对布局)
  15. The Interactive Interface(交互控件)
  16. Data Binding(数据绑定)
  17. Mastering the Grid(熟练掌握Grid布局)
  18. MVVM(数据绑定开发模式Mvvm讲解)
  19. Collection Views(集合控件讲解–List)
  20. Async and File I/O(异步I/O操作文件)
  21. Transforms(变换—缩放、定位等)
  22. Animation(动画)
  23. Triggers and Behaviors(触发器和行为)
  24. Page Navigation(页面导航)

其中我感觉有几章比较重要,如果对Xaml(WPF主要用的界面标记语言)开发不太熟悉的同事需要看一下这几章:

  • 7.XAML vs. Code:了解Xaml和Code两种方式来创建UI界面
  • 8.Code and XAML in Harmony:了解XAML和后台代码如何协同工作
  • 10.XAML Markup Extensions:了解扩展标记语言
  • 11.The Bindable Infrastructure:了解绑定的基础知识
  • 16.Data Binding:更深入的了解数据绑定
  • 18.MVVM:了解基于数据绑定的UI开发模式Mvvm

要对Forms的细节有深入理解看下面几章:

  • 3.Deeper into Text:深入理解文本
  • 5.Dealing with Sizes:深入理解如何处理尺寸大小,重点也是拿文本举例,教你如何理解移动开发里面像素、物理尺寸(英尺、厘米)、DPI、DIU,主要思想反正就是不要去关注表示大小的那些数值,字体应该使用字体枚举,布局应该是用比例去控制,要充分相信Xamarin平台能帮你控制好大小尺寸。
  • 13.Bitmaps:了解怎样在Forms中使用图片,也是满满的都是坑,显示在界面上的图片体积一定要尽量的小,不要将一张原始尺寸的图片加载成缩略图然后放在列表中显示,否则程序一定会内存溢出,一定要对图片进行裁剪,将适合的体积的图片用在适合的地方。从这一章中还可以学习图片在具体平台下的用法和差异等。
  • 19.Collection Views:了解集合控件,列表在App当中用得非常普遍,所以应当着重了解。
  • 20.Async and File I/O:在Xamarin中只能使用异步IO(或者说是PCL中只能使用异步IO),从趋势看未来的.Net Core可能也只支持异步IO、异步Http请求等,感觉这种更重视性能的IO思想是未来框架的趋势,所以可以借此熟悉一下,C#的异步语法应该算是众多编程语言中的佼佼者了。

下面对第五章Dealing with Sizes稍作讲解,这章重点介绍了移动平台下尺寸相关的一些知识,先看下下面两个表格:

型号 iPhone 2,3 iPhone 4 iPhone 5 iPhone 6 iPhone 6 Plus
像素尺寸 320×480 640×960 640×1136 750×1134 1080×1920
屏幕尺寸 3.5英寸 3.5英寸 4英寸 4.7英寸 5.5英寸
像素密度 165 DPI 330 DPI 326 DPI 326 DPI 401 DPI
单位点包含像素数量 1 2 2 2 3
点数尺寸 320×480 320×480 320×568 375×667 414×736
每英寸包含点数量 165 165 163 163 154
屏幕类型 WVGA WXGA 720P 1080P
像素尺寸 480×800 768×1280 720×1280 1080×1920
缩放比例 1 1.6 1.5 2.25
DIUs尺寸 480×800 480×800 480×853 480×853

第一张图是iPhone下的一些尺寸元素间的关系,第二张是WinPhone的,这里没有给出Android的,其实Android整体上说来跟iPhone的那些参数很相似。
Forms中真正使用的不是像素,而是点数,点里面包含的像素数量是不一致的,像iPhone2,3基本上是一一对应,一个点包含一个像素,iPhone4,5,6就是两倍像素,iPhone6Plus就是三倍像素,所以iPhone的图片里出现@2x@3x这些标识就是对应平台所使用的像素不同的图片。我们在Forms中使用的那些表示宽高的值就是这种点数单位,要知道设置的这些值可以获取整个页面的Width和Height值。
下面说下字号,Forms提供了几种枚举字号:Default,Micro,Small,Medium,Large,在不同的设备,不同的用户系统字号设置,不同的控件中,相同的枚举返回的字号数值可能都不一样。通过Device.GetNamedSize方法获取的FontSize值的单位是double,表示文本字符从最下面到最上面到高度,字体的宽度一般都是FontSize值的一半,字体的行距一般是FontSize值的1.2倍。

Forms中UI布局细节

在Forms中设计各种元素布局等细节依然可以参考设计网页采用的盒模型的思想。从大的块元素的分离到小如一个文字,都可以想象成一个个小盒子。由内容区,内边距,边框,外边距组成。

Forms中还有几个比较容易混淆的类:ContentView,Frame,BoxView。
虽然可以按照盒模型的思想来布局元素,但是Forms中没有标准的margin的概念,Forms的做法是在一个内容视图外面再嵌套一个ContentView,ContentView继承自Layout,只多了一个Content属性来存放内容视图。此时,ContentView的Padding属性就可以想象成盒子的Margin。
Frame在布局中也比较常用,通常用于定义页面中一组视图的区块,它继承自ContentView,多了些边框、阴影等属性。
BoxView是一个矩形填充区,在Forms中用得最多的地方就是用它来绘制横线、竖线等分割线。虽然看起来很山寨,但它却是是Forms中的一个标准用法。

APP的发布

前面教程重点是介绍Xamarin.Forms相关的东西,对于平台特定的那些没有做介绍,比如平台和Forms之间的交互(依赖注入,前面的Demo介绍PPT有),比如最后APP的发布,发布相关的东西参考前面提到的在线教程:

Android发布教程

我们项目中的Android安装包没有发布进商城,是通过网址直接下载,所以发布教程没有验证:https://developer.xamarin.com/guides/android/deployment,_testing,_and_metrics/publishing_an_application/

iOS发布教程

iOS需要发布,流程主要是有很多和apple打交道的地方比较麻烦,比如说开发者证书,AppStore证书,用特定的证书打包你的IPA,提交到itunesconnect,审核等等,Xamarin的教程如下:https://developer.xamarin.com/guides/ios/deployment,_testing,_and_metrics/app_distribution/

Xamarin组件商店的使用

Xamarin有自己的组件商店,里面有很多免费和收费的组件,刚开始就在这上面找东西,不过网速实在不可恭维,后来发现免费插件这上面有的GitHub上几乎都有,所以使用GitHub又快又方便。
如果要在组件商店中下载需要注意最后一步需要FQ,因为网站用了google提供的jquery库:https://components.xamarin.com/
GitHub上Xamarin提供的一个常用的免费插件目录,这个插件库里有Xamarin官方的也有第三方的。我们的项目所使用的插件大多来自这个目录,里面有插件的NuGet和GitHub地址: https://github.com/xamarin/plugins
GitHub上Xamarin官方插件库的源代码: https://github.com/jamesmontemagno/Xamarin.Plugins

GitHub上去找东西

在GitHub上使用“Xamarin.Forms”为关键词进行搜索,可以快速找到相关资源。

  • Xamarin-Forms-Labs:这个库很大,包含的东西很多,IOC容器、序列化组件、缓存组件、UI控件等,我们用得最多的还是UI控件。但是用法不是像其他插件一样直接引用它的相关dll(之前尝试过很久,直接使用会导致莫名其妙的问题),而是直接拷贝代码到我们项目中直接用,但是这个库也正如它的名字一样,是实验性的,在GitHub介绍上也可以看到可用控件里几乎所有控件都是beta状态,我们在使用过程中也发现了不少Bug,所以项目里的代码有所改动,跟以前应该不太一样了。我们项目里参考并使用的控件有CheckboxRadioButton等。
  • XamarinFormsGestureRecognizers:这个没有使用过,从说明来看是一个手势功能相关的库。XamarinForms里的控件默认只有Tap点击事件,其他手势操作都在平台内部,这个库就是教你怎样将它们连接起来,然后在PCL中写针对控件的手势操作代码。

IDE技巧

  • Android很简单,在Windows上启动海马玩模拟器,模拟器启动时间比较长,但启动好之后就可以不用关了,然后只需要用Visual Studio设置Android项目为启动项,附加到模拟器进行调试即可;真机用Usb连接使用同样的方式在IDE里调试。
  • iOS比较麻烦,需要打开Mac电脑上的BuildHost(如果Mac不在身边,可使用远程软件tightvnc操作,不过一台Mac同时只能供一人使用),然后Visual Studio设置iOS为启动项,可自动寻找局域网内的Mac电脑上的BuildHost,然后输入配对码即可连接成功,如果失败请重启BuildHost再试;真机调试一样,不过真机只能连接在Mac电脑上。

一些常用插件

Forms中插件的使用也比较简单,基本上用一次就会了。首先,插件的使用方式都很统一,Forms的PCL中一般引用两个库,两个库都是PCL的,一个带Abstractions后缀,里面只定义了接口和实体,不包含逻辑代码;另一个不带Abstractions后缀,就像工厂一样,只负责创建Abstractions程序集里定义的那些接口的实现者,创建的方式不是使用前面提到的Xamarin提供的依赖注入( UsingDependencyService ),而是条件编译的方式直接New对应平台的实现者。在Andorid和iOS(或者WP)里引用了带Abstractions后缀的程序集,然后引用一个真正的属于该平台的程序集(非PCL,可以调用平台特殊API),这个程序集实现了Abstractions程序集里的接口,它的实例化对象在运行时被真正使用。如果我们自己写插件就可以使用Xamarin提供的依赖注入的方式,在特定平台内部写好功能类,然后在PCL中直接导出就可以使用了。
然后下面列出一些常用插件:

  • Corcav.Behaviors:帮助你将列表的每一项绑定命令到这个列表的BindingContext,而不是具体项的BindingContext,帮助将事件转为命令,Xamarin自身不带这个功能。
  • EZ-Compress-for-Xamarin :压缩图片流的库。
  • MvvmLight:Mvvm开发模式的支持库,还用到了里面的Ioc容器(SimpleIoc,我们系统里有两套Ioc容器,一个就是这个,另一个是Xamarin的依赖注入容器);还用到了它提供的导航组件。
  • Xam.Plugins.Messaging:提供打电话、发短信、发Email等功能。
  • Media.Plugin:提供拍照、选照片的功能。
  • PCLStorage :跨平台的异步I/O库。
  • Vibrate:提供了手机震动的功能。
  • Toasts.Forms.Plugin :顶部的那个彩色浮动提示框。

然后一些用得比较多的UI组件有:圆形图片、CheckboxRadioButton、图片选择器等,有自己写的,也有在Xamarin-Forms-Labs的基础上改的。
Andorid使用插件时注意在工程的Properties的AndroidManifest.xml中写入对应的权限。

Xamarin官方论坛

遇到疑难的问题,上Xamarin官方论坛搜索,大部分你遇到的问题上面应该都会有,基本用不着主动提问,这个地址我认为访问频率相当高,地址如下:http://forums.xamarin.com/

没有涉及到的东西

本教程没有涉及到GIS相关的内容。
没有对特定平台内部相关知识介绍,我们团队的成员对平台特定API都了解太少,特别是涉及UI方面的,要掌握这些知识的难度跟学习原生开发无异,所以对一些难题解决起来比较费力,比如之前的Android和iOS的Tab页样式差异问题(Android的Tab在屏幕上面,iOS的Tab在屏幕底部)。因为Tab属于页面,跟控件不一样,不能使用CustomRenderers的技术重写样式,在论坛上搜索的结果如下:
http://forums.xamarin.com/discussion/54668/bottom-tab-bar-menu-for-android
https://forums.xamarin.com/discussion/10004/tabs-on-the-bottom-for-android-example-code
http://forums.xamarin.com/discussion/56320/is-there-any-way-to-show-tabs-on-bottom-in-android-using-tabbedpagerenderer
主要意思先是从设计的角度强调不要进行这样通用的设计,如果一定是通用样式那么给出的解决方案也是平台内部的,首先不说技术门槛,这个实现方式跟Forms的思想就是有冲突的,所以最好的方案就是在新APP里用Forms纯手写Tab页面。

资源汇总

官方Demo:
https://developer.xamarin.com/samples-all/
https://github.com/xamarin/xamarin-forms-samples
官方文档:
https://developer.xamarin.com/guides/xamarin-forms/creating-mobile-apps-xamarin-forms/
官方教材:
https://developer.xamarin.com/guides/xamarin-forms/creating-mobile-apps-xamarin-forms/
https://github.com/xamarin/xamarin-forms-book-samples/
官方论坛:
http://forums.xamarin.com/
常用插件:
https://github.com/xamarin/plugins
https://github.com/jamesmontemagno/Xamarin.Plugins
https://components.xamarin.com/
https://github.com/XLabs/Xamarin-Forms-Labs

验证码对抗之路及现有验证机制介绍

yahoo邮箱在九几年的时候,业务深受各种邮箱机器人的困扰,存在着大量的垃圾邮件,于是他们找到了当时仍在读大学的路易斯·冯·安(Luis von Ahn),并设计了经典的图形验证码,即通过简单的扭曲图形文字进行机器的识别。

通过这个简单的图形,他们很快的控制住了垃圾邮件的数量,并将大量的机器人据之门外。

但是即使验证码解决了垃圾邮件的问题,我们仍要提出一个问句:

 

验证码是必要的吗?

阿里有句简单的话:不忘初心,方得始终。

验证码不是一个功能性的需求,他并不能带来业务的提升,也不能带来任何价值。

验证码只是为了解决机器问题才诞生的。在设计和验证码演化的过程中,必须同时考虑安全性和体验。

 

让我们老考虑验证码的最简化模型,关键点在于:生成的问题能够由人来解答,并且机器难于解答。

于是传统的图形验证码的重点就放在了如何生成让机器难于解答的图片上来。

 

从上图看来,相应的各种方法已经有了相当成熟的一些对抗办法,更不用说现在已经广泛泛滥的打码平台了。

结合我们安全性和体验两方面来讲,传统验证码在两方面来说都已经不能满足要求了。

 

树林里分开了两条路

现在随着攻防的升级和对抗的不断加强,验证码的面前针对安全性和体验这两个关键点分出了两条路:

  • 从体验上来:基于人类用户的行为
  • 从安全性上来:基于人类认知问题的答案

 

基于人类认知问题的答案,我们已经碰见过很多种了,这里简单贴两个:

 

当然也存在这类:

 

简要分析,优点明显:机器没有这个认知能力(废话,好多我都做不出来)

 

存在的问题有:

1.题库维护的难题。如果题库被遍历完成,那么其安全性无法保证。

12306为什么后面把问题变成了图片呢? 因为有攻击者将每次的答案与出现的可选问题进行了多次重复遍历,当这个遍历到达一定次数后,会发现某个答案与某张图常常会关联出现(因为问题必定会有正确答案),通过这个出现概率即可得到答案。于是,12306才把文字变成了现在扭曲的图片。 并且还有研究人发现,12306的图片可能大量来自某百科,通过逆向使用该公司的图像识别服务,可以得到该图片的部分标签。

 

2.体验差

过年的时候,正常人平均都要答3、4次才能答对答案。而且部分图片因为分辨率不高、再加上缩小图片以后,会更加的看不清了。

基于人类用户的行为,具体点就是用户在进行相应操作时,会产生的操作记录和环境信息(详见设备指纹一文)等。

基于人类用户行为这条路是基于以下基础的:

  • 机器人的环境有别于正常用户
  • 机器人的动作或频率等有别于正常用户
  • 机器人的在相应关键业务点的行为逻辑有别于正常用户

 

但是这个基础在技术对抗上有个更关键的点,在于如何构建一个安全的信道,将这些数据回传回来。如果这个信道的采集机制、加密机制和传输机制被攻击者所探知,那么以上采集的信息将没有秘密可言。(换句话说,攻击者会伪装成正常用户的行为发送数据)

阿里所采用的前端采集和加密机制,利用自动化混淆、加密函数随机选择、线上自动迭代等能力将web前端打造为了一个可信的端,并将数据回传。(详见验证码的前世今生系列

 

该类方法的优点:

收回了对用户的强打扰将基于知识和问题的对抗转变为前端加解密采集等机制的对抗如果攻击者无法攻破这一层堡垒,即使打码平台存在也无法攻击成功

缺点:

加解密机制需要做到足够强,并且需要拥有攻击感知能力、线上的自我变更能力和自动化测试等能力

我们选择了同时走两条

基于上述思考,阿里滑动验证综合考虑两种类型的方法,结合并推出了滑动验证的体系:

通过基于用户行为的第一道防御将对正常用户的打扰降低,使得正常用户可以以极小的代驾通过滑动验证,而同时对不确定的用户实施知识型问题的验证,在拦截机器人的同时,保障业务的正常平稳运行。

再看看google先阶段的norecaptcha:

可以看到,google的模式与阿里的模式有些类似,所不同的是google所使用的验证码模式是点击,而阿里是滑动。

简单分析下google,对于第一步的点击验证,google更多的是通过其基于虚拟机的强混淆器对整个数据采集过程进行了加密,并综合了环境信息(如设备指纹、cookie、点击频率等信息)来进行判断,而第二步的知识验证也包括以下几种(部分在之前的图中没有出现):

  • 1.扭曲的图形
  • 2.图形的分类
  • 3.高级图形分类(会不断的出图,点完一张又一张)

 

那这样是否就是验证码问题的银弹呢?

之前已经提到过,无论是google的norecaptcha还是我们的滑动验证,其核心都在于其本身的风控引擎和相应的规则,对于攻击者来说也有两条路:

  1. 正面强行突破,破解前端的密文、然后模拟正常用户行为,达到直接通过的目的
  2. 退而求其次,通过触发二次验证过程,然后通过ocr或者打码等其他方法通过

 

rsa上针对googlenorecaptcha的破解

4月8日,BlackHat上公布了一篇来自哥伦比亚大学的论文,大致上讲了他们是如何突破google的norecaptcha验证码的。

简单说明下他们的破解过程:

1.攻击者通过大量的模拟器及代理IP来伪造Browsing History及Browser Environment来Fuzz测试Google的风险分析系统。测试过程中发现包括Useragent、Canvas Fingerprint、屏幕分辨率、鼠标行为动作众多因素均为风险判断的因子,风险判断决策返回结果随机且有严格的次数限制;

比如,攻击者发现,修改user agent、使用firefox而声称chrome等会导致norecaptcha出现回退,即出现普通的验证码。

 

2.在得出了这些参数与最终结果的关系后,也没有正面突破,走到了第二个思路,经过fuzz尝试后,发现google的容错性较高,即即使图中只有2个正确答案,你如果点击了3个图,包含两个正确答案,也算正确。并且,根据基于次数的统计发现,norecaptcha中出现答案的比例较多的为2个答案(74%),因此,攻击者很聪明的选择了每次选择3个图像的方法,因为这样做可以使得每次成功的概率提高(毕竟多选了一个可能正确的答案,即使选错了也可以对)。

3.同时,在大量的fuzz面前,攻击者们发现有些图片会时不时的出现。出现次数最多的一个图片出现了12次。他们开始意识到这些图片并不是实时生成的,而是从一个已经生成好的池子中生成的。重复的利用,也就意味着大规模的可能性。

尽管google生成的图每张的hash值不同,但是利用感知hash算法(Perceptual hash algorithm),攻击者可以对每次稍微变换了hash的图进行相似度比较,如果相似度较高,即可完成对已打标完成的图的重定位。

重定位完成后,既可以对已经完成的答案进行重复利用,而这种重复利用在大规模的攻击中是非常重要的,无限弹药在游戏中的作用你懂的。

4.完成了以上动作以后通过用图片搜索相应关键字(利用google自己的功能搞google)、其他图片搜索引擎(Alchemy、GRIS、Clarifai、NeuralTalk)等对图进行搜索,并利用这些数据得到了大量的图片相关的子类标信息,越来越多的标签信息意味着对图片有了更多更高维度的刻画。通过在各个网站收集相关标签,即可得到一个对该图片较为全面的描述。

 

5.利用机器学习的算法,其实就是利用Word2Vec,将两个图的对应关键字放在一个向量空间中,然后利用余弦相似性原理,找出相似度最高的几张图片。(与我们传统的找两个相似的文献或同作者的思路类似)。

6.将之前的五步进行组装。

  • 看见未曾见过的图,感知hash计算,如果是已有答案,直接给答案。
  • 看见见过的图,直接给答案。
  • 如果是未曾见过的,也不在数据库内,开启图形引擎获得标签信息,再扔进模型里面得到答案,将答案存入库里。
  • 循环以上步骤比如:同时出现红酒、酒杯、酒这几个标签,那么对所有获得的标签进行相似性比对后,会取出带有酒这个标签的前几个图

 

阿里在实际的对抗中,我们也发现有攻击者无法攻破第一层防御,即他们无法正常通过,但是却知道如何触发第二级验证,并尝试通过破解第二级验证的问题通过。

如果想要真的做好在安全和体验间的良好平衡,需要同时在两个方面都下功夫,才能保证整体的安全水位。

前端可信端体系的建立、强大认知问题系统的建立需要持续不断思考和提高。我们的每一次的变更,接入的客户都将实时享受到更高安全等级的防护。

 

作者:目明@阿里巴巴安全部,更多技术文章,请访问阿里聚安全博客