Understanding Lambdas, Delegates, and Other C# Mysteries

 

Understanding Lambdas, Delegates, and Other C# Mysteries

  • Comments 3

Source Code is here

 

Much of this example uses the principles in Charlie Calvert's blog posts as well as Eric Lippert's blog.

"Variance" in C# programs is very much anticipated.

Many developers consider this the "missing link" in the evolutionary growth of C#.

You may not know what variance is, but I'm sure you've felt it's pain.

With the release of C# 4.0, the language will now be covariant and contravariant with generic interface types.

This will allow to treat generic interfaces in a more natural and intuitive manner.

The details will be presented in this blog post.

 

The Goal – I am building the case for the C# 4.0 features of Covariance and Contravariance

Covariance and contravariance are complex topics. To understand them we need to work our way up the path to get there. We need a solid understanding of delegates, interfaces, and generics. I will also describe IEnumerable<T> generic iterators.

Covariance is supported only with delegates and interface types in C# 4.0. IEnumberable is the key interface to support this.

How it is implmented and what it is called

Declaration side covariance for interfaces and delegates. There are a few dozen interfaces that the C# team had to worry about.

Moreover, I was looking over the code in the CTP for Visual Studio 2010 and found code that really challenged my code reading skills. So, I'm going to enter some blog posts to help me fully understand lambdas, which essetially abbreviate the use of delegates. I hope to take you along the way.

 

Basic Delegates – skip this if you know what delegates are

Starting with the simplest of examples.

Back in the day when I was a C++ developer, we did similar coding with function pointers. In C++ the function would have looked like this:

double square_it(double x)

{

    return x * x;

}

Here is an implementation in C#:

Notice the code above shows "25" in the messagebox. This is the most simple example.

More sophisticated example – MultiCast Delegates – skip this section if you know what multicast delegates are

This next section is about multicast delegates, which is essentially a type safe array of function pointers.

The object called "totalDelegates" will essentiall point to 3 methods.

Part 1

This defines the functions that will be called through the delegates

Part 2

Implements Func(TResult) delegate types. Built in to .NET

Correlate the Func(TResult) to methods

Part 3

Aggregate the delegates into one object.

Part 4

Loop through array of delegates and call Method1(), Method2(), Method3()

 

The point here is the curr_instance() will call all 3 methods:

delegateList is essentially an array of refererences to methods:

More sophisticated example – Generic Enumerables – Not showing covariance and contravariance yet

 

Code to use the above classes

Now we will code up the form to exercise this code.

The key method to observe here is public IEnumerator<Person> GetEnumerator()

The expectation is that you can code with generic types in the same way you would with concrete types. C# 4.0 will guarantee that generic delegates and interfaces work in the way you expect them to.

Sample Hierarchy

The scenario we are trying understand well is that we want generics to wok with the same we get concrete types to work.

Let's start with the concrete types. Suppose you have 2 classes as follows:

This has always been legal in C# 3.0

But this is not legal

 

However when dealing with generics, in spite of what your intuition would tell you, this is not legal:

 

This is needed because we don't know if we are going to add a "SubContractor," which might also be derived class of a person.

Imagine a scenario where you have an array like this:

Imagine loop through this array and trying to access the "Salary." Everything works fine until you get to the "SubContractor," where "Hours" and "HourlyRate" are the data elements. We'd get a crash.

That is what the type system is protecting us from.

But the issue is only if you add things to the person_array

This is due to the fact that a list of Persons would allow you to add any kind of Person, such as a SubContractor, to the collection. This is a clear violation of type safety because the actual list object only allows Employee.

However there are certain circumstances when covariance can be allowed.

If a generic interface does not define anything that will input the defined type, meaning that it will only occur in output positions, the interface can be considered covariant.

Code in VS 2010

There is currently a hole in the type system that can sneak through the compiler in C# 3.0

string[] sa = new string[10];

object oa[] = sa;

oa[0] = "hello";

oa[1] = 5; //legal because 5 drives from object

But it is not until runtime that we get in trouble. This sneaks past the compiler. This is not good.

The cost is that at runtime that we need to check (dynamically) the types being assigned. But what if we were to read from the "sa" array and assign to a string object. That should be totally safe, right? The next example illustrates that if we are "reading" or returning a derived type we should be fairly safe.

Back to our Employee and Person Example

What SHOULD compile, but does not in C# 3.0

Because the delegates is only returning data, and therefore is "read-only," this code should be safe.

This code looks like it should succeed because a Employee is type of Person, and one should always be able to assign a more derived type of object, such as a Employee, to a base class reference, such as a Person. This is fixed in C# 4.0.

This code should compile in C# 3.0, but does not, because C# currently does not support "Declaration Side Covariance for Interface and Delegate Types," according to Anders Hejlsberg.

C# 4.0 Solution

 

The "out" keyword

The new feature being added in Visual Studio 2010 ensures that this assignment will work if you make one minor change to the declaration for your delegate. In particular, you need to use the keyword out in your type parameter:

delegate T MyFunction<out T>();

 

The "out" syntax guarantees that we are returning an object, not referencing it's internal elements. That makes it legal (and safe) to use a delegate in this fashion.

 

The next blog post will address "contravariance."

 

Contravariance

 

To set the stage, this all works as expected:

But this code is illegal in C# 3.0. We can make this legal in C# 4.0 with some modifications to the "delegate" declaration. It means adding the "in" keyword as seen below.

Now this code become legal if we declare the delegate as follows:

Notice the "in" modifier. Notice also that we use the keyword in, rather than the keyword out. That is because we are passing a parameter in, rather than returning a result "back out" of a function. I found this difficult to understand, so I read about some of this in Eric Lippert's blog. The point here is that "act1" will work with any "Person", and a "Employee" is "Person", therefore you can safely make the assignment.

 

Download the source code and "play!"

Page 1 of 1 (3 items)
Leave a Comment
  • Please add 4 and 4 and type the answer here:
  • Post