C# 5.0随着VisualStudio 2012一起正式发布了,让我们来看看C#5.0中增加了哪些功能。

1. 异步编程

在.Net 4.5中,通过async和await两个关键字,引入了一种新的基于任务的异步编程模型(TAP)。在这种方式下,可以通过类似同步方式编写异步代码,极大简化了异步编程模型。如下式一个简单的实例:

    static async void DownloadStringAsync2(Uri uri)
{
var webClient = new WebClient();
var result = await webClient.DownloadStringTaskAsync(uri);
Console.WriteLine(result);
}

而之前的方式是这样的:

    static void DownloadStringAsync(Uri uri)
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += (s, e) =>
{
Console.WriteLine(e.Result);
};
webClient.DownloadStringAsync(uri);
}

也许前面这个例子不足以体现async和await带来的优越性,下面这个例子就明显多了:

  public void CopyToAsyncTheHardWay(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
Action<IAsyncResult> readWriteLoop = null;
readWriteLoop = iar =>
{
for (bool isRead = (iar == null); ; isRead = !isRead)
{
switch (isRead)
{
case true:
iar = source.BeginRead(buffer, , buffer.Length,
readResult =>
{
if (readResult.CompletedSynchronously) return;
readWriteLoop(readResult);
}, null);
if (!iar.CompletedSynchronously) return;
break;
case false:
int numRead = source.EndRead(iar);
if (numRead == )
{
return;
}
iar = destination.BeginWrite(buffer, , numRead,
writeResult =>
{
if (writeResult.CompletedSynchronously) return;
destination.EndWrite(writeResult);
readWriteLoop(null);
}, null);
if (!iar.CompletedSynchronously) return;
destination.EndWrite(iar);
break;
}
}
};
readWriteLoop(null);
} public async Task CopyToAsync(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = await source.ReadAsync(buffer, , buffer.Length)) != )
{
await destination.WriteAsync(buffer, , numRead);
}
}

关于基于任务的异步编程模型需要介绍的地方还比较多,不是一两句能说完的,有空的话后面再专门写篇文章来详细介绍下。另外也可参看微软的官方网站:Visual Studio Asynchronous Programming,其官方文档Task-Based Asynchronous Pattern Overview介绍的非常详细, VisualStudio中自带的CSharp Language Specification中也有一些说明。

2. 调用方信息

很多时候,我们需要在运行过程中记录一些调测的日志信息,如下所示:

    public void DoProcessing()
{
TraceMessage("Something happened.");
}

为了调测方便,除了事件信息外,我们往往还需要知道发生该事件的代码位置以及调用栈信息。在C++中,我们可以通过定义一个宏,然后再宏中通过__FILE__和__LINE__来获取当前代码的位置,但C#并不支持宏,往往只能通过StackTrace来实现这一功能,但StackTrace却有不是很靠谱,常常获取不了我们所要的结果。

针对这个问题,在.Net 4.5中引入了三个Attribute:CallerMemberName、CallerFilePath和CallerLineNumber。在编译器的配合下,分别可以获取到调用函数(准确讲应该是成员)名称,调用文件及调用行号。上面的TraceMessage函数可以实现如下:

  public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = )
{
Trace.WriteLine("message: " + message);
Trace.WriteLine("member name: " + memberName);
Trace.WriteLine("source file path: " + sourceFilePath);
Trace.WriteLine("source line number: " + sourceLineNumber);
}

另外,在构造函数,析构函数、属性等特殊的地方调用CallerMemberName属性所标记的函数时,获取的值有所不同,其取值如下表所示:

调用的地方

CallerMemberName获取的结果

方法、属性或事件

方法,属性或事件的名称

构造函数

字符串 ".ctor"

静态构造函数

字符串 ".cctor"

析构函数

该字符串 "Finalize"

用户定义的运算符或转换

生成的名称成员,例如, "op_Addition"。

特性构造函数

特性所应用的成员的名称

例如,对于在属性中调用CallerMemberName所标记的函数即可获取属性名称,通过这种方式可以简化 INotifyPropertyChanged 接口的实现。关于调用方信息更详细的资料,请参看MSDN:http://msdn.microsoft.com/zh-cn/library/hh534540.aspx

方法调用信息

这是一个被写在Writting Enterprisey Code上的完整风格指南,但是其中我最喜欢的是迷人对你调用过的所有函数的日志记录:

Function AddTwoNumbers(a As Integer, b As Integer) As Integer
Logger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Entering AddTwoNumbers")
Dim result = OracleHelpers.ExecInteger("SELECT " & a & " + " & b)
Logger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Calling PrintPurchaseOrders")
PrintPurchaseOrders() ' IFT 12.11.96: don't know why this is needed but shipping module crashes if it is removed
Logger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Returned from PrintPurchaseOrders")
Logger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Exiting AddTwoNumbers")
Return result
End Function

即使这段代码用企业级标准编写得有效而清晰,而使用C# 5可以使它更加高效和清晰。C# 4推荐使用可选的参数,这意味着方法调用者可以不用考虑参数,编译器将会用默认值填充。

