C# 4.0 and variant generic delegates

Continuing on from my previous blog entry, in this entry I will talk about variant generic delegates. Just as with generic interfaces, generic delegates can use out and in to specify covariant and contravariant parameters respectively. And the MSIL equivalents are the + and - symbols.

Here are three variant generic delegate declarations:

delegate T CovariantDelegate<out T>();
delegate void ContraVariantDelegate<in T>(T t);
delegate T1 ContraAndCovariantDelegate<out T1, in T2>(T2 t2);

The rules are the same as with generic interfaces. The covariant delegate can only use the generic parameter as a return type, while the contravariant delegate can only use it as a delegate argument and not as a return type. The third delegate is both covariant and contravariant but with respect to two separate parameters. And here’s how these delegates can be used:

static Derived SomeFunc()
{
    return new Derived();
}

static void SomeOtherFunc(Base b)
{
}

static Derived AnotherFunc(Base b)
{
    return new Derived();
}

private void DoDelegates()
{
    CovariantDelegate<Base> covariantDeleg = SomeFunc; <--(#1)

    ContraVariantDelegate<Derived> contraDeleg = SomeOtherFunc; <--(#2)

    ContraAndCovariantDelegate<Base, Derived> contraCovarDeleg = AnotherFunc; <--(#3)
}

(#1) : Covariance at play here. Even though SomeFunc returns a Derived type, we use it to instantiate a delegate that expects a Base type.

(#2) : Contravariance here. The delegate expects a Derived argument, but we pass it a method that takes a Base argument.

(#3) : This is essentially a mix of (#1) and (#2).

Here’s a more realistic use of variance in generic delegates:

class Item
{
    . . .

    public int Code { get; set; }
}

class Book : Item
{
    public string Title { get; set; }
}

class DVD : Item
{
    public string Description { get; set; }

    public int Duration { get; set; }
}

Item FindItem(string searchText, Func<string, Item> findFunc)
{
    return findFunc(searchText);
}

Notice how the FindItem method’s second parameter is a Func<> object that itself takes a string and returns an Item. Func‘s return type parameter is covariant, and that’s what we can take advantage of. The code below shows two methods, one that searches for a book, and the other that searches for a DVD.

static List<Book> books = < . . . >

static Book FindBook(string searchText)
{
    return books.FirstOrDefault(book => book.Title == searchText);
}

static List<DVD> dvds = < . . . >

static DVD FindDVD(string searchText)
{
    return dvds.FirstOrDefault(dvd =>
        dvd.Description.StartsWith(searchText)
        || searchText.Contains(dvd.Duration.ToString()));
}

The following code shows how to call FindItem using a delegate of our choice.

Item book = FindItem("C++/CLI in Action", FindBook);

Item dvd = FindItem("Batman", FindDVD);

In the first call, we pass a method that returns a Book, whereas the method signature expects a method that returns an Item. But since Func<>‘s return parameter is covariant, this works fine. Essentially we are passing a Func<string, Book> as a Func<string, Item> argument. It’s the same for the second call too except that we have a DVD instead of a Book.

Now let’s see a contravariant delegate example. Consider the following method:

void ShowItemCode<T>(T item, Action<T> showFunc)
{
    showFunc(item);
}

It takes an Action<T> as the second argument which is a contravariant generic delegate. Now consider the following code:

static void ShowCode(Item item)
{
    Console.WriteLine(item.Code);
}

. . .

Book book = < initialize here . . .>
ShowItemCode(book, ShowCode);

DVD dvd = < initialize here . . .>
ShowItemCode(dvd, ShowCode);

In the first case we are calling ShowItemCode<Book> which expects Action<Book> as the second argument. But we pass ShowCode(Item) which matches Action<Item> yet it works because Action<> is contravariant.

Remember how in the previous entry I had mentioned a case where a covariant parameter type can be used as the parameter type for a contravariant delegate argument? Well, here’s an example of that:

interface ITestCovariant<out T>
{
    //void Bar(T t); <-- won't compile
    void Foo1(Action<T> func);
    void Foo2(Func<T, bool> func);
}

Even though T is a covariant parameter, we can use it as the parameter type for Action<> since Action is contravariant. I have also used it as the first type argument for the Func<> argument because though Func<> has a covariant return type, it’s input argument generic types are contravariant. Similarly for a contravariant interface:

interface ITestContravariant<in T>
{
    //T Bar(); <-- won't compile
    Func<T, bool> Foo1();
    Action<T> Foo2();
}

Here, we have a contravariant parameter T, but we’ve indirectly used it in the return type by using it to set the contravariant parameters in the Func<> and Action<> delegates.

Advertisements

One thought on “C# 4.0 and variant generic delegates

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s