C# Corner

C# 5.0: More Than Just Async

Much emphasis has been placed on the new async capabilities in C# 5.0. However, there are smaller -- but still useful -- features of which you may not be aware.

In my past two columns, I've covered a few of the features of the new async capabilities in the next version of C#. Async is an important feature and deserves the coverage it's getting. However, there are a few other neat little things Microsoft has added to C# that will help make your code cleaner and easier to maintain.

Caller Information
In the early days of the Microsoft .NET Framework (before log4net and other diagnostic and logging tools), I'd write logging calls that would include the name of the method currently executing. This made tracking down bugs in production much easier, as I'd know exactly where the problem was occurring:

private void Log(string message, string methodName)
{
  Console.WriteLine("[{2} -- {0}] - {1}", methodName, message, DateTime.Now);
}

Using this method, shown in Listing 1, was simple.

But there were a few obvious problems with this method:

  1. When doing copy/paste of some calls to Log, I'd forget to change the method name. This would sometimes lead me on a wild-goose chase inside my code.
  2. Refactoring of code meant I had to remember to also rename the parameters being sent to my Log method.

In time, I learned about using the StackFrame object. This allowed me to "examine" the stack frames of the call stack. Inside my Log method I could back up one level in the stack to find my caller:

private static void Log(string message)
{
  var sf = new StackFrame(1);    // Back up 1 level from the current method
  var methodName = sf.GetMethod().Name;
  Console.WriteLine("[{2} -- {0}] - {1}", methodName, message, DateTime.Now);
} 

This alleviated the previous two issues, and also gave the added benefit of being able to get source file and line number information (as long as I remembered to pass "true" as the second parameter of the StackFrame constructor):

private static void Log(string message)
{
  var sf = new StackFrame(1, true);    // Back up 1 level from the current method
  var methodName = sf.GetMethod().Name;
  var sourceFile = sf.GetFileName();
  var lineNumber = sf.GetFileLineNumber();
  Console.WriteLine("[{2} -- {0}] - {1} ({3}:{4})",      
     methodName, message, DateTime.Now, sourceFile, lineNumber);
}

But I still didn't like that I had to create a StackFrame and ask the framework to back up and give me the method name. While this probably wasn't an expensive operation, it wasn't free. All the while, my C++ friends were snickering at me because they'd had the __FILE__ macro (providing the name of the current source file at compile time) and the __LINE__ macro (providing the name of the current line number at compile time) for years. With C# 5.0, Microsoft has added not only filename and line number features into the compiler, but caller name information as well.

Now my logging uses a combination of optional parameters decorated with specific attributes. When the C# 5.0 compiler sees these attributes, it replaces them with debugging information. The attributes are:

  • CallerFilePathAttribute: Get the full path of the source file for the caller.
  • CallerLineNumberAttribute: Get the line number in the source file at which the method is called.
  • CallerMemberNameAttribute: Get the method name of the caller.

Here's how I use them:

private static void Log(string message, 
  [CallerMemberName] string methodName = null, 
  [CallerFilePath] string sourceFile = null,
  [CallerLineNumber] int lineNumber = 0)
{
  Console.WriteLine("[{2} -- {0}] - {1} ({3}:{4})", 
    methodName, message, DateTime.Now,   
    sourceFile, lineNumber);
}

This gives me the same features of the C++ macros, but without the StackFrame lookup.

Windows Presentation Foundation and Silverlight developers who implement INotifyPropertyChanged will also like the fact that they no longer need to hardcode the property name. By using the CallerMemberName, you can let the compiler fill in the calling property that raised the PropertyChanged event, as shown in Listing 2.

Notice how I don't pass "EmployeeCount" to the NotifyPropertyChanged method. The compiler will fill in the information for me, so it will be just as if I'd hardcoded it myself.

Breaking Changes
C# 5.0 actually contains three breaking changes. These are somewhat rare cases and, chances are, if you've run into these previously you've already fixed your code. However, if you're not aware of these issues, these changes could cause your existing code to behave differently.

The first breaking change involves foreach loops and lambda expressions. In previous versions of the C# compiler, any lambda expression that used the iteration variable within the expression (a "nested lambda expression") would capture the last value of the iteration variable, not each value through the loop.

Here's a simple example. I use a foreach loop to build up a list of lambda expressions that will return the iteration value doubled. The loop goes from 0 to 9, so you would expect the result to be 0, 2, 4, 6, 8, 10, 12, 14, 16, 18:

var computes = new List <Func <int > >();
foreach(var i in Enumerable.Range(0,10))
{
  computes.Add(() = > i * 2);
}

foreach (var func in computes)
{
  Console.WriteLine(func());
}

Run this code before C# 5.0 and you'll actually get the number 18 printed 10 times. A reference to the loop variable is captured (instead of the value, as you might expect). The result is that when I loop through and call each lambda to double the value, it prints 18 because the last value of "i" in the Enumerable.Range was 9. In C# 5.0, you'll now see values 0, 2, 4, 6, 8, 10, 12, 14, 16, 18.

The second breaking change is similar to the first one, but involves LINQ expressions. In this example, I have the integer values 1 through 4, along with their string representations. I loop through each number and grab the string representation based on matching up the ToString result of the number. You'd expect the result to be the string values 1, 2, 3 and 4, as shown in Listing 3.

But, as before, running this code prior to C# 5.0 produces the value "4" repeated four times. Again, the reference to the iteration variable is captured in the LINQ expression. With C# 5.0, Microsoft has corrected this so the value of the iteration value is captured, and the results are predictable.

Finally, Microsoft fixed the evaluation of named and positional arguments to occur in the correct order. In versions of C# prior to 5.0, the code in Listing 4 produces interesting results.

Because I'm calling the ReturnValue method in a specific order -- "1, 2, 3" in the first case and then "1, 3, 2" in the second -- I'd expect the output to be 1, 2, 3, 1, 3, 2. Instead, I get this: 2, 3, 1, 3, 2, 1.

It looks like the C# compiler is evaluating the named arguments first, and then the positional arguments. In this trivial example, it's not an issue. But suppose that the ReturnValue method did something meaningful and I needed the code executed in the order I wrote the calls? This behavior would definitely cause a problem. In C# 5.0, the compiler will now evaluate the method calls in a predictable, left-to-right order as they appear in the calling code.

C# 5.0 is an exciting improvement to the C# language. Take advan­- tage of all it has to offer and your code will benefit in many ways.

About the Author

Patrick Steele is a Senior .NET Developer with Billhighway in Troy, Michigan. A recognized expert on the .NET Framework, he is a Microsoft MVP award winner and a presenter at conferences and user group meetings.

comments powered by Disqus

Reader Comments:

Tue, Mar 5, 2013

"Run this code before C# 5.0 and you'll actually get the number 18 printed 10 times". I did try all previous framework and they all give the proper result except 2.0 and 3.0 since Enumerable isn't available in these

Tue, Mar 5, 2013

You have a space between = and > there should be none

Fri, Jan 4, 2013 MikeT UK

I wonder if that 2nd breaking change explains a recurrent issue i have been having where code optimisation strips loops from the code, i have 3-4 projects that i have to disable optimisation to get it to produce the expected results. and when reporting the issue to microsoft they claimed it was impossible

Add Your Comments Now:

Your Name:(optional)
Your Email:(optional)
Your Location:(optional)
Comment:
Please type the letters/numbers you see above

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.