public void WonderMethod(int a = , string b = "hello") { ... }

WonderMethod();  // compiles to WonderMethod(456, "hello")
WonderMethod(); // compiles to WonderMethod(123, "hello")

有了C# 5,你可以将一个特殊属性放置在可选参数上,编译器将会使用调用方法的信息填充变量而不是使用某个常量。这意味着我们能够实现Logger.Trace,来自动收集它是从哪里调用的信息:

public static void Trace(string message, [CallerFilePath] string sourceFile = "", [CallerMemberName] string memberName = "") {
string msg = String.Format("{0}: {1}.{2}: {3}",
DateTime.Now.ToString("yyyy-mm-dd HH:MM:ss.fff"), // Lurking 'minutes'/'months' bug introduced during .NET port in 2003 and has not been noticed because nobody ever looks at the log files because they contain too much useless detail
Path.GetFileNameWithoutExtension(sourceFile),
memberName,
message);
LoggingInfrastructure.Log(msg);
}

现在,如果调用LogTrace("some message"),编译器将会不会用空字符串填充而是使用文件以及调用所发生的成员:

// In file Validation.cs
public void ValidateDatabase() {
Log.Trace("Entering method");
// compiles to Log.Trace("Entering method", "Validation.cs", "ValidateDatabase")
Log.Trace("Exiting method");
}

请注意这些你为参数设置的属性必须是可选的,如果不是可选的,C#编译器将需要调用代码主动提供,并且提供的值必须覆盖默认值。

另一个你怎样使用这个的例子便是实现INotifyPropertyChanged,不需要逐字的匹配字符串,表达式:

public class ViewModelBase : INotifyPropertyChanged {
protected void Set<T>(ref T field, T value, [CallerMemberName] string propertyName = "") {
if (!Object.Equals(field, value)) {
field = value;
OnPropertyChanged(propertyName);
}
}
// usual INPC boilerplate
} public class Widget : ViewModelBase {
private int _sprocketSize;
public int SprocketSize {
get { return _sprocketSize; }
set { Set(ref _sprocketSize, value); } // Compiler fills in "SprocketSize" as propertyName
}
}

很值得的是,你也可以使用[CallerLineNumber]得到调用代码的行号,这个也许对诊断方法有用,但是如果你真的需要它,这也许是这段调用代码太过“企业化”的迹象。

在lambdas中使用循环变量

技术上,这个对长期存在的困扰和煎熬的修正,但是使得C#增加了可用性,所以我将会提及它。

自从C# 3以来,编写匿名函数比命名的更见快捷和容易了,匿名函数被广泛地使用鱼LINQ,但是他们也在其他情况下被使用,如你想要在不需要授权的巨大层级类和接口以及可见函数中快速的拥有参数化行为。匿名函数的一个重要特性就是你可以从本地环境中捕获变量,以下是一个示例:

public static IEnumerable<int> GetGreaterThan(IEnumerable<int> source, int n) {
return source.Where(i => i > n);
}

看这里,i=>i>n是一个捕获了n值的匿名函数。例如如果n是17,那么该函数便是 i=>i>17

在C#之前的版本中,如果你编写了一个循环,你不能在lambda中使用循环变量。事实上,它比想象中更糟。你可以在lambda中使用循环变量,但是它将给你一个错误的结果。它会使用循环退出时的玄幻变量值,不是呗捕获时的值。

例如,下面是一个返回“加法器”集合的函数:

public static List<Func<int, int>> GetAdders(params int[] addends) {
var funcs = new List<Func<int, int>>();
foreach (int addend in addends) {
funcs.Add(i => i + addend);
}
return funcs;
}
var adders = GetAdders(, , , , );
foreach (var adder in adders) {
Console.WriteLine(adder());
}

很明显这大错特错!在返回的集合中的每个函数都在捕获5作为加数后结束。这是因为为他们结束在循环变量,加数,然后最终的循环变量值为5。

要想在C# 3和4中使用这些,你需要记住将循环变量拷贝至一个局部变量中,然后用你的lambda覆盖局部变量:

foreach (var addend_ in addends) {
var addend = addend_; // DON'T GO NEAR THE LOOP VARIABLE
funcs.Add(i => i + addend)
}

由于这些函数是被局部变量覆盖而不是用循环变量,这些值现在被保存,你便能获得真确的值。

以此种方式并不是一种模糊的边缘情况,我在我的项目中碰到过很多次。有一个来自某个项目中的更加现实的例子便是构建一个用来过滤的函数,这个函数是构建自被用户指定的约束对象集合。该代码循环处理约束对象,并构建代表子句的函数列表(如 Name Equals "BOB" 变成 r =>r["Name"]=="BOB"),然后将这些函数混合至一个最终的过滤器中,该过滤器运行这所有的子句然后检查他们是不是为真。我第一次运行没有成功因为每隔子句函数以相同的约束对象覆盖--集合中的最后一个。

在C# 5中,这些修复以及你可以覆盖的循环变量能使你获得你期望的结果。

