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