# What else is new in C# 5?

361人阅读 评论(0)

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!

0
0

* 以上用户言论只代表其个人观点，不代表CSDN网站的观点或立场
个人资料
• 访问：452109次
• 积分：7850
• 等级：
• 排名：第2638名
• 原创：314篇
• 转载：225篇
• 译文：12篇
• 评论：29条
最新评论