via:mindscapehq.com , OSChina原创编译

C# 5.0中新增特性的更多相关文章

  1. Performance Tuning guide 翻译 || Performance Tuning Guide 11G中新增特性

    CSDN 对格式支持比較弱.能够到http://user.qzone.qq.com/88285879/blog/1399382878 看一致的内容. Performance Tuning Guide  ...

  2. 第30月第11天 Xcode 9.0中新增的API版本检查@available

    1.Xcode 9.0中新增的API版本检查@available https://www.jianshu.com/p/0a94baa6c3dd https://www.jianshu.com/p/b8 ...

  3. hive 0.10 0.11新增特性综述

    我们的hive版本升迁经历了0.7.1 -> 0.8.1 -> 0.9.0,并且线上shark所依赖的hive版本也停留在0.9.0上,在这些版本上有我们自己的bug fix patch和 ...

  4. MySQL5.6中新增特性、不推荐使用的功能以及废弃的功能

    虽然已经使用MySQL5.6版本有一段时间了,但由于没有和之前的版本作过详细比较,所以对于哪些重要的或者不太重要的特性是在新版本中引入的,还有哪些特性已经或者将要从旧版本中移除的并没有一个十分全面的了 ...

  5. C#8.0中新特性之一:结构readonly成员

    结构struct成员支持readonly,用来限制被其修饰的成员不会改变结构的内部状态.加上7.2版本添加的readonly struct和ref readonly方法返回以及之前的字段声明修饰作用, ...

  6. asp.net 2.0中新增的web.config的默认namespace功能 (转)

    看上去这个题目比较长,但实际上,我在看资料时发现,这就是说,在asp.net 2.0中,只需要在web.config里定义你要用的那些namespace,则在aspx页面中就不需要再象1.1那样,用 ...

  7. ASP.Net4.0中新增23项功能

    这篇文章介绍Visual Studio 2010 (ASP.Net 4.0)的新功能. 1.代码片段(Code Snippets): 代码段是预先开发的代码模板,可以节省我们对有关语法思考的时间.在V ...

  8. .net 4.0 中的特性总结(四):Tuple类型

    Tuple是具有指定数量和顺序的值的一种数据结构.针对这种数据结构,.Net4.0中提供了一组Tuple类型,具体如下: Tuple   Tuple<T>   Tuple<T1, T ...

  9. .net 4.0 中的特性总结(一):dynamic

    在新版本的C#中,dynamic关键词是一个很重要的新特性,现在你可以创建动态对象并在运行时再决定它的类型.而且.net 4.0为CLR加入了一组为动态语言服务的运行时环境,称为DLR(Dynamic ...

随机推荐

  1. [转] 在Mac上搭建React Native开发环境

    原文链接: http://blog.csdn.net/xiangzhihong8/article/details/53914336 概述 前面我们介绍过在window环境下开发React Native ...

  2. Linux Glibc库严重安全漏洞修复方案通知(腾讯开发者社区)

    如何查看当前glibc的版本号? rpm -aq | grep glibc 尊敬的用户:       您好!2015年1月28日, 腾讯云安全情报监测到LinuxGlibc库存在一处严重安全漏洞,可以 ...

  3. linux使用酷我在线听音乐

    一般linux系统自带音频播放器只能管理本地音乐,无法在线听歌.在线音乐如百度音乐盒,下载歌曲需要登录,比较麻烦.在github里有一个酷我音乐的开源项目,可以安装在linux系统下.链接地址:htt ...

  4. 分享知识-快乐自己:PageHelper 分页关键基础代码

    关键代码:点我下载样式   详细介绍 //使用分页插件 jQuery("#pagination").pagination(data.total, { items_per_page: ...

  5. POJ 2240 Arbitrage(Floyed-Warshall算法)

    题意:给出n种货币,m种兑换比率(一种货币兑换为另一种货币的比率),判断测试用例中套汇是否可行.(套汇的意思就是在经过一系列的货币兑换之后,是否可以获利.例如:货币i→货币j→货币i,这样兑换后,是否 ...

  6. Apache配置负载均衡-实例

    公司两台服务器都安装了tomcat,配置apache作为负载均衡,当一台服务器出现故障时还能保证业务正常运行. Server1:192.168.1.100 Server2:192.168.1.200 ...

  7. Java企业微信开发_03_自定义菜单

    一.本节要点 1.菜单相关实体类的封装 参考官方文档中的请求包的内容,对菜单相关实体类进行封装. 这里需要格外注意的是,企业微信中请求包的数据是Json字符串格式的,而不是xml格式.关于json序列 ...

  8. jquery 整理笔记(一)

    this:表示当前的上下文对象是一个html对象,可以调用html对象所拥有的属性,方法 $(this),代表的上下文对象是一个jquery的上下文对象,可以调用jquery的方法和属性值. each ...

  9. L102

    Let us make our future now, and let us make our dreams tomorrow's reality.I panted my congratulation ...

  10. BEC listen and translation exercise 37

    You're supposed to do that before 10.30 in the morning, but obviously, if it's an emergency, you can ...