C4.0和VS2010新特性

更新时间:2023-05-19 01:07:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

介绍VS2010的新特性等一些新功能

VS2010被认为将是续写Visual Studio 6 的扛鼎之作。整个IDE不仅是使用了WPF重构,而且使用了最新的NET Framework 4作为强大的后援支撑。从上至下可圈可点。下面我们就来看一看VS2010在哪些方面引人注目——

1)WPF重构界面:整个VS2010 IDE全部使用WPF重构,因此与Win7外观紧密集成,而且实现了先前所有NET版本所不能实现的一些功能——比如代码的无极缩放(打开一个项目应该可以看到左下角的显示比率,默认100%;这样您不必切换字体大小了,可以手动输入百分比,可以下拉选择,当然更可以直接Ctrl+鼠标滚轮快捷方式进行调整)。

2)快速搜索:

I)如果想寻找某个类(方法等)在何处调用,直接选中这个方法(类名),IDE会自动在当前打开的文档中使用淡紫色圈出所有的这个类(方法)名称。

II)快捷键“Ctrl+逗号”呼出搜索类和方法框,直接输入类名(不区分大小写,可以使用Pascal输入形式)自动列出所有的类和方法、属性等。

3)架构体系查看:

要想知道某个项目究竟有哪些文件,它们之间的调用关系等,在VS2010易如反掌——您所要做的只是打开架构浏览器(位于View菜单下的Architecture Explorer),然后就可以通过点击Solution View或者Class View查看当前项目(或者整个解决方案)的类、方法等。还可以通过文本框输入进行检索(点击一个漏斗图标,检索方式同“快速检索”)。您更可以使用Ctrl+A的方式选中全部的类(方法),点击“Architecture Explorer”左边第一个按钮,自动创建生成关联图。

当然,你想要知道某个方法在哪些地方被调用了,可以在该方法上右键,选择“Call Hierarchy”(显示层次关系)即可。

4)第三方程序的测试:

您可以在完全不知道第三方的程序情况下对其测试,这个是一个重大的突破。首先您创建一个Test Project,右键加入Coded UI Test文件,打开后选中“Record actions(录制行为)”那个选项,然后打开一个第三方的程序(比如画图板等),你随随便便做一些操作(在此之前务必按下右下角的录制动作的按钮),然后等到完毕之后再次点击那个停止记录的按钮,然后点击右边那个“Generate Codes”(生成代码)就可以生成代码,您可以对这些代码进行调试了。

5)可选参数和命名话参数(C#):

早些时候如果你想省略某些函数的参数,您不得不定义多次重载该函数以便获得这些函数的不同参数形式。在中自带参数省略的功能,但是C#的程序员只能望尘莫及。现在不必了!C#也完全可以这么做,为您少些诸多重载函数打开方便之门。比如:DoTask

介绍VS2010的新特性等一些新功能

(string taskName, bool Repeat=false) {……},但是可缺省参数必须在最后定义,例子中把Repeat移到taskName前是绝对不允许的,而且缺省参数的赋值必须是const类型(要不是写死的,要么是const变量,不能是其它的)。

与此同时,VS2010中还支持乱序给参数赋值——什么意思?如果某个函数有多个参数,你只要(函数名:数值)这种方式,您就可以随心所欲给任何函数参数赋值了。

假如有一个接口

6)协变和反变(Co-variant & Crop-variant)

这是VS2010新增的一个内容,用于在编译的时候确认是否允许不同类型的泛型接口之间是否存在转换的问题。

为了了解“协变”和“反变”的概念,我们先看一个例子:

假设我们定义了一个接口和若干类:

class Father

{

public virtual void Say()

{

Console.WriteLine("Father");

}

}

class Son : Father

{

public override void Say()

{

Console.WriteLine("Son");

}

}

class Son2 : Father

{

public override void Say()

{

Console.WriteLine("Son2");

}

}

Interface InAndOut<T, V>

介绍VS2010的新特性等一些新功能

{

void Input(V value);

T Output();

}

class Program<T,V>:InAndOut<T,V>

{

private object value = default(V);

public T Output()

{

return (T)value;

}

public void Input(V tv)

{

value = tv;

}

}

又假如我们已经实例化两个接口的实例:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

