C# 4.0 is the latest version of the C# programming language, which was released in April 11, 2010
· Named and Optional Arguments
· Dynamic Binding
· CoVariance & ContraVariance
· COM specific interop features
· Indexed Properties
Named & Optional Arguments
This is similar to default values as seen in Visual Basic and C++. For example:
|
|
| private void Increment(ref int x, int dx = 1) { x += dx; }
int x = 0; Increment(ref x); // dx takes the default value of 1 Increment(ref x, 2); // dx takes the value 2
|
| public void M(int x, int y = 5, int z = 7);
M(1, 2, 3); M(1, 2); // Equivalent to M(1, 2, 7)
M(1); // Equivalent to M(1, 5, 7)
|
It is also possible to explicitly specify Parameter Names In Method Calls.
The only restriction is that named parameters must be placed after the unnamed parameters.
Parameter names can be specified for both optional and required parameters, and can be used to improve readability or arbitrarily reorder arguments in a call.
For example:
|
|
| Stream OpenFile(string name, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read) { ... }
OpenFile("file.txt"); // use default values for both "mode" and "access" OpenFile("file.txt", mode: FileMode.Create); // use default value for "access" OpenFile("file.txt", access: FileAccess.Read); // use default value for "mode" OpenFile(name: "file.txt", access: FileAccess.Read, mode: FileMode.Create);
|
| public void M(int x, int y = 5, int z = 7);
M(1, z: 3); M(x: 1, z: 3); M(z: 3, x: 1); //Reversing the order of arguments |
Optional parameters make interoperating with COM easier. Previously, C# had to pass in every parameter in the method of the COM component, even those that are optional. For example:
|
|
| 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 support for optional parameters, the code can be shortened as: doc.SaveAs(ref fileName);
Which, due to the now optional ref keyword when using COM, can further be shortened as: doc.SaveAs(fileName);
Overload resolution
Named and optional arguments affect overload resolution, but the changes are relatively simple:
A signature is applicable if all its parameters are either optional or have exactly one corresponding argument (by name or position) in the call which is convertible to the parameter type.
Betterness rules on conversions are only applied for arguments that are explicitly given – omitted optional arguments are ignored for betterness purposes.
Thumb Rule: If multiple signatures are equally good, one that does not omit optional parameters is preferred.
|
|
| M(string s, int i = 1);
M(object o);
M(int i, string s = "Hello");
M(int i);
|
Call - M(5); // M(int)
Given these overloads, we can see the working of the rules above.
· M(string,int) is not applicable because 5 doesn't convert to string.
· M(int,string) is applicable because its second parameter is optional, and so, obviously are M(object) and M(int).
· M(int,string) and M(int) are both better than M(object) because the conversion from 5 to int is better than the conversion from 5 to object.
· Finally M(int) is better than M(int,string) because no optional arguments are omitted.
· Thus the method that gets called is M(int).
Dynamic Binding
Dynamic binding allows you to write method, operator and indexer calls, property and field accesses, and even object invocations which bypass the C# static type checking and instead gets resolved at runtime.
Dynamic types, which means the types will be determined during the runtime and any error that is produced will not affect compilation of the project.
Dynamic binding offers a unified approach to invoking things dynamically. With dynamic binding, when you have an object in your hand, you do not need to worry about whether it comes from COM, IronPython, the HTML DOM, reflection or elsewhere; you just apply operations to it and leave it to the runtime to figure out what exactly those operations mean for that particular object.
This affords you enormous flexibility, and can greatly simplify your code, but it does come with a significant drawback: Static typing is not enforced for these operations.
See the code below :
|
|
| public static void Main(string[] args) { dynamic obj = GetObject(); // C# 4.0 introduces a new static type called 'dynamic' obj.CallMe(10); }
public static dynamic GetObject() { Dynamic obj = new NamedAndOption(); Return obj; }
|
| int GetLength(dynamic obj) {return obj.Length;}
GetLength("Hello, world"); // A string has a Length property, GetLength(new int[] { 1, 2, 3 }); // and so does an array, GetLength(42); // but not an integer - An exception will be thrown here at run-time
|
Here the dynamic object is created and a method is invoked. As this is truly dynamic, everything will be evaluated in runtime and hence you can call CallMe() or even CallMe2(which is not present) during compile time and this will evaluated during runtime and produce errors.
Dynamic lookup is performed using three distinct mechanisms:
· COM IDispatch for COM objects,
· IDynamicMetaObjectProvider
DLR interface for objects implementing that interface. Thus any C# class can therefore intercept dynamic calls on its instances by implementing IDynamicMetaObjectProvider.
· Relection for all other objects.
The dynamic type
C# 4.0 introduces a new static type called dynamic.
When you have an object of type dynamic you can "do things to it" that are resolved only at runtime:
dynamic d = GetDynamicObject(…);
d.M(7);
The C# compiler allows you to call a method with any name and any arguments on d because it is of type dynamic.
At runtime the actual object that d refers to will be examined to determine what it means to "call M with an int" on it.
dynamic d = 7; // compile-time implicit conversion
int i = d; // runtime implicit conversion
Dynamic operations
Not only method calls, but also field and property accesses, indexer and operator calls and even delegate invocations and constructor calls can be dispatched dynamically:
|
|
| dynamic d = GetDynamicObject(…);
d.M(7); // calling methods
d.f = d.P; // getting and settings fields and properties
d["one"] = d["two"]; // getting and setting through indexers
int i = d + 3; // calling operators
string s = d(5,7); // invoking as a delegate
var c = new C(d); // calling a constructor
|
How different with 'Var':
After the introduction of "var" in C# 3.5, the use of anonymous types increased rapidly. var is determined during compile time, and it is Implicitly Typed Variable so it can not be a return type of a method.
Dynamic on the other hand can be a part of return type or argument of a method and will act during runtime when actual object is set to it.
CoVariance & ContraVariance
Let's first understand these two concepts in easy words.
· This is about using using two modifiers out
and in
- As 'Type Parameters' on a generic type.
· Variant type parameters can only be declared on interfaces and delegate types, due to a restriction in the CLR.
· When applicable - Variance only applies when there is a Reference Conversion between the type arguments. For example, an IEnumerable<int> is not an IEnumerable<object> because the conversion from int to object is a boxing conversion, not a reference conversion.
Variance
The following generics aspect is not allowed – Surprising!!
|
|
| IList<string> strings = new List<string>();
IList<object> objects = strings; // Not Allowed IEnumerable <object> objects = strings; // Not Allowed |
The second & third assignment – Ideally should be allowed- As you can assign Child to any Parent object as Parent is larger type and hold properties of Child.
However, surprisingly, this is disallowed – Reason being, Strings do not have the same element type as Objects. i.e. If it were allowed you could write:
|
|
| objects[0] = 5;
string s = strings[0]; |
This is like, allowing an int to be inserted into a list of strings and subsequently extracted as a string. This would be a breach of type safety. Thus disallowed.
Here comes the usage of Covariance & Contravariance.
Variance is about allowing assignments such as this in cases where it is safe. The result is that a lot of situations that were previously surprising now just work.
See the below quick example.
In .NET 4.0 the IEnumerable<T> interface will be declared in the following way:
|
|
| public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
} |
The "out" in these declarations is a new C# 4.0 modifier which signifies that the T can only occur in output position in the interface – the compiler will complain otherwise.
In return for this restriction, the interface becomes "covariant" in T, which means that an IEnumerable<A> is considered an IEnumerable<B> if A has a reference conversion to B.
As a result, any sequence of strings is also e.g. a sequence of objects. Thus the below becomes allowed.
IEnumerable <object> objects = strings; // Becomes Allowed Now (In C# 4.0)
Now let's understand this concept more.
Before that it's better to know - Generic Interfaces and Delegates only can have the Variance feature. i.e. Their type parameters can be marked as covariant or contravariant, using keywords out
and in
, respectively. These declarations are then respected for type conversions, both implicit and explicit, and both compile-time and run-time.
CoVariance
If you have a class hierarchy, Parent and Child (as name mentioned). Now ideally you can assign Child to any Parent object as Parent is larger type and hold properties of Child.
Now if you try to write come code as mentioned below.
| CoVariance – Error Code |
| namespace CoVarinace { class Vegetable { } class Potato : Vegetable { }
class Program { delegate T MyFunc<T>();
static void Main(string[] args) { MyFunc<Potato> potato1 = () => new Potato(); MyFunc<Vegetable> veg = potato1; // Fail } } }
|
This code will fail with the below error message - "Cannot implicitly convert type ' CoVarinace.Program.MyFunc<CoVarinace.Potato>' to 'CoVarinace.Program.MyFunc<CoVarinace.Vegetable>'
Here comes the usage of the new feature – 'CoVariance' - delegate T MyFunc<out T>();
See the modified code.
| CoVariance – No Errror |
| namespace CoVarinace { class Vegetable { } class Potato : Vegetable { }
class Program { delegate T MyFunc<out T>();
static void Main(string[] args) { MyFunc<Potato> potato1 = () => new Potato(); MyFunc<Vegetable> veg = potato1; //Covariance } } }
|
ContraVariance
This works with generic delegate which returns nothing but takes a parameter.
See the code.
| ContraVariance – Error Code |
| namespace ContraVarinace { class Vegetable { } class Potato : Vegetable { }
class Program { delegate void MyAction<T>(T a);
static void Main(string[] args) { MyAction<Vegetable> action1 = (veg) => {Console.WriteLine(veg); }; MyAction<Potato> potato1 = action1; } } }
|
This will throw you an error - Cannot implicitly convert type 'ContraVarinace.Program.MyAction<ContraVarinace.Vegetable>' to ' ContraVarinace.Program.MyAction<ContraVarinace.Potato>'
With a little modification the below code will work without any issue, you just need to put the keyword in.
delegate void MyAction<in T>(T a);
| ContraVariance – No Error |
| namespace ContraVarinace { class Vegetable { } class Potato : Vegetable { }
class Program { delegate void MyAction<in T>(T a);
static void Main(string[] args) { MyAction<Vegetable> action1 = (veg) => { Console.WriteLine(veg); }; MyAction<Potato> potato1 = action1; } } } |
Limitations
· Variant type parameters can only be declared on interfaces and delegate types, due to a restriction in the CLR.
· Variance only applies when there is a reference conversion between the type arguments. For instance, an IEnumerable<int> is not an IEnumerable<object> because the conversion from int to object is a boxing conversion, not a reference conversion.
Indexed properties
Many COM APIs expose "Indexed Properties" which are essentially properties with parameters. C# will not allow you to declare indexed properties, but to the extent that non-C# APIs expose them, will now allow you to access these using element access syntax. So instead of
o.set_P(i+1, o.get_P(i) * 2);
You can now write the more intuitive
o.P[i+1] = o.P[i] * 2;
Limitations
A few COM interface features still are not surfaced in C#, most notably default properties. As mentioned above these will be respected if you access COM dynamically, but statically typed C# code will still not recognize them.
Larger COM Example
Here is a larger Office automation example that shows many of the new C# features in action.
| |
|
| using System;
using System.Diagnostics;
using System.Linq;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word; class Program
{
static void Main(string[] args) {
var excel = new Excel.Application();
excel.Visible = true; excel.Workbooks.Add(); // optional arguments omitted excel.Cells[1, 1].Value = "Process Name"; // no casts; Value dynamically
excel.Cells[1, 2].Value = "Memory Usage"; // accessed var processes = Process.GetProcesses()
.OrderByDescending(p => p.WorkingSet)
.Take(10); int i = 2;
foreach (var p in processes) {
excel.Cells[i, 1].Value = p.ProcessName; // no casts
excel.Cells[i, 2].Value = p.WorkingSet; // no casts
i++;
} Excel.Range range = excel.Cells[1, 1]; // no casts Excel.Chart chart = excel.ActiveWorkbook.Charts. Add(After: excel.ActiveSheet); // named and optional arguments chart.ChartWizard(
Source: range.CurrentRegion,
Title: "Memory Usage in " + Environment.MachineName); //named+optional chart.ChartStyle = 45; chart.CopyPicture(Excel.XlPictureAppearance.xlScreen,
Excel.XlCopyPictureFormat.xlBitmap,
Excel.XlPictureAppearance.xlScreen); var word = new Word.Application();
word.Visible = true; word.Documents.Add(); // optional arguments word.Selection.Paste();
}
}
|
The code is much more terse and readable than the C# 3.0 counterpart.
Hope this helps.
Regards,
Arun Manglick