The main theme of C# 4.0 is dynamic programming. Objects can be dynamic in the sense that their behaviour and structure may not be known at compile type, i.e., not captured by a static type. For example, the following are dynamic objects:
.NET types accessed through reflection.
COM objects accessed through IDispatch.
Objects with changing structure such as HTML DOM objects.
Objects from dynamic languages such as Python or Ruby.
The new features of C# 4.0 therefore fall into four groups:
The key point of dynamic binding is: A dynamic object (i.e., an object declared with dynamic keyword) is assumed at compile time to support any operation, and only at runtime will an error occur if the object did not support a specific operation. The following code shows a scenario where dynamic objects can be used:
// Creates a customer
public object GetCustomer()
{
return new Customer("Yazan");
}
// Call GetCustomer method. GetCustomer return an object and this cannot be changed;
// it may be third party code, or code that cannot be changed because it is public
// and is likely to break other components
Customer c = GetCustomer() as Customer;
if (c != null)
{
// use the customer object
c.ProcessOrder();
}
The dynamic keyword tells the compiler that a variable's type can change or that it is now known until runtime. Think of it as using an object without having to cast it. The above code can be rewritten to use dynamic variables:
// Note that there is no need to cast the returned object
dynamic c = GetCustomer() as Customer;
c.ProcessOrder();
The following example illustrates the above points:
public void TestDynamicBinding1()
{
try
{
dynamic customer = GetCustomer();
// OK
string name = customer.Name;
// Error. <>f__AnonymousType0<string,int>' does not contain a definition for 'DoSomething'
customer.DoSomething();
}
catch (Exception ex)
{
Trace.WriteLine(ex.Mezssage);
}
}
public object GetCustomer()
{
return new { Name = "Yazan", ID = 1 };
}
Another interesting but highly dangerous use of dynamic variables is being able to use a dynamic variable for different types of data. The following example illustrates:
public void TestDynamicBinding2()
{
// The following is legal
dynamic myvar = 1.0;
myvar = "hello";
// A more meaningful use of the above scenario is when having to cast from int to long.
// This is no longer needed with a dynamic variable. For example,
// int n = 1; // OK
// n = 10L; // Error: Cannot implicitly convert type 'long' to 'int'.
// // An explicit conversion exists (are you missing a cast?)
dynamic n = 1; // OK
n = 10L; // OK
}
Note that switching from dynamic to static and static to dynamic is easy:
public class Person
{
public string Name { get; set; }
}
public class Employee : Person
{
public string ID { get; set; }
}
public class Manager
{
public string Title { get; set; }
}
public void TestDynamicBinding3()
{
try
{
Employee emp1 = new Employee();
dynamic dynEmp = emp1; // OK. static to dynamic
Employee emp2 = dynEmp; // OK. dynamic to static. Works because dynEmp is of type Employee
Person prsn = dynEmp; // OK. dynamic to static. Works because dynEmp is of type Employee
// which dervies from Person
Manager mgr = dynEmp; // RuntimeBinderException: "Cannot implicitly convert type
//'CS4Features.Employee' to 'CS4Features.Manager'"
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
With a dynamic type, not only method calls can be used, but also field and property accesses, indexers and operator calls and even delegate invocations and constructors can be dispatched dynamically:
dynamic d = GetDynamicObject(...);
d.M(7); // calling methods
d.M(x: "Hello"); // passing arguments by name
d.f = d.P; // getting and setting 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
C c = new C(d); // selecting constructors
The role of the C# compiler is simply to package up the necessary information about what is being done to d, so that the runtime can pick it up and determine what is the exact meaning of it, given an actual object referenced by d. Think of it as deferring part of the compiler’s job to runtime. If d implements the special interface IDynamicMetaObjectProvider (previously called IDynamicObject), then d is considered a dynamic object, otherwise d is treated as a standard .NET type and the operation will be resolved using .NET reflection on its type.
Named and optional parameters are really two distinct features, but are often useful together. Optional parameters allow you to omit arguments to member invocations, whereas named arguments is a way to provide an argument using the name of the corresponding parameter instead of relying on its position in the parameter list.
Some APIs, most notably COM interfaces such as the Office automation APIs, are written specifically with named and optional parameters in mind. Up until now it has been very painful to call into these APIs from C#, with sometimes as many as thirty arguments having to be explicitly passed, most of which have reasonable default values and could be omitted. Prior to C# 4.0, optional parameters were often implemented using overloaded functions:
// Method specifies all required parameters
private void Run(string name, bool bLog, List<int> lstID)
{
// Implementation comes here
}
// Method specifies only one parameters. Delegates to the first method passing
// in default values for the missing parameters
private void Run(string name)
{
Run(name, false, null); // Pass default values for second & third parameters
}
// Method specifies two parameters only. Delegates to the first method passing
// in default values for the missing parameters
private void Run(string name, bool bLog)
{
Run(name, bLog, null); // Pass default value for third parameter
}
The above can now be written using optional parameters. A parameter is declared optional simply by providing a default value for it. In the following, bLog and lstID are optional and can be omitted in calls:
private void RunWithOptionalParameters(string name, bool bLog = false, List<int> lstID = null)
{
// Implementation comes here
}
public void TestOptionalParameters()
{
RunWithOptionalParameters("Yazan"); // false and null for second and third parameters, respectively
RunWithOptionalParameters("Yazan", true); // null for third parameter
RunWithOptionalParameters("Yazan", true, new List<int>());
}
But what if you had a method with 20 optional parameters and you only wanted to specify a value for the last parameter? This is where named parameters come handy.
RunWithOptionalParameters("Yazan", new List<int>()); // Error: Argument 2: cannot convert from
// 'System.Collections.Generic.List<int>' to 'bool'
RunWithOptionalParameters("Yazan", lstID: new List<int>()); // OK.
RunWithOptionalParameters(lstID: new List<int>(), name: "Yazan"); // OK.
Note the following extra points:
Optional and named arguments can be used not only with methods but also with indexers and constructors.
A required parameter (one that does not have a default value) cannot appear after an optional parameter.
A ref or out parameter cannot have a default-argument.
It is an error for a positional argument (argument without an argument name) to appear after a named argument.
Consider the following code:
public class Person
{
public string Name { get; set; }
}
public class Employee : Person
{
public string ID { get; set; }
}
void DoSomething(Person p)
{
// Implementation ....
}
// Call DoSomething using instances of a base class and a derived class
Person p = new Person();
Employee e = new Employee();
DoSomething(p);
DoSomething(e);
DoSomething accepts an instance of Person class or an instance of a Person-derived class (remember that public inheritance means IS A). But now suppose that DoSomething has the following signature:
void DoSomething(IEnumerable<Person> coll)
{
// Implementation ....
}
By the same logic, when you call this function, you don't have to pass something
typed as IEnumerable
In summary, the C# compiler understands that it's okay to vary the type of an
argument, but doesn't understand that it's okay to vary the type of a generic
type parameter. And generic variance just means fixing that: fixing the
C# compiler so that it is okay to pass an IEnumerabl<Employee>
When you vary the type of an argument, you can only vary it in the direction of
more derived. Passing an
Employee to a function that expects a Person
is okay. Passing an Object to a function that
expects a Person is not okay. When you vary the
type of the generic type parameter to
IEnumerable
In other cases, as shown below, it turns out that the rule has to be the other way round: you can only vary the type parameter in the direction of less derived. Consider this function which takes an IComparer<T> instead of IEnumerable<T>:
void DoSomething(IComparer<Person> coll)
{
// Implementation ....
}
If we use the IEnumerable rule and call this
function with an IComparer
On the other hand, suppose we call this function with an IComparer<object>. IComparer<object> can compare arbitrary objects, so it can easily cope with the specific requirement of comparing transforms. So we can pass a base type in place of the expected type.
So sometimes the rule is that we can only vary in the direction of more derived, and sometimes the rule is that we can only vary in the direction of less derived (more base). How do we know which rule applies in any given case? The answer is that it depends on whether the generic type parameter appears in output or input positions.
In IEnumerable
In IComparer
List<string> lstStrings = new List<string>();
List<object> lstObjects = lstStrings;
The second assignment generates the following error:
Cannot implicitly convert type 'List<string>' to 'List<object>'
The second assignment is disallowed because a list of strings is not a list of objects. The second statement attempted to make lstObjects and lstStrings point to the same list. If that was to succeed, then you could have written:
// If lstObjects and lstString pointed to the same list, then we could insert
// a Customer object and retrieve it as a string! This is a breach of type safety!
lstObjects[0] = new Customer();
string s = lstStrings[0];
The problem generated by the second assignment can be overcome by using IEnumerable<T> interface which has no methods to insert objects into the collection. In fact the only method exposed by IEnumerable<T> is GetEnumerator():
IEnumerable<object> lstObjects = lstStrings; // OK.
Co- and contra-variance are about allowing assignments such as this in cases where it is safe. So the rule is: if a type parameter appears only in an output position, you can vary it in the more derived direction, and if a type parameter appears only in an input position, you can vary it in the less derived (base type) direction. C# specification refers to variance annotations which are in and out which are only used on type parameters when used with interfaces and delegate types. If the variance annotation is out, the type parameter is said to be covariant. If the variance annotation is in, the type parameter is said to be contravariant. If there is no variance annotation, the type parameter is said to be invariant. For example:
// X is covariant, Y is contra-variant, and Z is invariant
interface ISomething<out X, in Y, Z>
{
X Foo(Y y);
Z Bar();
}
In .NET 4.0, note the new declarations for IEnumerable<T> and IEnumerator<T>:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
T Current { get; }
}
The use of out means that the interfaces become covariant in T, which means that IEnumerable<A> is implicitly reference-convertible to IEnumerable<B> if A has an implicit reference conversion to B. For example, if class Employee derives from class Person, then a sequence of Employees is also a sequence of Persons:
List<Employee> lstEmployee = ...
IEnumerable<Person> lstPersons = ...
var result = lstEmployee.Union(lstPersons); // OK.
In .NET 4.0, note the new declarations for IComparable<T>:
public interface IComparer<in T>
{
int Compare(T x, T y);
}
The result is that an IComparer<object> can in fact
be considered an IComparer<string>
Co- and contra-variant type parameters can only be declared on interfaces and
delegate types. Co- and contra-variance only applies when there is a reference
(or identity) conversion between the type arguments. For instance, an
IEnumerable
Consider the following pre- C# 4.0 code:
using Microsoft.Office.Interop;
using Microsoft.Office.Interop.Word;
object foo = "MyFile.txt";
object bar = Missing.Value;
object optional = Missing.Value;
Document doc = (Document)Application.GetDocument(ref foo, ref bar, ref optional);
doc.CheckSpelling(ref optional, ref optional, ref optional, ref optional);
There are a few problems with the code above. First, you have to declare all your variables as objects and pass them with the ref keyword. Second, you can't omit parameters and must also pass the Missing.Value even if you are not using the parameter. And third, behind the scenes, you are using huge (in file size) interop assemblies just to make one method call. C# 4.0 will allow you to write the code above in a much simpler form that ends up looking almost exactly like 'normal' C# code. This is accomplished by using some of the features already discussed; namely dynamic support and optional parameters.
using Microsoft.Office.Interop.Word;
dynamic doc = Application.GetDocument("MyFile.txt");
doc.CheckSpelling();