By Doug Holland (Intel) on March 30, 2009 at 10:02 am Visual Studio 2010 and the .NET Framework 4.0 will soon be in beta and there are some excellent new features that we can all get excited about with this new release. Along with Visual Studio 2010 and the .NET Framework 4.0 we will see version 4.0 of the C# programming language. In this blog post I thought I'd look back over where we have been with the C# programming language and look to where Anders Hejlsberg and the C# team are taking us next.
In 1998 the C# project began with the goal of creating a simple, modern, object-oriented, and type-safe programming language for what has since become known as the .NET platform. Microsoft launched the .NET platform and the C# programming language in the summer of 2000 and since then C# has become one of the most popular programming languages in use today.
With version 2.0 the language evolved to provide support for generics, anonymous methods, iterators, partial types, and nullable types.
When designing version 3.0 of the language the emphasis was to enable LINQ (Language Integrated Query) which required the addiiton of:
- Implictly Typed Local Variables.
- Extension Methods.
- Lambda Expressions.
- Object and Collection Initializers.
- Annonymous types.
- Implicitly Typed Arrays.
- Query Expressions and Expression Trees.
If you're in need of learning about, or distilling knowledge of, any of these language features that I have mentioned so far I would highly recommend that you take a look at the C# Programming Language by Anders Hejlsberg, Mads Torgersen, Scott Wiltamuth, and Peter Golde.
What is excellent about this edition is that it contains annotations from many other Microsoft employees and Visual C# MVP's giving you additional perspectives on the language and its capabilities. Another excellent book is Essential LINQ by Charlie Calvert and Dinesh Kulkarni.
In the past programming languages were designed with a particular paradigm in mind and as such we have languages that were, as an example, designed to be either object-oriented or functional. Today however, languages are being designed with several paradigms in mind. In version 3.0 the C# programming language acquired several capabilities normally associated with functional programming to enable Language Integrated Query (LINQ).
In version 4.0 the C# programming language continues to evolve, although this time the C# team were inspired by dynamic languages such as Perl, Python, and Ruby. The reality is that there are advantages and disadvantages to both dynamically and statically typed languages.
Another paradigm that is driving language design and innovation is concurrency and that is a paradigm that has certainly influenced the development of Visual Studio 2010 and the .NET Framework 4.0. See the MSDN Parallel Computing development center for more information about those changes. I'll also be blogging more about Visual Studio 2010 and the .NET Framework 4.0 in the next few weeks.
Essentially the C# 4.0 language innovations include:
- Dynamically Typed Objects.
- Optional and Named Parameters.
- Improved COM Interoperability.
- Safe Co- and Contra-variance.
Enough talking already let's look at some code written in C# 4.0 using these language innovations...
In C# today you might have code such as the following that gets an instance of a statically typed .NET class and then calls the Add
method on that class to get the sum of two integers:
Calculator calc = GetCalculator();
int sum = calc.Add(10, 20);
Our code gets all the more interesting if the Calculator
class is not statically typed but rather is written in COM, Ruby, Python, or even JavaScript. Even if we knew that the Calculator class is a .NET object but we don't know specifically which type it is then we would have to use reflection to discover attributes about the type at runtime and then dynamically invoke the Add
method.
object calc = GetCalculator();
Type type = calc.GetType();
object result = type.InvokeMember("Add",
BindingFlags.InvokeMethod, null,
new object[] { 10, 20 });
int sum = Convert.ToInt32(result);
If the Calculator class was written in JavaScript then our code would look somewhat like the following.
ScriptObect calc = GetCalculator();
object result = calc.InvokeMember("Add", 10, 20);
int sum = Convert.ToInt32(result);
With the C# 4.0 we would simply write the following code:
dynamic calc = GetCalculator();
int result = calc.Add(10, 20);
In the above example we are declaring a variable, calc
, whose static type is dynamic. Yes, you read that correctly, we've statically typed our object to be dynamic. We'll then be using dynamic method invocation to call the Add
method and then dynamic conversion to convert the result of the dynamic invocation to a statically typed integer.
You're still encouraged to use static typing wherever possible because of the benefits that statically typed languages afford us. Using C# 4.0 however, it should be less painful on those occassions when you have to interact with dynamically typed objects.
Another major benefit of using C# 4.0 is that the language now supports optional and named parameters and so we'll now take a look at how this feature will change the way you design and write your code.
One design pattern you'll often see as that a particular method is overloaded because the method needs to be called with a variable number of parameters.
Let's assume that we have the following OpenTextFile
method along with three overloads of the method with different signatures. Overloads of the primary method then call the primary method passing default values in place of those parameters for which a value was not specified within the call to the overloaded method.
public StreamReader OpenTextFile(
string path,
Encoding encoding,
bool detectEncoding,
int bufferSize) { }
public StreamReader OpenTextFile(
string path,
Encoding encoding,
bool detectEncoding) { }
public StreamReader OpenTextFile(
string path,
Encoding encoding) { }
public StreamReader OpenTextFile(string path) { }
In C# 4.0 the primary method can be refactored to use optional parameters as the following example shows:
public StreamReader OpenTextFile(
string path,
Encoding encoding = null,
bool detectEncoding = false,
int bufferSize = 1024) { }
Given this declaration it is now possible to call the OpenTextFile method omitting one or more of the optional parameters.
OpenTextFile("foo.txt", Encoding.UTF8);
It is also possible to use the C# 4.0 support for named parameters and as such the OpenTextFile method can be called omitting one or more of the optional parameters while also specifying another parameter by name.
OpenTextFile("foo.txt", Encoding.UTF8, bufferSize: 4098);
Named arguments must be provided last although when provided they can be provided in any order.
If you have ever written any code that performs some degree of COM interoperability you have probably seen code such as the following.
object filename = "test.docx";
object missing = System.Reflection.Missing.Value;
doc.SaveAs(ref filename,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing);
With optional and named parameters the C# 4.0 language provides significant improvements in COM interoperability and so the above code can now be refactored such that the call is merely:
doc.SaveAs("foo.txt");
When performing COM interoperability you'll notice that you are able to omit the ref modifer although the use of the ref modifier is still required when not performing COM interoperability.
With previous versions of the technologies it was necessary to also ship a Primary Interop Assembly (PIA) along with your managed application. This is not necessary when using C# 4.0 because the compiler will instead inject the interop types directly into the assemblies of your managed application and will only inject those types you're using and not all of the types found within the PIA.
The final language improvement that we will explore is co-variance and contra-variance and we'll begin by exploring co-variance with .NET arrays.
string[] names = new string[] {
"Anders Hejlsberg",
"Mads Torgersen",
"Scott Wiltamuth",
"Peter Golde" };
Write(names);
Since version 1.0 an array in the .NET Framework has been co-variant meaning that an array of strings, for example, can be passed to a method that expects an array of objects. As such the above array can be passed to the following Write method which expects an array of objects.
private void Write(object[] objects)
{
}
Unfortunately arrays in .NET are not safely co-variant as we can see in the following code. Assuming that the objects variable is an array of strings the following will succeed.
objects[0] = "Hello World";
Although if an attempt is made to assign an integer to the array of strings an ArrayTypeMismatchException
is thrown.
objects[0] = 1024;
In both C# 2.0 and C# 3.0 generics are invariant and so a compiler error would result from the following code:
List<string> names = new List<string>();
Write(names);
Where the Write
method is defined as:
public void Write(IEnumerable<object> objects) { }
Generics with C# 4.0 now support safe co-variance and contra-variance through the use of the in
and out
contextual keywords. Let's take a look at how this changes the definition of the IEnumerable<T>
and IEnumerator<T>
interfaces.
public interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T>
{
T Current { get; }
bool MoveNext();
}
You'll notice that the type parameter T
of the IEnumerable
interface has been prefixed with the out
contextual keyword. Given that the IEnumerable
interface is read only, there is no ability specified within the interface to insert new elements with the list, it is safe to treat something more derived as something less derived. With the out
contextual keyword we are contractually affirming that IEnumerable<out T>
is safely co-variant. Given that IEnumerable<out T>
is safely co-variant we can now write the following code:
IEnumerable<string> names = GetTeamNames();
IEnumerable<object> objects = names;
Because the IEnumerable<out T>
interface uses the out
contextual keyword the compiler can reason that the above assignment is safe.
Using the in
contextual keyword we can achieve safe contra-variance, that is treating something less derived as something more derived.
public interface IComparer<in T>
{
int Compare(T x, T y);
}
Given that IComparer<in T>
is safely contra-variant we can now write the following code:
IComparer<object>
objectComparer = GetComparer();
IComparer<string>
stringComparer = objectComparer;
Although the current CTP build of Visual Studio 2010 and the .NET Framework 4.0 has limited support for the variance improvements in C# 4.0 the forthcoming beta will allow you to use the new in
and out
contextual keywords in types such as IComparer<in T>. The .NET Framework team is updating the types within the framework to be safely co- and contra-variant.
I've listed here some resources you might find useful in exploring the capabilities of the C# 4.0 programming language and would encourage you to also download and install the beta once it is available in the coming months.