Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic class with self-referencing type constraint

Tags:

c#

generics

Consider the following code:

abstract class Foo<T>
    where T : Foo<T>, new()
{
    void Test()
    {
        if(Bar != null)
            Bar(this);
    }

    public event Bar<T> Bar;
}

delegate void Bar<T>(T foo)
    where T : Foo<T>, new();

The line Bar(this) results in the following compiler Error:
Argument type Foo<T> is not assignable to parameter type T

T is constrained to Foo<T> as I want derived classes to basically tell the base class their type, so that the type can be used in the event callback in order to save the implementor from having to cast the callback argument to the derived type.

I can see the code doesn't quite work but I'm having a bit of a blockage as to how to do this correctly without ending up with a generic delegate that can be used for any old thing. I'm also not quite sure why the T constraint doesn't create a compiler error considering it seems to be recursive.

EDIT

I need to clarify this I think! Here's a new example which, I hope will be much clearer. Note below that the OnDuckReady event handler below generates a compiler error.

How do I get the event to pass in the correct type?

abstract class Animal<T>
    where T : Animal<T>, new()
{
    void Test()
    {
        if(AnimalReady != null)
            AnimalReady(this);
    }

    public event AnimalHandler<T> AnimalReady;
}

delegate void AnimalHandler<T>(Animal<T> animal)
    where T : Animal<T>, new();

class Duck : Animal<Duck>
{
    public void FlyAway()
    {
    }
}

class Test
{
    void Main()
    {
        Duck duck = new Duck();
        duck.AnimalReady += OnDuckReady; // COMPILER ERROR
    }

    void OnDuckReady(Duck duck)
    {
        duck.FlyAway();
    }
}
like image 739
Nathan Ridley Avatar asked Jul 07 '11 22:07

Nathan Ridley


1 Answers

You can cast 'this' to T:

Bar((T)this);

This however will fail if you have the following:

public class MyFoo : Foo<MyFoo> { }

public class MyOtherFoo : Foo<MyFoo> { }

Because 'MyOtherFoo' is not an instance of 'MyFoo'. Take a look at this post by Eric Lippert, one of the designers of C#.

like image 179
eulerfx Avatar answered Sep 21 '22 06:09

eulerfx