Is it possible to use the generic "in" and "out" modifier on the same T?

Viewed 347

This code is invalid because T can't have the in and out modifier at the same time:

public interface IInOut<in out T>
{

}

But you can do this "workaround":

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();
    }
}

The second example works and the InOutWorkaround class has the same type for TIn and TOut, so why is it not possible to add both modifier to the same T directly in the interface? Or is it possible with a different syntax?

3 Answers

in T says that T can not be used covariantly, and out T says that T can not be used contravariantly. Your in out T would therefore mean that the type can not be used covariantly and can not be used contravariantly, which means it'd be invariant. So in effect that would behave identically to just writing public interface IInOut<T>, because when no in or out modifiers are used the generic type is considered invariant.

In the case of your class InOutWorkaround<T>, T is still invariant, so the fact that you're using it as both an in and out type is fine, because it's invariant, as it meets both restrictions. If you were attempting to have a type that could be used both covariantly and contravariantly, your workaround didn't achieve that, because the T in InOutWorkaround is invariant (because all generic type arguments for all classes are invariant). That generic type argument cannot be used either covariantly or contravariantly.

One could have interfaces 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>, and IReadWrite<T>:ISplitReadWrite<T,T>.

If one has a class MyCollection<T> which implements IReadWrite<T>, then a MyCollection<Cat> could be converted to IReadable<Animal>, IWritable<SiameseCat>, or an ISplitReadWrite<Animal,SiameseCat>. Note, however, that the only IReadable<T> that would yield an item that could be stored into a MyCollection<Cat> would be IReadable<Cat>, the only IWritable<T> that could handle everything that might appear in a MyCollection<Cat> would be IWritable<Cat>. The only forms of ISplitReadWrite<Tout,Tin> that would allow one to read out an item and write it back to the same collection without a cast would be those where the two types were the same, and the only such type implemented by MyCollection<Cat> would be ISplitReadWrite<Cat,Cat>.

Note that one could have an interface with methods that could be equally usable with MyCollection<Animal> and MyCollection<SiameseCat>, such as "swap the items in slots i1 and i2 of the same collection", but such an interface wouldn't need any generic parameter at all. Id one has an IPermutable interface, it could include methods like void swapItems(int i1, int i2); which wouldn't have any generic types in their signatures, and thus wouldn't make it necessary for the type to include any generic type arguments.

According to Extending Variant Generic Interfaces specification

The compiler does not infer the variance from the interface that is being extended. You can create an interface that extends both the interface where the generic type parameter T is covariant and the interface where it is contravariant if in the extending interface the generic type parameter T is invariant.

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

This example looks like as an exactly your case, T is invariant generic type parameter in InOutWorkaround<T> interface, compiler doesn't infer (or inherit it in extending interface), so your workaround is pointless

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