使用扩展方法将协变接口解构为ValueTuples时的行为怪异

时间:2018-12-24 14:55:14

标签: c# tuples covariance variance valuetuple

这是我的协变接口,其中一种方法返回并枚举,这也是协变的(一如既往)。很简单。

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参数是否应协变有效?我的意思是,它实际上是相同的关键字。这对我来说很有意义。

0 个答案:

没有答案