What else is new in C# 5?

转载 2012年03月27日 11:13:47

The big new feature in C# 5 is asynchronous programming support, which I wrote about last week.  However, the C# folks have also slipped in a couple of smaller features and I thought I’d round things out by mentioning those.

Method caller information

There’s a complete style guide to be written on Writing Enterprisey Code, but one of my favourite “enterprisey” tells, after the use of Visual Basic, is obsessively logging every function you pass through:

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

Although this code is efficient and clear by enterprisey standards, with C# 5 it can be even efficienter and clearer.  C# 4 introduced optional parameters, which meant callers of a method could leave out the arguments and the compiler would fill in the default values:

public void WonderMethod(int a = 123, string b = "hello") { ... }
 
WonderMethod(456);  // compiles to WonderMethod(456, "hello")
WonderMethod();     // compiles to WonderMethod(123, "hello")

With C# 5, you can put a special attribute on an optional parameter and the compiler will fill in the value not with a constant but with information about the calling method.  This means we can implement the Logger.Trace to automagically pick up where it’s being called from:

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);
}

Now, if the caller calls Log.Trace("some message") the compiler will fill in the missing arguments not with the empty string, but with the file and member where the call happens:

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

Notice that the parameters to which you apply the attributes must be optional.  If they aren’t optional, the C# compiler will require the calling code to provide them, and the provided values will override the defaults.

Another example of how you can use this is in implementing INotifyPropertyChanged without needing either literal strings, expression magic or mystic weavers:

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
  }
}

For what it’s worth, you can also get the line number of the calling code using [CallerLineNumber].  This may be useful for diagnostic methods, but if you really need it, that may be a sign that the calling code is just a bittoo enterprisey.

Using loop variables in lambdas

Technically, this is a fix to a long-standing cause of confusion and suffering.  But it makes C# that bit more usable, so I’m going to mention it anyway.

Since C# 3, it’s been quicker and easier to write anonymous functions than named ones, thanks to lambda syntax.  Anonymous functions are widely used in LINQ, but they’re also used in many other cases where you want to quickly parameterise behaviour without investing in some humungous hierarchy of classes and interfaces and virtual functions.  An important feature of anonymous functions is that they can capture variables from their local environment.  Here’s an example:

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

Here, i => i > n is an anonymous function that captures the value of n.  For example, if n is 17, then the function isi => i > 17.

In previous versions of C#, if you wrote a loop, you couldn’t use the loop variable in a lambda.  Actually, it was rather worse than that.  You could use the loop variable in a lambda, but it would give you the wrong results — it would use the value of the loop variable at the time the loop was exited, not at the time the variable was captured.

For example, here’s a function which returns a collection of ‘adder’ functions, one ‘adder’ for each addend in the input:

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;
}

Let’s take it for a spin:

var adders = GetAdders(1, 2, 3, 4, 5);
foreach (var adder in adders) {
  Console.WriteLine(adder(10));
}
 
// Printout: 15 15 15 15 15

Clearly this is horribly wrong!  Every function in the returned collection has ended up capturing 5 as its addend.  This is because they closed over the loop variable, addend, and the final value of the loop variable was 5.

To make this work in C# 3 and 4, you have to remember to copy the loop variable into a local variable (within the scope of the loop), and have your lambda close over the local variable:

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

Because the functions are closing over a local variable rather than the loop variable, the value is now preserved and you get the correct results.

This isn’t an obscure edge case by the way — I’ve come up against it numerous times in my projects.  A more realistic example from one project is building a function to perform filtering.  The function is built up from a collection of Restriction objects specified by the user.  The code loops over the Restriction objects and builds up a list of functions representing the clauses (e.g. Name Equals “BOB” becomesr => r["Name"] == "BOB"), then combines these functions into a final filter function which runs all of the clauses and checks they are all true.  My first pass at this didn’t work because each clause function ended up closing over the same Restriction object — the last one in the collection.

In C# 5, this is fixed and you can close over loop variables and get the results you expect.  If you like to take advantage of C#’s hybrid OO-functional nature, this removes a nasty bear trap that has been causing problems for years.

So that’s it for C# 5.  From a language point of view, there’s not a whole lot of new stuff to learn, though the async and await keywords conceal a great deal of depth.  Happy coding!

相关文章推荐

What Is New in MySQL 5.7之新特性篇

  • 2017年05月31日 00:13
  • 155KB
  • 下载

Nicholas:HDFS:What is New in Hadoop 2

  • 2014年05月29日 14:08
  • 1.54MB
  • 下载

WWDC 2012 Session笔记——200 What is new in Cocoa Touch

这是博主的WWDC2012笔记系列中的一篇,完整的笔记列表可以参看这里。如果您是首次来到本站,也许您会有兴趣通过RSS,或者通过页面下方的邮件订阅的方式订阅本站。 之前写过一篇iOS6 SDK新...

What Is New in MySQL 5.7之新特性篇

MySQL 5.7新增加的特性 以下特性已经被添加到MySQL 5.7: l 安全性改进。 添加这些安全改进: Ø 服务器现在要求“mysql.user”系统表里的账户记录行有一个非空“pl...

What Is New in MySQL 5.7(MySQL 5.7的新特性)

1.4 What Is New in MySQL 5.7 1.4 MySQL 5.7的新特性 This section summarizes what has been added to,...

What is new in Android security (M and N Version) - Google I/O 2016 翻译

截至发博,字幕还在后期中,应该快了吧。 YouTube视频链接:https://www.youtube.com/watch?v=XZzLjllizYs字幕翻译: 1 00:00:01,820 –...
  • mpx_xb
  • mpx_xb
  • 2016年10月09日 16:21
  • 770

What is Action in C#?

C#中Action的简单解释(转的,英文)
  • A12116
  • A12116
  • 2017年07月03日 13:33
  • 163

ruby on rails 2.1 what is new (CN)

  • 2008年10月29日 10:47
  • 2.37MB
  • 下载

Pair Programming What is in it for me.pdf

  • 2011年08月22日 10:35
  • 353KB
  • 下载

C++ Memory Management : What is the difference between malloc/free and new/delete?

http://forums.codeguru.com/showthread.php?t=401848 Q: What is the difference between malloc/fre...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:What else is new in C# 5?
举报原因:
原因补充:

(最多只允许输入30个字)