Странное поведение при деконструкции ковариантных интерфейсов в ValueTuples с помощью методов расширения - PullRequest
0 голосов
/ 24 декабря 2018

Это мой ковариантный интерфейс с одним возвращаемым и перечислимым методом, который также ковариантен (как всегда).Простой.

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 параметры будут ковариантно действительными? Я имею в виду, это буквально одно и то же ключевое слово.Это имело бы смысл для меня.

...