Как разрешить производные коллекции в универсальном методе

У меня есть несколько классов, представляющих объекты, все из которых имеют границы, описанные полем Прямоугольник единства. (Zone, Room, Structure, Tunnel, Room...)

Эти объекты часто помещаются в коллекции. (List<Zone>, List<Room>...)

Я хочу иметь единственный статический служебный метод, который будет проверять, перекрывает ли один из них какие-либо границы из коллекции таких объектов, без необходимости создания списка с использованием LINQ.

public static bool BoundsOverlapsOtherBounds(Bounds bound, List<Bounds>)

Как мне использовать полиморфизм С#, интерфейсы, ковариантность для достижения этого без необходимости сначала приводить List<Room> или List<Zone> к List<Bounds>?

Мои попытки до сих пор всегда приводили к ошибкам компилятора Невозможно преобразовать X в Y".

Можешь попробовать List<T>

sri harsha 05.07.2019 11:38

Если Zone, Room и т. д. реализуют какой-то общий интерфейс, который выставляет прямоугольные границы, вы можете выполнить сравнение без приведения коллекции. bool BoundsOverlapsOtherBounds<T>(IHasBoundaries bound, IEnumerable<IHasBoundardies> others) where T : IHasBoundaries.

Scott Hannen 05.07.2019 15:58

Можете ли вы поделиться кодом для одного из ваших классов Zone/Room/…? Я предполагал, что все они наследуются от класса Bounds, но, как всегда с предположениями, это может быть не так.

user10608418 05.07.2019 16:24

@ScottHannen, универсальный T, ничего туда не добавляет, поскольку он не используется ни в одном из параметров. Imo его следует использовать в случае, если вы хотите использовать List вместо IEnumerable (IEnumerable будет автоматически работать для общего родителя/интерфейса без необходимости использования дженериков). Итак, для List это будет что-то вроде: BoundsOverlapsOtherBounds<T>(Bounds bound, List<T> others) where T : Bounds. который, как я предполагаю, будет соответствовать решению, предложенному Шри Харшей

user10608418 05.07.2019 16:27

@Knoop - будет выведен общий аргумент. Что касается IEnumerable<T> vs List, это просто привычка. Я должен был просто использовать List, чтобы не добавлять что-то еще сверху. В вопросе не указывалось, что все унаследовано от Bounds. Но это то же самое в любом случае. Если это так, общее решение работает. Или, если вы просто реализуете какой-то интерфейс, раскрывающий границы, он будет работать точно так же.

Scott Hannen 05.07.2019 16:37

@ScottHannen Дело в том, что вы не использовали T ни в одном из параметров, что делало его бесполезным. Код из вашего комментария при использовании List вместо IEnumerable все равно не скомпилируется, если вы накормите его List<Room>. В любом случае ответ, который вы добавили, был таким, каким я пытался сказать, что это нужно сделать, если вы хотите использовать дженерики. Вы правы, что в этом случае нет необходимости указывать T в вызове, и он будет выведен, но я не это имел в виду.

user10608418 05.07.2019 17:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
75
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема в том, что List<Zone> отличается от List<Bounds>. Вы можете добавить Room к List<Bounds>, но не к List<Zone>, поэтому их нельзя преобразовать. Однако я предполагаю, что вы хотите только перебрать список границ, а не изменять коллекцию, для этого вам нужен только IEnumerable вместо List. Поскольку IEnumerable<Zone> действует так же, как IEnumerable<Bounds>, это разрешено. Поэтому, если вы действительно хотите прочитать только элементы параметра bounds, измените подпись на это:

public static bool BoundsOverlapsOtherBounds(Bounds bound, IEnumerable<Bounds> bounds)

который должен принимать любые List из (Zone, Room, …)

Надеюсь это поможет

Ответ принят как подходящий

Поскольку (как подразумевается) все эти типы уже наследуются от Bounds, вам не нужно приводить List<Room> или List<Zone> к List<Bounds>.

Ты можешь это сделать:

bool BoundsOverlapsOtherBounds<T>(Bounds bound, List<T> bounds) where T : Bounds

Общее ограничение означает, что вы можете передать любой List<T> методу, если T реализует или наследует Bounds.

Итак, если у вас есть List<Room>, вы можете передать его методу без явного приведения:

var rooms = new List<Room>();
var otherBounds = new Bounds();
var overlaps = BoundsOverlapsOtherBounds(otherBounds, rooms);

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


Если по какой-то причине эти объекты не имеют общего типа, то, скорее всего, это тот случай, когда они должны. Наследование — это решение а, но нам не нужно использовать его, чтобы типы имели общие характеристики. Иногда это загоняет нас в угол. Интерфейс также может иметь смысл:

interface IHasBoundaries // not a great name?
{
    Boundaries Bounds { get; }
}

Это полиморфизм. Несколько форм (или типы) могут реализовывать интерфейс, и вас совершенно не волнует, чем они отличаются — только то, что у них общего. Вы можете написать код, который имеет дело с IHasBoundaries, и в этом контексте это единственное, что вам нужно знать об этих объектах, что они реализуют интерфейс.

Тогда ваш метод выглядит так:

bool BoundsOverlapsOtherBounds<T>(IHasBoundaries bound, List<T> bounds) 
    where T : IHasBoundaries

Другие вопросы по теме