现在我们令:iobj1= iobj2,可行吗?

乍一看似乎可行——为什么呢?因为联想到右边的两个子类Son会被自动隐式转化成其父类Father。就好像是Father f = new Son()一样可以(注意:我们把子类隐式转化成父类成为“协变”,反之成为“反变”)。而且,根据接口定义,输入方向是接受一个Son(实际存储在iobj2中,被隐式转成Father),输出的时候还是存储的Son被隐式转化成Father输出。

这种思考逻辑固然没有错,但是它有一个前提条件——即从iobj1输入方向看,必须是Son到Father,输出的话也必须是Son到Father!但是泛型仅仅是一个定义,谁能够保证在类中Father或者Son一定是输入(或者是输出)参数呢?如果我们改变成以下的形式呢?

class Program<T,V>:InAndOut<T,V>

{

private object value = default(T);

public V Output()

介绍VS2010的新特性等一些新功能

{

return (V) value;

}

public void Input(T tv)

{

value = tv;

}

}

这样就出现了问题——首先,iobj1指向iobj2(接受一个Father的参数,此时如果我的Father输入的是Son2,那么实际转化到Son的时候就发生异常了;同样地,输出因为是Son2不能转化成Son,因此发生异常)。

这个问题就是因为输入输出方向不明确所导致的。如果我们强制是一开始就给出的输入(输出方向)。即V只能作为输入,T只能作为输出就可以了。

推而广之,假设存在两个泛型T和V,假设V:T(V是T的子类)。那么泛型接口之间转换的一般规则是:输出类型是父类,“输出”一般是协变;反之,输入类型是子类,一般是反变。约束这种输入输出泛型的规则就是:输出线的接口加关键词out,输入加in。如下所示:

Interface InAndOut<out T, in V>

{

void Input(V value);

T Output();

}

那么你输入以下代码,就可以输出结果:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

这种规则本质上是编译器的行为理解。但是不一定就是正确结果,考察下列例子: InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son2, Father> iobj2 = new Program < Son2, Father >();

这一段代码照样可以通过编译,但是运行仍旧报异常。为什么呢?因为“输入端”和“输出端”尽管都符合了隐式转换的条件,但是你注意:把一个Son对象存储到iobj2的时候,iboj2的输出要求是Son2,而不是Son! 因此要保证运行正确,必须做到这样:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

输入端接受Son,能够隐式转成蓝色的Father,蓝色Father存储的子类对象同样必须可以转化成Son(即一个协变的东西必须能够支持其反变;反之,一个反变的泛型必须支持其协变泛型,这就是著名的“协变反变类型”,简称“协-反变类型”)。

介绍VS2010的新特性等一些新功能

证明:(红色的Son开始):隐式转化成Father(协变),然后蓝色的Father(其中存储Son)强制转化成绿色的Son(反变)。

同理,(黑色的Father开始):黑色Father返回的内容(存储Son

)可以强制转化成蓝色Father的内容(反变),同时可以隐式转化成绿色Son(协变)。我觉得可以使用对角线规则验证(猜想,对于任意的泛型A,B,C,D):

InAndOut<A,B>

(B既可以转成D,也可以转成C;输出A包含的内容

可以转化成D,也可以转化成C)

InAndOut<C, D>

VS2010之所以那么强大,究其原因是其背后有着强大的C#4.0作为后台支撑。和以往的所有版本相比,C#4.0的动态性大大增强——dynamic就是一个非常明显的例子:

(一)dynamic初探:

以前因为某些特殊原因,需要动态的调用外部类(假设这个类是实现了某个带有参数的接口函数的),通常我们只能用反射了。示例代码如下:

Assembly asm = Assembly.LoadFile(“xxxxx”)

asm.CreateInstance("MyAssembly.ClassName").GetType().InvokeMember("Say", BindingFlags.InvokeMethod, null,

asm.CreateInstance("MyAssembly.ClassName "), new string[] { "aaa" });

这里顺便简略说一下反射流程:首先通过绝对路径加载某个NET的dll文件,然后创建该assembly中某个class的instance(该class必须有无参构造函数),获取其类型之后动态调用其函数Say,“BindingFlags.InvokeMethod”表明是一个普通类方法,“null”的地方是传递一个参数名的,和指明最后的string[]中的一串values内容一一匹配的……可见使用反射调用函数是很痛苦的一件事情。

