这是我的协变接口,其中一种方法返回并枚举,这也是协变的(一如既往)。很简单。
public interface IDataSource<out TData> {
IAsyncEnumerable<TData> StreamData(CancellationToken cancellationToken = default);
}
现在,我想通过使用ValueTuple
向该枚举添加更多数据:
public interface IDataSource<out TData> {
IAsyncEnumerable<(TData data, DateTime timestamp)>> StreamData(CancellationToken cancellationToken);
}
现在,我收到一个编译器错误,指出TData data
必须始终有效。我知道ValueTuple
不是协变的。
好吧,那我就滚动我自己的非常特殊的元组,因为我真的很想以后可以在foreach
中使用这种不错的解构语法:
public interface IDataSource<out TData> {
IAsyncEnumerable<IDataTuple<TData>> StreamData(CancellationToken cancellationToken);
}
public interface IDataTuple<out TData> {
void Deconstruct(out TData data, out DateTime timestamp);
}
但是编译器仍然拒绝。这会导致相同的错误,现在在out TData data
上。
好的编译器,您,您对此有何看法:
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
}
}
它可以编译并像魅力一样工作。但为什么? 在这里将Deconstruct
声明为扩展方法而不是接口方法有什么区别?协方差在哪里停止并且不变性在哪里开始?
此外, out
参数是否应协变有效?我的意思是,它实际上是相同的关键字。这对我来说很有意义。