20

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();
    }
}
ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58
Nathan Ridley
  • 33,766
  • 35
  • 123
  • 197

3 Answers3

20

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#.

Matt Jenkins
  • 2,824
  • 1
  • 30
  • 34
eulerfx
  • 36,769
  • 7
  • 61
  • 83
4

The code would be clearer if you didn't use "Bar" for two purposes. That having been said, I think what's needed is to use a generic with two parameters (e.g. T and U) such that T derives from U, and U derives from Foo. Alternatively, it's possible to do some nice things with interfaces. A useful pattern is to define:

interface ISelf<out T> {T Self<T> {get;}}

and then, for various interfaces that one might want to combine in an object:

interface IThis<out T> : IThis, ISelf<T> {}
interface IThat<out T> : IThat, ISelf<T> {}
interface ITheOtherThing<out T> : ITheOtherThing, ISelf<T> {}

If classes that implement IThis, IThat, and ITheOtherThing also implement ISelf<theirOwnTypes>, one can then have a routine whose parameter (e.g. "foo") has to implement both IThis and IThat accept the parameter as type IThis. Parameter "foo" will be of type IThis (which in turn implements IThis) while Foo.Self will be of type IThat. Note that if things are implemented this way, one may freely typecast variables to any desired combination of interfaces. For example, in the above example, if the object passed as "foo" was a type which implemented IThis, IThat, ITheOtherThing, and ISelf<itsOwnType> it could be typecast to ITheOtherThing>, or IThis, or any other desired combination and arrangement of those interfaces.

Really a pretty versatile trick.

Edit/Addendum

Here's a somewhat more complete example.

namespace ISelfTester
{
    interface ISelf<out T> {T Self {get;} }

    interface IThis { void doThis(); }
    interface IThat { void doThat(); }
    interface IOther { void doOther(); }

    interface IThis<out T> : IThis, ISelf<T> {}
    interface IThat<out T> : IThat, ISelf<T> {}
    interface IOther<out T> : IOther, ISelf<T> {}

    class ThisOrThat : IThis<ThisOrThat>, IThat<ThisOrThat>
    {
        public ThisOrThat Self { get { return this; } }
        public void doThis() { Console.WriteLine("{0}.doThis", this.GetType()); }
        public void doThat() { Console.WriteLine("{0}.doThat", this.GetType()); }
    }
    class ThisOrOther : IThis<ThisOrOther>, IOther<ThisOrOther>
    {
        public ThisOrOther Self { get { return this; } }
        public void doThis() { Console.WriteLine("{0}.doThis", this.GetType()); }
        public void doOther() { Console.WriteLine("{0}.doOther", this.GetType()); }
    }
    class ThatOrOther : IThat<ThatOrOther>, IOther<ThatOrOther>
    {
        public ThatOrOther Self { get { return this; } }
        public void doThat() { Console.WriteLine("{0}.doThat", this.GetType()); }
        public void doOther() { Console.WriteLine("{0}.doOther", this.GetType()); }
    }
    class ThisThatOrOther : IThis<ThisThatOrOther>,IThat<ThisThatOrOther>, IOther<ThisThatOrOther>
    {
        public ThisThatOrOther Self { get { return this; } }
        public void doThis() { Console.WriteLine("{0}.doThis", this.GetType()); }
        public void doThat() { Console.WriteLine("{0}.doThat", this.GetType()); }
        public void doOther() { Console.WriteLine("{0}.doOther", this.GetType()); }
    }
    static class ISelfTest
    {
        static void TestThisOrThat(IThis<IThat> param)
        {
            param.doThis();
            param.Self.doThat();
        }
        static void TestThisOrOther(IThis<IOther> param)
        {
            param.doThis();
            param.Self.doOther();
        }
        static void TestThatOrOther(IThat<IOther> param)
        {
            param.doThat();
            param.Self.doOther();
        }

        public static void test()
        {
            IThis<IThat> ThisOrThat1 = new ThisOrThat();
            IThat<IThis> ThisOrThat2 = new ThisOrThat();
            IThis<IOther> ThisOrOther1 = new ThisOrOther();
            IOther<IThat> OtherOrThat1 = new ThatOrOther();
            IThis<IThat<IOther>> ThisThatOrOther1 = new ThisThatOrOther();
            IOther<IThat<IThis>> ThisThatOrOther2a = new ThisThatOrOther();
            var ThisThatOrOther2b = (IOther<IThis<IThat>>)ThisThatOrOther1;
            TestThisOrThat(ThisOrThat1);
            TestThisOrThat((IThis<IThat>)ThisOrThat2);
            TestThisOrThat((IThis<IThat>)ThisThatOrOther1);
            TestThisOrOther(ThisOrOther1);
            TestThisOrOther((IThis<IOther>)ThisThatOrOther1);
            TestThatOrOther((IThat<IOther>)OtherOrThat1);
            TestThatOrOther((IThat<IOther>)ThisThatOrOther1);
        }
    }
}