现在呢?您根本不需要那么麻烦了!因为C#的dynamic会为您做好一切的,下面就是见证奇迹的时刻——

Assembly asm = Assembly.LoadFile("xxxxx");

dynamic dfun = asm.CreateInstance("MyAssembly.ClassName");

dfun.Say("Hello!");

注意到咖啡色的代码了么——什么?dynamic竟然可以智能感知出动态加载的那个类的方法Say?其实不然:当你按下这个点的时候,IDE是没有智能感知的,但是如果你知道这个类是有这个方法(因为接口给了其一个契约,必须实现接口中的方法;而接口的方法是公开的),你就可以完全不理会智能感知,照样写,照样编译通过运行。神奇吧!

介绍VS2010的新特性等一些新功能

看到这里,你就不会认为dynamic和var是“差不多”的概念了(var无非是根据赋值的类型编译器自己判断;且var不能作为函数返回值类型,但是dynamic可以)。 或许有人会疑问:dynamic可以完全替代类似像简单工厂、抽象工厂一类的东西了咯?我的理解是——不对!从上面的定义中可以得知:dynamic必须首先获取对象实例,然后动态反射是它做的事情;如果完全取代反射,实例也获取不到,如何反射呢?真是“巧妇难为无米之炊”啊!

说道dynamic可以作为返回值,下面给出一个例子:

class DynamicClass

{

public int Num1 { get; set; }

public int Num2 { get; set; }

public DynamicClass(int n1, int n2)

{

Num1 = n1;

Num2 = n2;

}

public dynamic DynamicAction

{ get; set; }

}

主函数注意咖啡色部分:

static void Main(string[] args)

{

DynamicClass t = new DynamicClass(1, 2);

t.DynamicAction = new Func<int, int, double>((x, y) => x + y); Console.WriteLine(t.DynamicAction.Invoke(t.Num1,t.Num2)); }

道理很简单:因为dynamic类型可以赋值任何东西(包括匿名委托),所以我创建了一个匿名委托给它。然后调用计算结果(匿名委托的调用使用Invoke,可以省略)。 但是……dynamic不仅仅可以动态反射类方法和属性,还可以“空中楼阁”般动态地去创建一个类方法和属性,并且赋值,相信吗?这是第二话。

(二)神奇的ExpandoObject类和自定义动态类扩展:

介绍VS2010的新特性等一些新功能

dynamic在第一话中已经展示它动态根据赋值类型直接自动完成反射的强大功能。现在又是一个新奇迹的诞生——

static void Main(string[] args)

{

dynamic d = new ExpandoObject();

= "ServiceBoy";

d.Action = Func<string>(()=>;);

Console.WriteLine(d.Action());

}

初看这个代码只是简单的读写Name属性,毫无稀奇可言。但是你注意哦——你到MSDN——或者你索性new ExpandoObject().Name 试试看,有Name和Action这个属性吗?——没有啊,真的没有!嘿,奇了怪了,既然没有,为什么你可以凭空“捏造出一个属性”,而且可以给属性赋值,并且读取属性内容呢?

俗话说的好——天下没有白给的食——微软这个类意在向我们揭露一个惊天的大秘密,那就是你可以自定义dynamic类,让这个类跟随你的要求动态的改变自己(比如增加一个新属性等)。我们可以参照MSDN,给出一个自定义的ExpandoObject: public class SimpleDynamic : DynamicObject

{

Dictionary<string, object> Properties = new Dictionary<string,

object>();

Dictionary<string, object[]> Methods = new Dictionary<string, object[]>();

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

{

if (!Methods.Keys.Contains())

{

Methods.Add(, null);

}

if (args != null)

{

Methods[] = args;

}

StringBuilder sbu = new StringBuilder();

foreach (var item in args)

介绍VS2010的新特性等一些新功能

{ sbu.Append(item);

}

result = sbu.ToString();

return true;

}

public override bool TrySetMember(SetMemberBinder binder, object value)

{

if (!Properties.Keys.Contains())

{

Properties.Add(, value.ToString());

}

return true;

}

public override bool TryGetMember(GetMemberBinder binder, out object

result)

{

return Properties.TryGetValue(, out result);

}

}

