Предотвращение InvalidOperationException при использовании ToTask на IObservable - PullRequest
0 голосов
/ 10 июля 2019

Мне нужно как-то предотвратить InvalidOperationException, который выбрасывается при вызове ToTask в Observable, который не производит никаких значений.

Что я видел, так это то, что метод ToTask создает экземпляр ToTaskObserver и хочет, чтобы по крайней мере одно значение не выдало исключение:

private sealed class ToTaskObserver<TResult> : SafeObserver<TResult>
{
    //[...]
    private bool _hasValue;
    private TResult _lastValue;

    public ToTaskObserver(TaskCompletionSource<TResult> tcs, CancellationToken ct)
    {
       //[...]
    }

    public override void OnNext(TResult value)
    {
       _hasValue = true;
       _lastValue = value;
    }

    //[...]

    public override void OnCompleted()
    {
        if (_hasValue)
        {
            _tcs.TrySetResult(_lastValue);
        }
        else
        {
           _tcs.TrySetException(new InvalidOperationException(Strings_Linq.NO_ELEMENTS));
        }
        //[...]
    }
    //[...]
}

Единственное решение, которое я нашел, это имитировать метод расширения и вставлять фиктивную запись в Observable путем слияния:

public static Task<Record<TKey, TValue>> ToTask<TKey, TValue>(
    this IObservable<Record<TKey, TValue>> source, CancellationToken token)
{
   var pseudoObservable = new[] {Record.Create<TKey, TValue>(default, default)}.ToObservable();
   return source.Merge(pseudoObservable).ToTask(token);
}

Этот вопрос особенно касается метода ToTask. Я знаю, что когда я буду использовать метод Subscribe, у меня не будет этой проблемы.

У кого-нибудь есть лучшее решение для этого? У меня есть сценарии, в которых моя наблюдаемая не будет иметь никаких записей.

1 Ответ

0 голосов
/ 10 июля 2019

Здесь есть несколько вариантов:

  • Если вы ожидаете несколько записей (а вы, вероятно, нет, потому что ожидание их не работает хорошо), тогда вы можете использовать .ToList().
  • Если вы ожидаете 0 или 1 записей, а default(T) не является допустимым значением, вы можете использовать .LastOrDefaultAsync(), затем проверить значение по умолчанию.
  • Если вы 'ожидая 0 или 1 записей и default(T) является допустимым значением, вы все равно можете использовать .ToList(), или вы можете использовать класс стиля Option:

класс Option будет выглядетьвот так, если только я не забыл о встроенной опции для C # (у F # она есть).

public class Optional<T>
{
    private readonly bool _hasValue;
    private readonly T _value;
    public Optional(T t)
    {
        _value = t;
        _hasValue = true;
    }

    public Optional()
    {
        _hasValue = false;
        _value = default(T);
    }

    public bool HasValue => _hasValue;
    public T Value 
    {
        get 
        {
            if(HasValue)
                return _value;
            else
                throw new InvalidOperationException("No value present");
        }
    }
}

public static class X { 
    public static IObservable<Optional<T>> ToOptional<T>(this IObservable<T> source)
    {
        return source.Publish(_source => _source
            .Select(t => new Optional<T>(t))
            .LastOrDefaultAsync()
            .Select(n => n.Equals(default(Optional<T>)) ? new Optional<T>() : n)
        );
    }
}

Я предпочитаю опцию Optional вместо List, потому что List выглядит смешно, когдаВы знаете, что есть максимум одно значение.Но до вас.Вот пример использования:

var fortyTwoList = await Observable.Return(42).ToList();
var noneList = await Observable.Empty<int>().ToList();
var fortyTwo = await Observable.Return(42).ToOptional();
var none = await Observable.Empty<int>().ToOptional();

none.Dump();        //Linqpad
fortyTwo.Dump();    //Linqpad
noneList.Dump();        //Linqpad
fortyTwoList.Dump();    //Linqpad
...