Как передать дочерний тип в универсальный в родительском классе C#

#c# #oop #generics

#c# #ооп #общие

Вопрос:

В настоящее время я использую стороннюю dll и не имею возможности отключить ее или не использовать. У меня есть класс, который выглядит следующим образом:

 public abstract class ParentClass<T> : ApiClass<T> where T : ApiClass<T>
{
    //...stuff
}
 

Это вызывает несколько проблем, если я хочу создать словарь или вернуть тип ParentClass, чтобы мне не нужно было заранее знать, какой это дочерний класс. Чтобы упростить разработку вокруг этого, я хотел бы сделать что-то вроде этого:

 //ChildClass should be what is passed to the generic, without directly referencing it
public abstract class ParentClass : ApiClass<InheritedClass>
{
    //...stuff
}

public class ChildClass : ParentClass
{

}

//Now I could define a dictionary like
Dictionary<KeyClass, ParentClass> Map;
 

Определение его, как во втором примере, позволило бы мне создать словарь, как и в первом. Я не уверен, что смогу определить словарь, когда у меня нет значения для общего? Немного больше информации о ApiClass, определение для ApiClass выглядит следующим образом:

 public abstract class ApiClass<Wrapper> : OtherApiClass where Wrapper : ApiClass<Wrapper>
{
    //...Stuff
}
 

Итак, в подобном сценарии, как бы я определил свой родительский класс без общего, но где тип, передаваемый ApiClass, является дочерним классом родительского класса (ApiClass явно нуждается в дочернем типе)?

Редактировать: немного прояснено значение InheritedClass

Редактирование 2: я не ищу альтернативы, я спрашиваю, как это сделать. Если я передам что-либо, кроме последнего унаследованного класса (текущего родительского класса, который я определил в начале вопроса), произойдет сбой Api. Если бы синтаксис существовал, он выглядел бы примерно так

 public abstract class ParentClass : ApiClass<typeof(this)>
 

Комментарии:

1. Корень ваших проблем, похоже, вызван определением class ApiClass<T> where T : ApiClass<T> . Это очень необычное определение. Это класс, который вы написали, или это что-то определенное в сторонней библиотеке?

2. Это определяется сторонней библиотекой, и я бы действительно предпочел, чтобы ее не существовало. К сожалению, для того, чтобы мой родительский класс или любой его дочерний элемент могли правильно загружаться в их приложение, он должен следовать этому ограничению, при котором он передает себя или дочерний класс в качестве общего. У меня также нет или нет доступа к исходному коду для него.

3. Исходя из этого определения, я предполагаю, что вы могли бы просто сохранить a Dictionary<KeyClass, OtherApiClass> и привести значение к ParentClass<T> тому, когда вам нужна конкретная функциональность

4. Это может не помочь. Вероятно, это не поможет. Хорошо, это не поможет. scotthannen.org/blog/2018/04/05 /…

5. @andrewwwilliamson на самом деле, я не уверен, почему я не подумал просто использовать OtherApiClass в словаре вместо этого. Это, вероятно, решает все проблемы, которые мне нужны для этого, поскольку мне в основном нужно ссылаться на него в функциях и словарях. Спасибо! Я просто оставлю класс как есть, даже если захочу его изменить. В любом случае, это в основном ограничение из-за зависимости.

Ответ №1:

Я полагаю, что сторонняя библиотека, которую вы используете, определяет ApiClass следующим образом:

 public abstract class ApiClass<T> where T : ApiClass<T>
 

Это пример любопытно повторяющегося шаблона шаблона.

Давайте рассмотрим это на конкретном примере:

 public abstract class ApiClass<T> where T : ApiClass<T>
{
    public abstract T GetNewInstance();
}
 

Если бы у меня был этот код, я мог бы использовать его следующим образом:

 public class MyApiClass : ApiClass<MyApiClass>
{
    public override MyApiClass GetNewInstance()
    {
        return new MyApiClass();
    }
}
 

Это позволяет MyApiClass иметь метод, определенный как абстрактный метод в этом родительском классе, который знает о текущем экземпляре. Эрик Липперт обсуждает это здесь .

Недостатком этого является то, что им можно злоупотреблять:

 public class MyNefariousApiClass : ApiClass<MyApiClass>
{
    public override MyApiClass GetNewInstance()
    {
        throw new NotImplementedException();
    }
}
 

Это законно, но в C # нет способа принудительно использовать сам класс в качестве универсального параметра. Если бы у нас class MyNefariousApiClass : ApiClass<this> это было, но у нас этого нет.

Теперь, когда вы говорите, что у вас есть класс public abstract class ParentClass<T> : ApiClass<T> where T : ApiClass<T> , который является вашим кодом для использования сторонней библиотеки.

Если это так, то это не обычный способ использования этого шаблона. Если вы переопределяете класс, он должен выглядеть так:

 public abstract class ParentClass<T> : ApiClass<T> where T : ParentClass<T>
 

Затем мы можем довести это до конечной точки и определить ChildClass примерно так:

 public class ChildClass : ParentClass<ChildClass>
{ }
 

Теперь, если вам нужен словарь, который может содержать любой такой ChildClass , вы застряли, поскольку родительский тип является общим. Единственное разумное решение — использовать интерфейс.

 public interface IParent
{ }

public abstract class ParentClass<T> : ApiClass<T>, IParent where T : ParentClass<T> 
{ }
 

Затем вы можете написать Dictionary<KeyClass, IParent> dictionary = new ... .

Комментарии:

1. Не уверен, что это действительно что-то решает, так как теперь мне нужен интерфейс с нулевыми функциями, просто чтобы указать его в словаре. Как @andrewwwilliamson в комментариях, поскольку ApiClass наследует OtherApiClass, я могу просто указать OtherApiClass . Это не помогает мне решить проблему с шаблоном, но если у нас не было чего-то подобного class Parent : ApiClass<T> where T : ApiClass<typeof(this)> , я действительно не вижу способа обойти это. Не уверен, что я получу, указав ParentClass<T> вместо ApiClass<T> в общем? Есть ли польза в этом?

2. @VextaRelos — Цель этого шаблона — позволить конечному классу возвращать экземпляр самого себя. Если вы придерживаетесь ApiClass<T> этого, вы не сможете этого сделать. Возможно, у вас другие потребности, но именно поэтому они выбрали этот шаблон в первую очередь. И я ясно дал понять, что вы можете подорвать это, потому что C # не позволяет class Parent : ApiClass<T> where T : this .

3. Я немного обеспокоен двумя отрицательными голосами. Я думал, что это было довольно четкое обсуждение рассматриваемого шаблона и того факта, что вы не можете делать то, что хотите, и вам нужно договориться с интерфейсом — другого пути нет.

4. Не уверен, кто проголосовал против, но да, похоже, на самом деле нет возможности делать то, что мне нужно. Дело не в том, что я хочу передать <typeof(this)> в ApiClass , дело в том, что ApiClass буквально приведет к сбою программы, если я этого не сделаю. Мой проект выполняется как дополнение к этому Api. У меня нет другого варианта определения класса.

5. @VextaRelos — В вашем вопросе должны быть такие детали, включая полный код, чтобы показать, что вам нужно.