首先说明这个例子的作用:随意增加不重复的属性并赋值(取值),并且让你随意创建或者调用(带参或无参)函数进行输入(输出)。

分析一下这个类的主要特点:

一般地,任何一个类——如果需要动态为自身添加属性、方法等的,就必须实现IDynamicObjectProvidor接口或者是DynamicObject虚类(之所以用虚类的原因是“各取所需”的缘故,DynamicObject类都通过虚方法virtual去“实现”了接口中所有的方法,只要继承了这个类,读者可以根据需要“任意”动态覆盖你要的方法)。这里介绍三个最常见的方法:

o 如果需要支持动态创建写属性,必须覆盖TrySetMember,其方法介绍如下:

介绍VS2010的新特性等一些新功能

o 如果需要支持动态创建读属性,必须覆盖TryGetMember,其参数作用和

TrySetMember大致相当,只是反作用(用于获取某个已有属性的内容,并且反向传递给object作为输出结果,注意TryGetMember的value是一个out类型)。同时,这个函数多出一个result类型,用于返回已有属性的存储的值(NULL抛出异常,被认为是错误的)。

o 如果需要动态调用函数并输出结果,必须覆盖TryInvokeMember方法,此函数

比较复杂:

根据以上表格,对照不难读懂我的示例代码——现在假设你是这样调用的:

1)

dynamic d = new SimpleDynamic();

= “Serviceboy”;

Console.WriteLine();

介绍VS2010的新特性等一些新功能

首先创建了一个d的动态类型,然后当赋值给Name的时候,因为Name是属性,所以触发了“TrySetMember”函数,该函数自动检查是否已经存在这个属性名,如果不存在,则将其添加进入一个Dictionary中并将对应赋予的值传递进去保存起来。当使用输出的时候,同样地,TryGetMember被触发,系统检测是否预先创建过这个值,如果没有,则抛出异常;存在的话,取出对应的存储value并返回给系统。

2)

dynamic d = new SimpleDynamic();

Console.WriteLine(d.Say(“Hello!”));

首先创建了一个d的动态类型,当动态创建一个方法的时候,系统检测是否包含这个方法名,不包含将添加这个方法名到Dictionary保存,接着检查参数是否为空,不为空把参数赋值给那个函数名作为Key的Dictionary中保存,最后使用StringBuilder串起来赋值给result作为输出。

下面给出一个比较复杂的例子——自定义的XML创建器(仿Jeffery Zhao): public class XmlCreator : DynamicObject

{

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

{

//如果除了第一个节点是字符串,后面都是XElement对象,表明此节点是父节点

if (args[1] is XElement)

{

XElement root = new XElement(args[0].ToString());

//把子节点添加到父节点

for (int i = 1; i < args.Length; ++i)

{

root.Add(args[i]);

}

result = root;

}

//否则是子节点

else

{

//拷贝所有属性到数组:

string[] attributes = new

string[binder.CallInfo.ArgumentNames.Count];

介绍VS2010的新特性等一些新功能

for (int i = 0; i < binder.CallInfo.ArgumentNames.Count; i++) {

attributes[i] = binder.CallInfo.ArgumentNames[i];

}

//拷贝所有属性值到数组:

string[] values = new string[args.Length - 1];

for (int i = 1; i < args.Length; ++i)

{

values[i - 1] = args[i].ToString();

}

XElement subelement = new XElement(args[0].ToString());

//属性名称获取

for (int i = 0; i < attributes.Length; ++i)

{

subelement.SetAttributeValue(attributes[i], values[i]);

}

result = subelement;

}

return result != null;

}

}

该函数功能是:输出任意同时带有属性的节点,同时可以嵌套——比如:

dynamic xmlCreator = new XmlCreator();

XElement ele = xmlCreator.CreateElement(“Books”,

xmlCreator(“Book”,name:C#,price:100.50)

);

介绍VS2010的新特性等一些新功能

(一)关于“权限验证”的基础知识:

通常我们注意到有这样一个现象:在某些论坛中我们可以查看别人的帖子,但是如果你点击了回复,不是跳转到Reply类似的回复页面而是跳转到了Login.aspx页面。如果您不知道中还存在着这么一个可以方便检测是否是匿名用户登录的功能,通常你会选择Session去记录,简略的代码往往是这样:

o 先写一个类,直接继承于System.Web.UI.Page,然后这样Coding: Public partial class RegisterRequired : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

if (Session[“username”]==null)

{

Response.Redirect(“Login.aspx?FromUrl=” +

Request.Url.AbsoluteUri

”);

}

}

}