The thing to note is that some classes implement different combinations of IThis, IThat, and IOther, and some methods require different combinations. The four non-static classes given above are all unrelated, as are the interfaces IThis, IThat, and IOther. Nonetheless, it is possible for method parameters to require any combination of the interfaces provided that implementing classes follow the indicated pattern. Storage locations of a "combined" interface type may only be passed to parameters which specify the included interfaces in the same order. An instance of any type which properly implements the pattern, however, may be typecast to any "combined" interface type using any subset of its interfaces in any order (with or without duplicates). When used with instances of classes that properly implement the pattern, the typecasts will always succeed at run-time (they could fail with rogue implementations).

Rand Random
  • 7,300
  • 10
  • 40
  • 88
supercat
  • 77,689
  • 9
  • 166
  • 211
  • I've updated my question for clarity. Take a look and tell me what you think. – Nathan Ridley Jul 08 '11 at 07:08
  • @Nathan Ridley: I think one of your difficulties may be that generic class parameters can't be covariant, which is to say that if something affects a Foo, it will not accept a Foo, even if TT inherits T. Although ja72 offers a solution in this case, I would suggest that a more versatile approach is to use interfaces, which can be covariant. If a routine expects an ISelf, it will also accept an ISelf for any TT which inherits from T. – supercat Jul 08 '11 at 20:36
  • Your example code is broken (the ISelf definition is not valid C#). Did you mean something like the following? `interface ISelf where T : ISelf { }` `interface IThis : ISelf> { }` `interface IThat : ISelf> { }` `interface ITheOtherThing : ISelf> { }` `interface IThisThat : IThis, IThat { }` – Glenn Slayden Jul 26 '12 at 01:20
  • @GlennSlayden: On first line I put the "out" in the wrong place. For the other lines, the idea is that a non-generic `IThis` would also have the generic `IThis` whose purpose is to incorporate `ISelf`. A typical implementation `Foo` of `IThis` and `IThat` would implement `IThis`, and, in so doing, also automatically impliment `IThis>`, `IThis>>>>`, etc. for any possible nesting sequence. – supercat Jul 26 '12 at 14:35
  • @GlennSlayden: Basically, the idea would be that a class which simply needed an `IThis` would just use the non-generic form, but implementers would implement `IThis` (which wouldn't require implementing any members beyond those of `ISelf` and `IThis`). Note that it this interface does not imply that an instance of `IThis` will in fact implement both `IThis` and `IThat` (it might implement `IThis`, and have a `ISelf.Self` property that returns some *other object instance* which implements `IThat`), such an implementation would go against the interface contract. – supercat Jul 26 '12 at 14:42
  • @GlennSlayden: The fact that a consumer of an interface might get flummoxed if it's passed a screwy implementation should not generally be considered a flaw with an interface design. Indeed, *any* non-trivial public interface will carry with it the possibility of broken or malicious implementation (BTW, if I had my druthers, .net would allow interfaces to have `internal` members; consumers of the interface could then expect that, at least in non-full-trust contexts, any and all object instances implementing those interface would be classes defined in its assembly. – supercat Jul 26 '12 at 14:53
  • The problem with your interface definition as shown is that the word 'public' cannot appear inside the definition, and there is no return type specified--if you intend `Self` to be a function, that is. Can you correct the post so that it compiles? That would be the best way for me to understand your idea. Thanks. – Glenn Slayden Jul 27 '12 at 06:42
  • @GlennSlayden: Sorry about the problems. I mostly program in vb.net (which I think is in many ways a better language), but people seem to prefer to discuss things in C#, so thanks for explaining what was wrong. Hopefully I got it right this time. BTW, is there any way to copy/paste code into a `
    ` tag without having to replace all the less-thans with `<` first?  One nice feature of VB is that the `(Of T)` notation doesn't use less-than signs.
    – supercat Jul 27 '12 at 16:03
2
delegate void Bar<T>(Foo<T> foo) where T : Foo<T>, new();

It works great. I tested it.

here is the test code

public abstract class Foo<T> where T :Foo<T> {
    public event Bar<T> Bar;

    public void Test ()
    {
        if (Bar != null)
        {
            Bar (this);
        }
    }
}

public class FooWorld : Foo<FooWorld> {
}

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

class MainClass
{
    public static void Main (string[] args)
    {
        FooWorld fw = new FooWorld ();
        fw.Bar += delegate(Foo<FooWorld> foo) {
            Console.WriteLine ("Bar response to {0}", foo);
        };

        fw.Test ();
    }
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133