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

本文关键字:泛型类 约束 引用类型 | 更新日期: 2023-09-27 18:01:42

考虑以下代码:

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<T>不可分配给参数类型T

T被约束为Foo<T>因为我希望派生类基本上告诉基类它们的类型,这样就可以在事件回调中使用该类型,从而避免实现者将回调参数强制转换为派生类型。

我可以看到代码不太好用,但我有点受阻,不知道如何在不使用可用于任何旧事物的通用委托的情况下正确地完成这项工作。我也不太确定为什么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();
    }
}

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

您可以将"this"强制转换为T:

Bar((T)this);

然而,如果你有以下情况,这将失败:

public class MyFoo : Foo<MyFoo> { }
public class MyOtherFoo : Foo<MyFoo> { }

因为"MyOtherFoo"不是"MyFoo"的实例。看看这篇由EricLippert撰写的文章,他是C#的设计者之一。

如果不将"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和ITheOtherTing的类也实现ISelf< 他们的OwnTypes>,然后可以有一个例程,其参数(例如"foo"(必须同时实现IThis和接受参数为IThis类型的IThis。参数"foo"的类型将是IThis(进而实现IThis(,而foo.Self的类型将为IThat。请注意,如果以这种方式实现,则可以自由地将变量类型转换为任何所需的接口组合。例如,在上面的示例中,如果作为"foo"传递的对象是实现IThis、IThat、ITheOtherTing和ISelf< itsOwnType>它可以被类型转换为ITheOtherTing>或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也是不相关的。尽管如此,只要实现类遵循指示的模式,方法参数就可能需要接口的任何组合。"组合"接口类型的存储位置只能传递给以相同顺序指定所包含接口的参数。然而,正确实现模式的任何类型的实例都可以按任何顺序(有或没有重复(使用其接口的任何子集被类型转换为任何"组合"接口类型。当与正确实现模式的类的实例一起使用时,类型转换将始终在运行时成功(它们可能会因流氓实现而失败(。

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