Wykonywanie zapytań względem sekwencji obserwowanych przy użyciu operatorów LINQ
W przypadku mostkowania z istniejącymi zdarzeniami platformy .NET przekonwertowaliśmy istniejące zdarzenia platformy .NET na widoczne sekwencje, aby je zasubskrybować. W tym temacie przyjrzymy się pierwszej klasie charakteru obserwowanych sekwencji jako obiektów IObservable<T> , w których ogólne operatory LINQ są dostarczane przez zestawy Rx do manipulowania tymi obiektami. Większość operatorów bierze obserwowaną sekwencję i wykonuje na niej pewną logikę i generuje kolejną możliwą do obserwacji sekwencję. Ponadto, jak widać w naszych przykładach kodu, można nawet połączyć wiele operatorów w sekwencji źródłowej, aby dostosować wynikową sekwencję do dokładnego wymagania.
Używanie różnych operatorów
W poprzednich tematach użyliśmy już operatorów Tworzenia i generowania, aby tworzyć i zwracać proste sekwencje. Użyliśmy również operatora FromEventPattern, aby przekonwertować istniejące zdarzenia platformy .NET na widoczne sekwencje. W tym temacie użyjemy innych statycznych operatorów LINQ typu obserwowanego, aby można było filtrować, grupować i przekształcać dane. Takie operatory przyjmują obserwowane sekwencje jako dane wejściowe i tworzą widoczne sekwencje jako dane wyjściowe.
Łączenie różnych sekwencji
W tej sekcji przeanalizujemy niektóre operatory, które łączą różne obserwowane sekwencje w jedną zauważalną sekwencję. Zwróć uwagę, że dane nie są przekształcane, gdy łączymy sekwencje.
W poniższym przykładzie użyjemy operatora Concat, aby połączyć dwie sekwencje w jedną sekwencję i zasubskrybować ją. Na potrzeby ilustracji użyjemy bardzo prostego operatora Range(x, y), aby utworzyć sekwencję liczb całkowitych rozpoczynających się od x i tworzących liczby sekwencyjne y później.
var source1 = Observable.Range(1, 3);
var source2 = Observable.Range(1, 3);
source1.Concat(source2)
.Subscribe(Console.WriteLine);
Console.ReadLine();
Zwróć uwagę, że wynikowa sekwencja to 1,2,3,1,2,3
. Dzieje się tak, ponieważ w przypadku korzystania z operatora Concat druga sekwencja (source2) nie będzie aktywna, dopóki nie zostanie zakończona sekwencja 1(source1
) wypychając wszystkie jej wartości. Dopiero po source1
zakończeniu source2
rozpocznie się wypychanie wartości do wynikowej sekwencji. Subskrybent uzyska następnie wszystkie wartości z wynikowej sekwencji.
Porównaj to z operatorem Scal. Jeśli uruchomisz następujący przykładowy kod, otrzymasz polecenie 1,1,2,2,3,3
. Wynika to z faktu, że dwie sekwencje są aktywne w tym samym czasie, a wartości są wypychane w miarę ich występowania w źródłach. Wynikowa sekwencja kończy się tylko wtedy, gdy ostatnia sekwencja źródłowa zakończyła wypychanie wartości.
Zwróć uwagę, że aby scalanie działało, wszystkie źródłowe sekwencje widoczne muszą być tego samego typu IObservable<T>. Wynikowa sekwencja będzie typu IObservable<T>. Jeśli source1
tworzy błąd OnError w środku sekwencji, wynikowa sekwencja zostanie zakończona natychmiast.
var source1 = Observable.Range(1, 3);
var source2 = Observable.Range(1, 3);
source1.Merge(source2)
.Subscribe(Console.WriteLine);
Console.ReadLine();
Inne porównanie można wykonać za pomocą operatora Catch. W takim przypadku, jeśli source1
zakończy się bez żadnego błędu, nie source2
zostanie uruchomiona. W związku z tym, jeśli uruchomisz następujący przykładowy kod, otrzymasz 1,2,3
tylko od source2
czasu (co powoduje wygenerowanie 4,5,6
) jest ignorowane.
var source1 = Observable.Range(1, 3);
var source2 = Observable.Range(4, 3);
source1.Catch(source2)
.Subscribe(Console.WriteLine);
Console.ReadLine();
Na koniec przyjrzyjmy się onErrorResumeNext. Ten operator przejdzie dalej, nawet jeśli source1
nie można ukończyć source2
z powodu błędu. W poniższym przykładzie, mimo że source1
reprezentuje sekwencję, która kończy się z wyjątkiem (przy użyciu operatora Throw), subskrybent otrzyma wartości (1,2,3
) opublikowane przez source2
. W związku z tym, jeśli oczekujesz, że każda sekwencja źródłowa spowoduje wygenerowanie błędu, jest to bezpieczniejsze zakłady, aby użyć metody OnErrorResumeNext, aby zagwarantować, że subskrybent nadal otrzyma pewne wartości.
var source1 = Observable.Throw<int>(new Exception("An error has occurred."));
var source2 = Observable.Range(4, 3);
source1.OnErrorResumeNext(source2)
.Subscribe(Console.WriteLine);
Console.ReadLine();
Zwróć uwagę, że aby wszystkie te operatory kombinacji działały, wszystkie widoczne sekwencje muszą być tego samego typu T.
Projekcja
Operator Select może przetłumaczyć każdy element obserwowanej sekwencji na inny formularz.
W poniższym przykładzie projektujemy sekwencję liczb całkowitych odpowiednio na ciągi o długości n.
var seqNum = Observable.Range(1, 5);
var seqString = from n in seqNum
select new string('*', (int)n);
seqString.Subscribe(str => { Console.WriteLine(str); });
Console.ReadKey();
W poniższym przykładzie, który jest rozszerzeniem przykładu konwersji zdarzeń platformy .NET, który widzieliśmy w temacie Mostkowanie z istniejącymi zdarzeniami platformy .NET , użyjemy operatora Select, aby projektować typ danych IEventPattern<MouseEventArgs> w typ punktu . W ten sposób przekształcamy sekwencję zdarzeń przenoszenia myszy na typ danych, który można analizować i manipulować dalej, jak widać w następnej sekcji "Filtrowanie".
var frm = new Form();
IObservable<EventPattern<MouseEventArgs>> move = Observable.FromEventPattern<MouseEventArgs>(frm, "MouseMove");
IObservable<System.Drawing.Point> points = from evt in move
select evt.EventArgs.Location;
points.Subscribe(pos => Console.WriteLine("mouse at " + pos));
Application.Run(frm);
Na koniec przyjrzyjmy się operatorowi SelectMany. Operator SelectMany ma wiele przeciążeń, z których jeden przyjmuje argument funkcji selektora. Ta funkcja selektora jest wywoływana dla każdej wartości wypychanej przez źródło, które można zaobserwować. Dla każdej z tych wartości selektor projektuje go w mini obserwowalnej sekwencji. Na końcu operator SelectMany spłaszcza wszystkie te mini sekwencje w jedną wynikową sekwencję, która następnie jest wypychana do subskrybenta.
Możliwe do obserwowania zwrócone z SelectMany publikuje OnCompleted po sekwencji źródłowej i wszystkie miniobserwowalne sekwencje utworzone przez selektor zostały ukończone. Uruchamia błąd OnError, gdy wystąpił błąd w strumieniu źródłowym, gdy wyjątek został zgłoszony przez funkcję selektora lub gdy wystąpił błąd w dowolnej z mini obserwowanych sekwencji.
W poniższym przykładzie najpierw utworzymy sekwencję źródłową, która tworzy liczbę całkowitą co 5 sekund, i decydujemy się na utworzenie pierwszych 2 wartości (przy użyciu operatora Take). Następnie użyjemy polecenia SelectMany
, aby projektować każdą z tych liczb całkowitych przy użyciu innej sekwencji {100, 101, 102}
. Dzięki temu są tworzone {100, 101, 102}
dwie minioserwowalne sekwencje i {100, 101, 102}
. Są one w końcu spłaszczone w jeden strumień liczb całkowitych {100, 101, 102, 100, 101, 102}
i wypychane do obserwatora.
var source1 = Observable.Interval(TimeSpan.FromSeconds(5)).Take(2);
var proj = Observable.Range(100, 3);
var resultSeq = source1.SelectMany(proj);
var sub = resultSeq.Subscribe(x => Console.WriteLine("OnNext : {0}", x.ToString()),
ex => Console.WriteLine("Error : {0}", ex.ToString()),
() => Console.WriteLine("Completed"));
Console.ReadKey();
Filtrowanie
W poniższym przykładzie użyjemy operatora Generate (Generowanie), aby utworzyć prostą, zauważalną sekwencję liczb. Operator Generate ma kilka przeciążeń. W naszym przykładzie przyjmuje stan początkowy (0 w naszym przykładzie), funkcję warunkową do zakończenia (mniej niż 10 razy), iterator (+1), selektor wyników (funkcja kwadratowa bieżącej wartości). i wyświetlać tylko te mniejsze niż 15 przy użyciu operatorów Where i Select.
IObservable<int> seq = Observable.Generate(0, i => i < 10, i => i + 1, i => i * i);
IObservable<int> source = from n in seq
where n < 5
select n;
source.Subscribe(x => {Console.WriteLine(x);}); // output is 0, 1, 4, 9
Console.ReadKey();
Poniższy przykład to rozszerzenie przykładu projekcji, które przedstawiono wcześniej w tym temacie. W tym przykładzie użyliśmy operatora Select, aby projektować typ danych IEventPattern<MouseEventArgs> w typ punktu . W poniższym przykładzie użyjemy operatora Where and Select, aby wybrać tylko te ruchy myszy, które nas interesują. W takim przypadku filtrujemy ruch myszy do tych nad pierwszym dwusektorem (gdzie współrzędne x i y są równe).
var frm = new Form();
IObservable<EventPattern<MouseEventArgs>> move = Observable.FromEventPattern<MouseEventArgs>(frm, "MouseMove");
IObservable<System.Drawing.Point> points = from evt in move
select evt.EventArgs.Location;
var overfirstbisector = from pos in points
where pos.X == pos.Y
select pos;
var movesub = overfirstbisector.Subscribe(pos => Console.WriteLine("mouse at " + pos));
Application.Run(frm);
Operacja oparta na czasie
Operatory buforu umożliwiają wykonywanie operacji opartych na czasie.
Buforowanie sekwencji możliwej do obserwacji oznacza, że wartości sekwencji obserwowanej są umieszczane w buforze na podstawie określonego przedziału czasu lub przez próg liczby. Jest to szczególnie przydatne w sytuacjach, gdy spodziewasz się wypchnięcia ogromnej ilości danych przez sekwencję, a subskrybent nie ma zasobu do przetworzenia tych wartości. Buforując wyniki na podstawie czasu lub liczby i zwracając tylko sekwencję wartości po przekroczeniu kryteriów (lub po zakończeniu sekwencji źródłowej), subskrybent może przetwarzać wywołania OnNext we własnym tempie.
W poniższym przykładzie najpierw utworzymy prostą sekwencję liczb całkowitych dla każdej sekundy. Następnie użyjemy operatora Bufor i określimy, że każdy bufor będzie zawierać 5 elementów z sekwencji. OnNext jest wywoływany, gdy bufor jest pełny. Następnie obliczamy sumę buforu przy użyciu operatora Sum. Bufor jest automatycznie opróżniany i rozpoczyna się kolejny cykl. Wydruk będzie, 10, 35, 60…
w którym 10=0+1+2+3+4, 35=5+6+7+8+9 itd.
var seq = Observable.Interval(TimeSpan.FromSeconds(1));
var bufSeq = seq.Buffer(5);
bufSeq.Subscribe(values => Console.WriteLine(values.Sum()));
Console.ReadKey();
Możemy również utworzyć bufor z określonym przedziałem czasu. W poniższym przykładzie bufor będzie przechowywać elementy, które zostały skumulowane przez 3 sekundy. Wydruk będzie 3, 12, 21... w którym 3=0+1+2, 12=3+4+5 itd.
var seq = Observable.Interval(TimeSpan.FromSeconds(1));
var bufSeq = seq.Buffer(TimeSpan.FromSeconds(3));
bufSeq.Subscribe(value => Console.WriteLine(value.Sum()));
Console.ReadKey();
Pamiętaj, że jeśli używasz buforu lub okna, musisz upewnić się, że sekwencja nie jest pusta przed jego filtrowaniem.
Operatory LINQ według kategorii
Temat Operatory LINQ według kategorii zawiera listę wszystkich głównych operatorów LINQ zaimplementowanych przez typ obserwowany według ich kategorii; w szczególności: tworzenie, konwersja, łączenie, funkcjonalne, matematyczne, czas, wyjątki, różne, wybór i pierwotny.