Я пытался понять, как получить эквивалент следующего кода 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, но я пока не нашел решения, которое бы работало.
Первый звучит правильно. Есть ли значимая разница между использованием ковариации и использованием оператора where
?
Ковариация - это немного другое. IEnumerable
объявляется как IEnumerable<out T> { ... }
, что позволяет писать, например. IEnumerable<object> x = new List<string>()
- т. е. он сообщает компилятору, что можно безопасно обращаться к List<string>
как к коллекции любых старых объектов, потому что вы можете только вынимать элементы из коллекции, а не помещать их.
Я думаю, это имеет смысл? В любом случае ваше решение похоже на то, что мне нужно. Я бы с радостью принял это как ответ
Я думаю, что вам нужен ограничение универсального типа:
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
объекты и никогда их не возвращать.
Ковариация - это своего рода концепция ООП, она не уникальна для С#.
@CoderinoJavarino Я знаю. Вот почему я сказал: «Ковариантность в C# — это...» — я говорю, что конкретно означает ковариантность в C#, как это определено в документации по C#. Обновил формулировку, надеюсь, будет понятнее.
Хотели
interface IExecutor<T> where T : Command { void Execute(T command); }
? Хотя в вашем примере вы можете просто сделатьinterface IExecutor { void Execute(Command command); }
(обратите внимание, что это не ковариация. Ковариация интерфейса позволяет вам писатьIEnumerable<object> x = new List<string>()
, что немного отличается)