This is my covariant interface with one method returning and enumerable which is also covariant (as it always is). Simple.
public interface IDataSource<out TData> {
IAsyncEnumerable<TData> StreamData(CancellationToken cancellationToken = default);
}
Now I want to add more data to that enumerable by making use of a ValueTuple
:
public interface IDataSource<out TData> {
IAsyncEnumerable<(TData data, DateTime timestamp)>> StreamData(CancellationToken cancellationToken);
}
Now I get a compiler error stating that TData data
must be invariantly valid. I see, ValueTuple
is not covariant.
Alright, then I'll just roll my own very special tuple since I really want to be able to use that nice deconstruction syntax in the foreach
later on:
public interface IDataSource<out TData> {
IAsyncEnumerable<IDataTuple<TData>> StreamData(CancellationToken cancellationToken);
}
public interface IDataTuple<out TData> {
void Deconstruct(out TData data, out DateTime timestamp);
}
But the compiler says no, still. This results in the same error, now on out TData data
.
Alright compiler, you, what do you think about this one:
public interface IDataSource<out TData> {
IAsyncEnumerable<IDataTuple<TData>> StreamData(CancellationToken cancellationToken = default);
}
public interface IDataTuple<out TData> {
TData Data { get; }
DateTime Timestamp { get; }
}
public static class DataTupleExtensions {
public static void Deconstruct<TData>(this IDataTuple<TData> tuple, out TData data, out DateTime timestamp) {
data = tuple.Data;
timestamp = tuple.Timestamp;
}
}
public async Task StreamData<TData>(IDataSource<TData> dataSource, CancellationToken cancellationToken = default) {
await foreach (var (data, timestamp) in dataSource.StreamData(cancellationToken)) {
// Do stuff
}
}
This compiles and works like a charm. But why? What is the difference between declaring the Deconstruct
as an extension method rather than an interface method here? Where does covariance stop and invariance begin?
And moreover, shouldn't out
parameters be covariantly valid? I mean, it's literally the same keyword. It would make sense to me.