Подтвердить что ты не робот

Общий класс с ограничением типа самореференции

Рассмотрим следующий код:

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

Ответ 1

Вы можете наложить 'this' на T:

Bar((T)this);

Однако это приведет к ошибке, если у вас есть следующее:

public class MyFoo : Foo<MyFoo> { }

public class MyOtherFoo : Foo<MyFoo> { }

Потому что "MyOtherFoo" не является экземпляром "MyFoo". Взгляните на этот пост Эрика Липперта, одного из разработчиков С#.

Ответ 2

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

Ответ 3

Код будет более понятным, если вы не использовали "Бар" для двух целей. Это было сказано, я думаю, что нужно использовать общий с двумя параметрами (например, 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 < theirOwnTypes > , тогда можно иметь подпрограмму, параметр которой (например, "foo" ) должен реализовывать как IThis, так и IThat, которые принимают параметр как тип IThis. Параметр "foo" будет иметь тип IThis (который, в свою очередь, реализует IThis), а Foo.Self будет иметь тип IThat. Обратите внимание, что если все будет реализовано таким образом, можно свободно преобразовать переменные в любую желаемую комбинацию интерфейсов. Например, в приведенном выше примере, если объект, переданный как "foo" , был типом, который реализовал IThis, IThat, ITheOtherThing и ISelf < itsOwnType > , он мог бы быть typecast для ITheOtherThing > или IThis, или любую другую желаемую комбинацию и расположение этих интерфейсов.

На самом деле довольно универсальный трюк.

Edit/Добавление

Вот несколько более полный пример.

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, а некоторые методы требуют разных комбинаций. Четыре нестатических класса, приведенные выше, не связаны друг с другом, как и интерфейсы IThis, IThat и IOther. Тем не менее, параметры метода могут потребовать любую комбинацию интерфейсов, если реализующие классы следуют указанному шаблону. Место хранения "комбинированного" типа интерфейса может передаваться только параметрам, которые определяют входящие интерфейсы в том же порядке. Тем не менее, экземпляр любого типа, который правильно реализует шаблон, может быть типичным для любого "комбинированного" типа интерфейса с использованием любого подмножества его интерфейсов в любом порядке (с дубликатами или без них). При использовании с экземплярами классов, которые должным образом реализуют шаблон, машинные приемы всегда будут успешными во время выполнения (они могут завершиться неудачей с реализацией изгоев).