具有自引用类型约束的泛型类

时间:2011-07-07 22:59:26

标签: c# generics

请考虑以下代码:

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();

Bar(this)导致以下编译器错误:
参数类型Foo&lt; T&gt;不能分配给参数类型T

T被约束为Foo&lt; T&gt;。因为我希望派生类基本上告诉基类它们的类型,以便可以在事件回调中使用该类型,以便保存实现者不必将回调参数强制转换为派生类型。

我可以看到代码不能正常工作,但我对如何正确执行此操作有一点障碍,而不会使用可用于任何旧事物的泛型委托。我也不太确定为什么T约束不会产生编译器错误,因为它似乎是递归的。

修改

我认为我需要澄清这一点!这是一个新的例子,我希望会更清楚。请注意,下面的OnDuckReady事件处理程序会生成编译器错误。

如何让事件以正确的类型传递?

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();
    }
}

3 个答案:

答案 0 :(得分:16)

您可以将'this'投射到T:

Bar((T)this);

如果您有以下内容,则会失败:

public class MyFoo : Foo<MyFoo> { }

public class MyOtherFoo : Foo<MyFoo> { }

因为'MyOtherFoo'不是'MyFoo'的实例。请看一下C#设计师之一Eric Lippert的this帖子。

答案 1 :(得分:3)

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

效果很好。我测试了它。

这是测试代码

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 ();
    }
}

答案 2 :(得分:2)

如果您没有将“Bar”用于两个目的,代码会更清晰。已经说过,我认为需要的是使用具有两个参数(例如T和U)的泛型,使得T从U派生,并且U派生自Foo。或者,可以使用接口做一些不错的事情。一个有用的模式是定义:

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

然后,对于可能想要在对象中组合的各种接口:

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

如果实现IThis,IThat和ITheOtherThing的类也实现了ISelf&lt; hisOwnTypes &gt;,则可以有一个例程,其参数(例如“foo”)必须同时实现IThis和IThat接受参数类型为IThis。参数“foo”将是IThis类型(反过来实现IThis),而Foo.Self将是IThat类型。请注意,如果以这种方式实现,可以自由地将变量类型转换为任何所需的接口组合。例如,在上面的例子中,如果作为“foo”传递的对象是实现了IThis,IThat,ITheOtherThing和ISelf&lt; itsOwnType &gt;的类型。它可以是ITheOtherThing&gt;,或IThis,或这些接口的任何其他所需组合和排列。

真的是一个多才多艺的技巧。

修改/附录

这是一个更完整的例子。

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);
        }
    }
}

需要注意的是,有些类实现了IThis,IThat和IOther的不同组合,有些方法需要不同的组合。上面给出的四个非静态类都是无关的,接口IThisIThatIOther也是如此。尽管如此,如果实现类遵循指示的模式,则方法参数可能需要接口的任何组合。 “组合”接口类型的存储位置可以仅传递给以相同顺序指定所包括的接口的参数。然而,任何正确实现模式的类型的实例可以使用任何顺序的任何接口子集(有或没有重复)对任何“组合”接口类型进行类型转换。当与正确实现模式的类的实例一起使用时,类型转换将始终在运行时成功(它们可能会因恶意实现而失败)。