Saturday, 28 July 2012

What else is new Featuresin C# 5.0 ?

Introduction of New Features in C# 5.0



Please click on the C# 5.0 video to learn more about the C#5.0 new features

1. C# Evolution Matrix

Microsoft just published a new version of C# : 5.0 beta with CLR version 4.5 (Visual Studio 11 beta).
In order to get a big picture of the whole evolution of C# language, I summarized all the key features
into a C# Evolution Matrix for your reference as below diagram shows:



In C# version 5.0, there are two key features: Async Programming and Caller Information.

csharpasync1.JPG




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 bit too 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 is i => 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



In C# version 5.0, there are two key features: Async Programming and Caller Information.

2. Async Feature
Two new key words are used for Async feature: async modifier and await operator. Method marked
with async modifier is called async method. This new feature will help us a lot in async programming.
For example, in the programming of Winform, the UI thread will be blocked while we use
HttpWebRequest synchronously request any resource in the Internet. From the perspective of user
experience, we cannot interact with the form before the request is done.
private void
btnTest_Click(object sender, EventArgs e)

{

var request = WebRequest.Create(txtUrl.Text.Trim());

var content=new MemoryStream();

using (var response = request.GetResponse())

{

using (var responseStream = response.GetResponseStream())

{

responseStream.CopyTo(content);

}

}

txtResult.Text = content.Length.ToString();

}



In the above example, after we clicked the Test button, we cannot not make any change to the form
before the txtResult textbox shows the result.
In the past, we can also use BeginGetResponse method to send async request as this sample in MSDN
shows:

http://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest.begingetresponse(v=vs.80).aspx. But it
will takes us a lot effort to realize it.
Now, we can simply use below code to do request asynchronously :
private async void
btnTest_Click(object sender, EventArgs e)

{

var request = WebRequest.Create(txtUrl.Text.Trim());

var content = new MemoryStream();

Task<WebResponse> responseTask = request.GetResponseAsync();

using (var response = await responseTask)

{
using (var
responseStream = response.GetResponseStream())

{

Task copyTask = responseStream.CopyToAsync(content);

//await operator to supends the excution of the method until the task is completed. In the meantime,
the control is returned the UI thread.

await copyTask;

}

}

txtResult.Text = content.Length.ToString();

}
The await operator is applied to the returned task. The await operator suspends execution of the
method until the task is completed. Meanwhile, control is returned to the caller of the suspended
method.


3. Caller Information
Caller Information can help us in tracing, debugging and creating diagnose tools. It will help us
to avoid duplicate codes which are generally invoked in many methods for same purpose, such
as logging and tracing.
We could get the below information of caller method :
Below example are a common practice prior to the new feature of Caller Information:
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;
namespace
ConsoleApplicationTest

{

class Program

{

static void Main(string[] args)

{

InsertLog("Main");

MethodB();

Console.ReadLine();

}
static void MethodA()


{

InsertLog("MethodA");

MethodB();

}
static void MethodB()


{ }
static void
InsertLog(string methodName)

{

Console.WriteLine("{0} called method B at {1}", methodName,
DateTime.Now);

}

}

}
In both Main and MethodA methods, method InsertLog is invoked for logging. Now we can change the
codes to be as per below lines:
using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.CompilerServices;

using System.Text;

using System.Threading.Tasks;
namespace
ConsoleApplicationTest

{

class Program

{

static void Main(string[] args)

{

//InsertLog("Main");

MethodB();

Console.ReadLine();

}
static void MethodA()


{

//InsertLog("MethodA");

MethodB();

}
static void MethodB(

[CallerMemberName] string memberName = "",

[CallerFilePath] string sourceFilePath = "",

[CallerLineNumber] int sourceLineNumber = 0)

{

InsertLog(memberName);

}
static void
InsertLog(string methodName)

{

Console.WriteLine("{0} called method B at {1}", methodName,
DateTime.Now);

}

}

}

.
Post a Comment