o 你可以在需要验证的页面让它直接继承RegisterRequired,在Login页面自己写

一些方法去验证,然后直接:Response.Redirect(Request[“FromUrl”])即可。 这样做当然是毫无问题,但是需要反复指定重定向之类的功能,比较啰嗦。相对而言,微软已经抽象出了一个类,叫做FormsAuthentication,专门用于发布凭据(验证当前客户是否是匿名还是已经登录的)。该类使用Cookie进行验证。

1)首先需要在Web.config中的<system.web>节点进行配置:

<authentication Mode=”Forms”>

<forms Name=”Cookie的Name” Timeout=”60”

Cookieless=”AutoDetect” slidingExpiration=”true” loginUrl=”Login.aspx” /> <authentication>

我们通常只要这样设置就可以了,现在来逐一解释以下各个主要参数的妙用:

o

o Name:Cookie的名字,您可以自定义。 Timeout:指定Cookie过期的时间,超过了这段时间Cookie作废(意味着你如

果点击了某个页面之后间隔Timeout的时间段,再次重定向到某个不允许匿名用户的页面,会重新回到loginUrl指定的页面要求重新登录,单位:秒)。

介绍VS2010的新特性等一些新功能

o Cookieless:Cookie的模式(建议使用自动检测,防止某些用户禁用了Cookie;

此时,Cookie将作为字符串嵌入到当前的地址栏中去)。

o slidingExpiration:设置Cookie过期的模式: 如果是true,意味着只要用户浏览页面,Cookie的时间将自动在上一次的时间基础上再度重新开始计时(比如你定义30,那么当第29秒的时候你在登录的情况下重定向到某个页面了,此时Cookie又从0秒开始计时,到下一个30秒一次也不操作才算作废)。 如果是false,则只能在规定的秒内浏览,超过自动重定向(建议true)。

o loginUrl:如果没有Cookie(匿名用户)访问不允许匿名用户访问的页面时候,自

动重定向到登录页面,强制用户登录获取Cookie。 2)在完成这些操作以后,在<system.web>节点中您还要指定哪些页面(不)允许匿名访问:

<authorization>

<allow users=”*”/>

<deny users=”?”/>

</authorization> 这种定义表示:在web.config定义的同级目录下的所有页面都必须在获取凭据

(Cookie)之后才允许您访问(“*”表示任何用户,“?”表示匿名用户),不然直接重定向到登录页面。

通常情况下,一般Default.aspx页面是允许别人进行访问的(放置于web程序的根目录下,其余的根据需求放在不同的文件夹里)。如果要设置“匿名用户除了可以直接访问Default.aspx,其余什么都不可以”的情况下,这样做比较符合:

o

o 和Default.aspx同目录下的那个web.config中不要写(<authorization>。 在需要验证的文件夹中创建一个web.config,然后写上ii)的“<authorization> ”部分即可。

如果你喜欢将不同文件夹的不同页面配置全部写在根目录的那个web.config中,您可以这样做:

<location path=”页面或者是文件夹”>

<system.web>

<authorization>

<allow users=”*”/>

<deny users=”?”/>

</authorization>

</system.web>

</location>

3)完成了以上的步骤之后,您只要在某个页面(比如登录页面,发放Cookie凭据给当前用户就可以了):

介绍VS2010的新特性等一些新功能

if (判断登录条件)

……

FormsAuthentication.SetAuthCookie (凭据名称, 是否跨浏览器支持) if (string.IsNullOrEmpty(Request[“ReturnUrl”]))

{

Response.Redirect (“默认页面”);

}

Response.Redirect (Request[“ReturnUrl”]);

这里解释一下: “凭据名称”:应该是唯一的用户非匿名的凭据名(最好是Id或者是之后所讲的

MemberShip中的UserName,这样你可以直接从获取这个Id,以便后续的操作)

o “是否跨浏览器支持”:用于指定是否对于不同浏览器进程之间享用同一个o

