Ковариация интерфейса C#, укажите тип параметра

Я пытался понять, как получить эквивалент следующего кода Java на С# (команда - это функциональный интерфейс).

public interface Executor<C extends Command> {
    void execute(final C command) throws Exception;
}

То, как мой код в настоящее время разработан в версии Java, необходимо, чтобы тип C расширял команду, которая, насколько я понимаю, обрабатывается с ковариацией в C#.

Однако, согласно документам С#, что-то вроде следующего не будет работать, потому что "Тип используется только как возвращаемый тип методов интерфейса и не используется как тип аргументов метода."

interface IExecutor<out Command>
{
    void Execute(Command command);
}

Есть ли способ указать, что тип параметра для метода должен быть ковариантным для типа интерфейса в С#?

Я относительно новичок в C#, поэтому может быть, что это проблема XY, но я пока не нашел решения, которое бы работало.

Хотели interface IExecutor<T> where T : Command { void Execute(T command); }? Хотя в вашем примере вы можете просто сделать interface IExecutor { void Execute(Command command); } (обратите внимание, что это не ковариация. Ковариация интерфейса позволяет вам писать IEnumerable<object> x = new List<string>(), что немного отличается)

canton7 24.05.2019 14:10

Первый звучит правильно. Есть ли значимая разница между использованием ковариации и использованием оператора where?

J Lewis 24.05.2019 14:13

Ковариация - это немного другое. IEnumerable объявляется как IEnumerable<out T> { ... }, что позволяет писать, например. IEnumerable<object> x = new List<string>() - т. е. он сообщает компилятору, что можно безопасно обращаться к List<string> как к коллекции любых старых объектов, потому что вы можете только вынимать элементы из коллекции, а не помещать их.

canton7 24.05.2019 14:14

Я думаю, это имеет смысл? В любом случае ваше решение похоже на то, что мне нужно. Я бы с радостью принял это как ответ

J Lewis 24.05.2019 14:16
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
42
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Я думаю, что вам нужен ограничение универсального типа:

interface IExecutor<T> where T : Command
{
    void Execute(T command);
}

Это говорит о том, что T может быть чем угодно, если он расширяет класс Command.

Ковариация в C# немного отличается от этого и касается преобразований между разными типами (массивами, дженериками и делегатами).

Например, IEnumerable объявлен как IEnumerable<out T> { ... }, что делает его ковариантным. Это обещание компилятору, что вы будете брать только элементы вне из IEnumerable<T> и никогда не вставлять их.

Это означает, что безопасно писать, например.

IEnumerable<object> x = new List<string>();

Поскольку из IEnumerable можно вынимать только строки, можно с уверенностью притвориться, что все они являются объектами. Если бы вам было разрешено помещать элементы в IEnumerable, вы могли бы поместить любой старый объект в коллекцию, которая допускает только string, что было бы небезопасно.

Возьмем ваш пример, поскольку вы когда-либо добавляли Commands в свой IExecutor, вы можете объявить его контравариантным:

interface IExecutor<in T> where T : Command
{
    void Execute(T command);
}

Это позволит вам написать:

IExecutor<Command> baseExecutor = ....;
IExecutor<SpecialisedCommand> executor = baseExecutor;

Это безопасно, потому что вы пообещали компилятору, что методы в IExecutor будут принимать только Command объекты и никогда их не возвращать.

Ковариация - это своего рода концепция ООП, она не уникальна для С#.

Coderino Javarino 24.05.2019 14:21

@CoderinoJavarino Я знаю. Вот почему я сказал: «Ковариантность в C# — это...» — я говорю, что конкретно означает ковариантность в C#, как это определено в документации по C#. Обновил формулировку, надеюсь, будет понятнее.

canton7 24.05.2019 14:22

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

EF сериализует объект в json с включенными связанными объектами, создавая цикл
Как заставить кнопки возвращаться в исходное состояние после серии нажатий
Преобразование сложного json в объект С#
Как найти в строке общее имя в двух столбцах, разных таблицах?
Как загрузить сборки в разные каталоги в ядре dotnet?
Десериализовать список объектов без всеобъемлющего объекта
Как создать интерфейс для набора классов с практически одинаковым функционалом, но с разными параметрами и типами возвращаемых значений?
Как десериализовать json, где тип свойств не фиксирован - может быть пустой строкой или объектом, пожалуйста, предложите. как справиться с этой ситуацией?
Делегат устарел в iOS 12.0. Пожалуйста, используйте WKWebView. Как исправить это в Xamarin?
Есть ли способ «сказать» IntelliSense и компилятору, что класс реализует интерфейс, фактически не реализуя его?