Since I have talked before about Visual Studio 2010 and Dynamic Typing, it is now time to talk about the upcoming C# 4.0 which we are expecting soon.
To understand how C# evolved and what is the paradigm of C# 4.0, let’s get a quick overview of previous versions.
In 1998 the C# began as a simple, modern, object-oriented, and type-safe programming language for the .NET platform. Since launched in the summer of 2000, 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 addition of:
- Implicitly Typed Local Variables.
- Extension Methods.
- Lambda Expressions.
- Object and Collection Initializers.
- Anonymous types.
- Implicitly Typed Arrays.
- Query Expressions and Expression Trees.
Although C# is an object-oriented programming, but C# 3.0 included some capabilities of functional programming to enable 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.
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.
Essentially the C# 4.0 language innovations include:
- Dynamically Typed Objects.
- Optional and Named Parameters.
- Improved COM Interoperability.
- Safe Co-variance and Contra-variance.
To show these features, I’ll show some examples posted by Doug Holland on his The C# Programming Language Version 4.0 blog post…
Dynamically Typed Objects
In C# today you might need to get an instance of a 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);
With the C# 4.0 we would simply write the following code:
dynamic calc = GetCalculator(); int sum = calc.Add(10,20);
In the above example we are declaring a variable, calc, whose static type is 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.
Optional and Named Parameters
Another major benefit of using C# 4.0 is that the language now supports optional parameters (like C and C++) and named parameters.
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.
In C# 4.0 a 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: 1024);
Named arguments must be provided last although when provided they can be provided in any order.
Improved COM Interoperability
When you work with COM interop methods, you had to pass a reference to Missing.Value for unneeded parameters, like 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);
Now, in C# 4.0 you can only write:
doc.SaveAs(filename);
Notice that you are able to omit the ref modifier although the use of the ref modifier is still required when not performing COM interoperability.
Also it was necessary to 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.
Safe Co-variance and Contra-variance
Co-variance means that an instance of a subclass can be used when an instance of a parent class is expected, while Contra-variance means that an instance of a super class can be used when an instance of a subclass is expected. When neither is possible, it is called Invariance.
Since version 1.0 arrays in .NET has been co-variant meaning that an array of string can be assigned to an array of object.
string[] strings = new string[] { "Hello" }; object[] objects = names;
Unfortunately arrays in .NET are not safely co-variant. Since objects variable was an array of string the following will succeed.
objects[0] = "Hello World";
Although if an attempt is made to assign an integer to the objects array an ArrayTypeMismatchException is thrown.
objects[0] = 1024;
To achieve safety, both C# 2.0 and C# 3.0 generics are invariant and so a compiler error would result from the following code:
List strings= new List(); List<object> objects = strings;
For the Liskov Substitution Principle (LSP) to be achieved, methods of a derived class need to have contra-variant inputs and co-variant outputs.
Generics with C# 4.0 now support safe co-variance and contra-variance through the use of the in (contra-variant) and out (co-variant) contextual keywords. Let’s take a look at how this changes the definition of the IEnumerable<T> and IEnumerator<T> interfaces.
public interface IEnumerable { IEnumerator GetEnumerator(); } public interface IEnumerator { T Current { get; } bool MoveNext(); }
Given that an IEnumerable collection is read only, there is no ability specified within the interface to insert new elements, it is safe to treat a more derived element as something less derived. With the out contextual keyword we are contractually affirming that IEnumerable<out T> is safely co-variant. We can now write the following code without a compiler error:
IEnumerablestrings = GetStrings(); IEnumerable<object> objects = strings;
Using the in contextual keyword we can achieve safe contra-variance, that is treating something less derived as something more derived.
public interface IComparer { int Compare(T x, T y); }
Given that IComparer<in T> is safely contra-variant we can now write the following code:
IComparer objectComparer = GetComparer(); IComparer stringComparer = objectComparer;
It is important to notice that co-variance in IEnumerable<object> refers to the fact that its Current property can return a string instead of an object as output, while contra-variance in IComparer<string> refers to the fact that its Compare method can accept an object instead of a string as input.