Cookie。

o ReturnUrl:是某个不允许匿名用户访问的页面路径,当使用

FormsAuthentication.SetAuthCookie给当前用户分发凭据的完毕之后,判断ReturnUrl是否为空(不为空直接跳转到那个页面,否则人为指定一个页面)。 当然,如果ReturnUrl肯定不为空,您直接可以这样做:

FormsAuthentication.RedirectFromLoginPage(凭据名称, 是否跨浏览器支持) 。

除了登录之外,“注销”(SignOut也是一个非常实用的功能),实际上你可以自己在页面中这样做:FormsAuthentication.SignOut();

(二)中的Login控件:

中实际上已经为你现成提供了一个成为Login的控件,这样你就不必总是去自定义登录控件了。您可以在Login_Authenticate中写验证代码:

protected void Login1_Authenticate(object sender,

AuthenticateEventArgs e)

{

//判断条件

e.Authentication = true; //必须加入,相当于是SetAuthCookie的功能,可以直接在获取UserTextBox中的Name。 Response.Redirect(…..);

}

或者你直接指定Login1的DestinationPageUrl属性,这样一旦成功登录后就自动跳转到该属性指定的页面了。

介绍VS2010的新特性等一些新功能

(三)中登出控件:

我们通常看到的登录页面往往是:如果匿名用户访问登录页面,应该出现登录的提示框(输入用户名、口令等信息),当成功登录之后该提示框出现“欢迎XXX登录”的字样,然后底下有一个按钮,

允许你注销。这个我们完全可以不写任何代码,直接通过拖拽控件实现: 除了刚才讲的登录控件之外,我们还需要三个控件:LoginView、LoginStatus和LoginName。

LoginView不直接使用,它包含两个模板:AnoymousTemplate和

LoggedTemplate(RoleGroupTemplate稍后在角色部分讲到)。我们通常把Login控件放在AnoymousTemplate中,在LoggedTemplate中放上一个LoginName(自动显示登录以后的名字),同时放上一个LoginStatus控件(默认LogOut状态),当用户点击之后再次成为匿名用户。

(一)“事件”与“委托”:

在“NET基本探究系列”委托的部分我们已经知道了委托的出现为不同类之间动态调用同参、同返回值的函数提供了便利。现在我们来进一步研究委托在实际开发中的例子——事件(event)。

我们只要是用NET开发的就一直不断在接触事件——举个最简单的例子就是你在WinForm上拉一个Button,然后双击就出现一个“事件函数”。这个“事件函数”的本质是和“事件”挂钩的。如果你查看InitializeComponent()这部分的代码,你会发现有这样一行东西:

private void InitializeComponent()

