Можно ли использовать модификаторы generi c "in" и "out" на одном и том же T? - PullRequest
1 голос
/ 26 мая 2020

Этот код недействителен, потому что T не может иметь модификатор in и out одновременно:

public interface IInOut<in out T>
{

}

Но вы можете сделать это «обходное решение»:

public interface IInOutWorkaround<in TIn, out TOut>
{
    TOut Test(TIn value);
}

public class InOutWorkaround<T> : IInOutWorkaround<T, T>
{
    public T Test(T value)
    {
        throw new NotImplementedException();
    }
}

Второй пример работает, и класс InOutWorkaround имеет один и тот же тип для TIn и TOut, так почему же нельзя добавить оба модификатора к одному и тому же T непосредственно в интерфейсе? Или возможно с другим синтаксисом?

Ответы [ 3 ]

5 голосов
/ 26 мая 2020

in T говорит, что T может не использоваться ковариантно, а out T говорит, что T может не использоваться контравариантно. Таким образом, ваш in out T будет означать, что тип может не использоваться ковариантно и может не использоваться контравариантно, что означает, что он будет инвариантным . Фактически это будет вести себя идентично простой записи public interface IInOut<T>, потому что, когда не используются модификаторы in или out, общий тип c считается инвариантным .

В случай вашего класса InOutWorkaround<T>, T по-прежнему инвариантен, поэтому тот факт, что вы используете его как тип in, так и out, нормально, потому что он инвариантен, поскольку соответствует обоим ограничениям. Если вы пытались создать тип, который мог бы использоваться как ковариантно, так и контравариантно, ваш обходной путь не достиг бы этого, потому что T в InOutWorkaround является инвариантным (потому что все аргументы типа generi c для всех классов инвариантны). Этот аргумент обобщенного типа c нельзя использовать ни ковариантно, ни контравариантно.

2 голосов
/ 26 мая 2020

Могут быть интерфейсы IReadable<out T> { T read(int index); }, IWritable<in T> { void write(int index, T dat);, ISplitReadWrite<out Tout, in Tin>:IReadable<Tout>,IWritable<Tin> и IReadWrite<T>:ISplitReadWrite<T,T>.

Если есть класс MyCollection<T>, реализующий IReadWrite<T>, тогда MyCollection<Cat> может быть преобразованным в IReadable<Animal>, IWritable<SiameseCat> или ISplitReadWrite<Animal,SiameseCat>. Обратите внимание, однако, что единственный IReadable<T>, который даст элемент, который может быть сохранен в MyCollection<Cat>, будет IReadable<Cat>, единственный IWritable<T>, который может обработать все, что может появиться в MyCollection<Cat>, будет IWritable<Cat>. Единственными формами ISplitReadWrite<Tout,Tin>, которые позволили бы считывать элемент и записывать его обратно в ту же коллекцию без приведения, были бы те, в которых два типа были одинаковыми, и был реализован единственный такой тип by MyCollection<Cat> будет ISplitReadWrite<Cat,Cat>.

Обратите внимание, что можно иметь интерфейс с методами, которые можно было бы одинаково использовать с MyCollection<Animal> и MyCollection<SiameseCat>, например «поменять местами элементы в слотах i1 и i2. из той же коллекции », но такой интерфейс вообще не нуждается в параметре generi c . Id one имеет интерфейс IPermutable, он может включать такие методы, как void swapItems(int i1, int i2);, в сигнатурах которых не будет никаких типов generi c, и, следовательно, не потребуется включать в тип какие-либо общие c аргументы типа.

1 голос
/ 26 мая 2020

Согласно Generi расширяющего варианта c Интерфейсы спецификация

Компилятор не делает вывод об отклонении от интерфейса, который расширяется. Вы можете создать интерфейс, который расширяет как интерфейс, в котором параметр типа generi c T является ковариантным, так и интерфейс, в котором он является контравариантным, если в расширяемом интерфейсе параметр типа generi c T является инвариантным.

interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

Этот пример выглядит как в точности ваш случай, T является инвариантным обобщенным параметром типа c в интерфейсе InOutWorkaround<T>, компилятор не выводит (или не наследует его в расширяющем интерфейсе) , поэтому обходной путь бессмысленен

public class InOutWorkaround<T> : IInOutWorkaround<T, T>
{
    public T Test(T value)
    {
        throw new NotImplementedException();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...