{

this.button1 = new System.Windows.Forms.Button();

this.SuspendLayout();

……

this.button1.Click += new

System.EventHandler(this.button1_Click);

注意上述代码中黑色的部分:其中Click是一个Button的事件,而“+=”(VB中是AddressOf)类似于为Click事件指明触发函数(在本文件的button1_Click)。 如果你右键查看Click的定义,你会发现Button类的Click事件的定义:

public event EventHandler Click;

而如果你再“刨根问底”下去,会发现Click的类型是EventHandler,而这恰恰又是一个委托。正是因为“委托”具备不同类之间允许动态调用同参同返回值函数的功能,所以你在一个Form类上引入Button类(拖拽一个Button到上面),双击Button之后在Form类自身生成一个与Button事件委托同参同返回值的私有函数,同时把这个函数和事件使用

“+=”的形式挂接起来,完成了事件函数的初始化操作。至此,我们可以得出这样一个结论:

介绍VS2010的新特性等一些新功能

“事件”和“委托”是密不可分的——“事件”恰恰利用了委托的“动态函数”特性使得你可以在不同类之间调用同参同返回值的函数,以便达到“触发”的效果。同时,我们也知道了一般事件定义方法:public event 委托类型名 事件名;——注意,委托类型必须是无返回值的,即void类型。

我们可以把“事件”的初始化和“委托”的初始化加以对比,增强我们对于它们之间关系理解的记忆:

委托: EventHander eh = new EventHander (同参同返回类型函数名)

事件:XXX.Click += new 事件委托名 (同参同返回类型函数名)

请特别注意我放大的这个部分:既然事件的本质是委托,为何不像委托一样使用等于符号,而偏偏使用“+=”进行初始化操作呢?这里就牵涉到了“隐式事件声明”的概念:一般地,任何在NET中的事件都是一个事件列表,通过已经重载的“+=”对事件进行批量的增加或者删除操作。为了解释清楚这个概念,现在我们“+=”代码展开让读者看看,NET究竟背后“秘密地”干了些什么——

(二)显示声明的“事件”:

我们注意到一个非常有意思的现象——如果你把一个类的事件(比如Button)同时指向(绑定到一个以上)的事件函数中的时候:

private void InitializeComponent()

{

this.button1 = new System.Windows.Forms.Button();

this.SuspendLayout();

……

this.button1.Click += new

System.EventHandler(this.button1_Click);

this.button1.Click += new

System.EventHandler(this.button2_Click); //自己对应添加一个Button方法

此时,如果你在button1_click中写上了弹出显示“1”的msgbox,然后在

button2_click同时写上弹出“2”的对话框,你会“惊讶地”发现不是仅仅弹出2,而是“先1后2”!——Why?难道“事件”天性有记忆力?

如果我们回顾“多路委托”的概念的时候,知道一个委托可以加载多个同参同返回值的委托函数;同样地,一个事件也可以像委托一样进行“多路事件”的加载,只是微软在每一个事件背后本质上做了这样一些东西(拿Button作例子):

public class Button

{

private List<EventHandler> clickevents = new

List<EventHandler>();

public event EventHandler Click

介绍VS2010的新特性等一些新功能

add

{

clickevents.Add(value);

}

remove

{

clickevents.Remove(value);

}

public void OnClick()

{

foreach (var item in clickevents) {

item(this,null); //此处自行调用事件,表明到OnClick函数被执行后触发相应挂接的事件函数。

}

}

}

这个定义看上去很像属性,但是和属性不一样的在于“事件属性”多出了两个特殊的关键词“add”和“remove”,其本质是对“+=”和“-=”的重载定义:即用“+=”挂接到一个新函数时候,自动委派执行add中的Add方法,把真正执行的函数通过value传递并且添加到clickevents列表中。反之,使用“-=”,会自动执行remove,去掉已经定义的事件函数,从事件列表中移除。这样,当你点击了一个Button的时候,系统自然调用OnClick类似的函数,然后从事件列表中依次执行已经添加的事件函数。事件的“记忆性”就是这样形成的。

实际上,你完全可以按照这种方式,自行添加一个事件列表去有选择地控制事件的执行。不信你可以试一试。

在“权限登录”一篇中我们介绍了如何使用微软最新的权限特性,通过在配置文件中声明简单的权限可以达到控制登录用户和匿名用户的目的,同时还教会大家如何使用Login控件的Authentication事件进行登录。但是,事件中的验证代码还是需要你来完成的。作为快速开发的VS IDE,微软有没有为我们提供更简单的方法呢?答案是“有的”。今天就给出一个完整的,几乎不用编写任何代码的简单登录、注册和密码遗忘的功能的页面。同时为了对比,

介绍VS2010的新特性等一些新功能

首先创建一个WebSite项目(必须是WebSite,否则无法正常生成数据和使用内置函数!),然后创建以下3个页面:Default.aspx(一个Login控件),Register.aspx(一个CreateUserWizard控件)和一个ForgetPassword.aspx(一个密码遗忘控件:PasswordRecovery)。把Default设置成默认起始页,然后添加以下代码到web.config: <location path="Default2.aspx">

<system.web>

<authorization>

<allow users="*"/>

</authorization>

</system.web>

</location>

<location path="ForgetPassword.aspx">

<system.web>

<authorization>

<allow users="*"/>

</authorization>

</system.web>

</location>

<system.web>

<compilation debug="true" targetFramework="4.0"/>

<authentication mode="Forms">

<forms path="/" timeout="60" slidingExpiration="true"

loginUrl="Default.aspx"/>

</authentication>

<authorization>

<allow users="*"/>

<deny users="?"/>

</authorization>

同时,将您的Login控件转成一个Template的模式(允许自定义样式,比如添加一些其它控件等),添加一个“Register”和“Forget Password”LinkButton,指定其

PostBackUrl,以便使其点击成功跳转到注册和密码遗忘的页面:

<asp:Login runat="server"

onauthenticate="Login1_Authenticate">

介绍VS2010的新特性等一些新功能

<LayoutTemplate>

<table cellpadding="1" cellspacing="0"

style="border-collapse:collapse;">

<tr>

<td>

<table cellpadding="0">

<tr>

<td align="center" colspan="2">

Log In</td>

</tr>

<tr>

<td align="right">

<asp:Label runat="server"

AssociatedControlID="UserName">User Name:</asp:Label>

</td>

<td>

<asp:TextBox runat="server"></asp:TextBox> <asp:RequiredFieldValidator runat="server" ControlToValidate="UserName"

ErrorMessage="User Name is required."

ToolTip="User Name is required."

ValidationGroup="Login1">*</asp:RequiredFieldValidator>

</td>

</tr>

<tr>

<td align="right">

<asp:Label runat="server"

AssociatedControlID="Password">Password:</asp:Label>

</td>

<td>

<asp:TextBox runat="server"

TextMode="Password"></asp:TextBox>

<asp:RequiredFieldValidator runat="server" ControlToValidate="Password"

ErrorMessage="Password is required."

ToolTip="Password is required."

ValidationGroup="Login1">*</asp:RequiredFieldValidator>

介绍VS2010的新特性等一些新功能

</td>

</tr>

<tr>

<td colspan="2">

<asp:CheckBox runat="server" Text="Remember me next time." />

</td>

</tr>

<tr>

<td align="center" colspan="2" style="color:Red;"> <asp:Literal runat="server"

EnableViewState="False"></asp:Literal>

</td>

</tr>

<tr>

<td align="left">

<asp:Button runat="server"

CommandName="Login" Text="Log In"

ValidationGroup="Login1" />

</td>

<td align="right">

<asp:LinkButton runat="server"

PostBackUrl="~/Register.aspx">Register Now</asp:LinkButton>

&nbsp;&nbsp;

<asp:LinkButton runat="server"

PostBackUrl="~/ForgetPassword.aspx">Forget Password</asp:LinkButton>

</td>

</tr>

</table>

</td>

</tr>

</table>

</LayoutTemplate>

</asp:Login>

介绍VS2010的新特性等一些新功能

在Register中放上一个CreateUserWizard控件。在ForgetPassword页面放上一个PasswordRecovery控件。同时在web.config中添加以下配置(位于<system.web>节点外):

<>

<mailSettings>

<smtp from="发送方地址xxx@" deliveryMethod="Network"> <network host="smtp服务器地址(smtp.xx.xx)" userName="登录用户名" password="口令" defaultCredentials="服务器要求验证" />

</smtp>

</mailSettings>

</>

同时对于PasswordRecovery控件中作如下定义:

<asp:PasswordRecovery runat="server" >

<MailDefinition Subject="邮件主题" />

</asp:PasswordRecovery>

基本上这样,一个非常简单的注册功能的多页面“项目”就完成了。简单吧。下面我们分析它的原理:

1、首先看Default.aspx页面部分:如果是WebSite工程,当放入一个Login的控件的时候,它会在根目录的一个叫“APP_DATA”的特别文件夹里(如果看不到请右键项目,选择添加文件夹,选择即可)创建一个ASPNETDB.MDF数据库文件。并且创建了相关的表以便后续的用户注册等操作。

2、在Register.aspx页面中存在一个CreateuserWizard控件,该控件一旦当用户输入用户名等注册信息之后,自动触发CreatingUser事件,同时调用

Membership.CreateUser内置类把信息逐一写入那个自动生成的数据库中,其函数声明如下:

public static MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool

isApproved, out MembershipCreateStatus status);

其它的参数可以通过英文名字就逐一得知,我就不罗嗦了;这里需要注意两个特别的参数:“isApproved”是表示是否将该用户停用;“MembershipCreateStatus”是一个枚举,用于报告创建的情况(如果成功,将会是Success)。您完全可以在

“CreateUserWizard1_CreateUserError”事件中通过“e.CreateUserError”来获取失败的信息,做特殊处理。

本文来源:https://www.bwwdw.com/article/p2n4.html

Top