Sdílet prostřednictvím


Dotazování pozorovatelných sekvencí pomocí operátorů LINQ

V části Přemostění s existujícími událostmi .NET jsme převedli existující události .NET na pozorovatelné sekvence, abychom je mohli přihlásit k odběru. V tomto tématu se podíváme na prvotřídní povahu pozorovatelných sekvencí jako objekty IObservable<T> , ve kterých jsou obecné operátory LINQ poskytovány sestaveními Rx pro manipulaci s těmito objekty. Většina operátorů vezme pozorovatelnou sekvenci, provede s ní určitou logiku a vypíše další pozorovatelnou sekvenci. Kromě toho, jak je vidět z našich ukázek kódu, můžete dokonce zřetězet více operátorů na zdrojové sekvenci, abyste výslednou sekvenci upravili přesně podle svých požadavků.

Použití různých operátorů

K vytvoření a vrácení jednoduchých sekvencí jsme už použili operátory Create a Generate v předchozích tématech. K převodu existujících událostí .NET na pozorovatelné sekvence jsme také použili operátor FromEventPattern. V tomto tématu použijeme další statické operátory LINQ pozorovatelného typu, abyste mohli filtrovat, seskupovat a transformovat data. Tyto operátory přebírají pozorovatelné sekvence jako vstup a vytvářejí pozorovatelné sekvence jako výstup.

Kombinování různých sekvencí

V této části prozkoumáme některé operátory, které kombinují různé pozorovatelné sekvence do jedné pozorovatelné sekvence. Všimněte si, že data se při kombinování sekvencí netransformují.

V následující ukázce použijeme operátor Concat ke kombinování dvou sekvencí do jedné a přihlášení k odběru. Pro ilustraci použijeme velmi jednoduchý operátor Range(x, y), abychom vytvořili sekvenci celých čísel, která začíná na x a následně vytváří sekvenční čísla y.

var source1 = Observable.Range(1, 3);
var source2 = Observable.Range(1, 3);
source1.Concat(source2)
       .Subscribe(Console.WriteLine);
Console.ReadLine();

Všimněte si, že výsledná sekvence je 1,2,3,1,2,3. Je to proto, že při použití operátoru Concat nebude druhá sekvence (source2) aktivní, dokud 1. sekvence (source1) nedokončí vložení všech svých hodnot. Je to až po source1 dokončení, pak source2 začne odesílat hodnoty do výsledné sekvence. Odběratel pak získá všechny hodnoty z výsledné sekvence.

Porovnejte to s operátorem Sloučit. Pokud spustíte následující ukázkový kód, získáte 1,1,2,2,3,3. Je to proto, že obě sekvence jsou aktivní ve stejnou dobu a hodnoty jsou odsunuty tak, jak se vyskytují ve zdrojích. Výsledná sekvence se dokončí pouze v době, kdy poslední zdrojová sekvence dokončila vkládání hodnot.

Všimněte si, že aby sloučení fungovalo, musí být všechny zdrojové pozorovatelné sekvence stejného typu IObservable<T>. Výsledná posloupnost bude typu IObservable<T>. Pokud source1 uprostřed sekvence vznikne chyba OnError, výsledná sekvence se dokončí okamžitě.

var source1 = Observable.Range(1, 3);
var source2 = Observable.Range(1, 3);
source1.Merge(source2)
       .Subscribe(Console.WriteLine);
Console.ReadLine();

Další porovnání lze provést pomocí operátoru Catch. V takovém případě, pokud source1 se dokončí bez chyby, pak source2 se nespustí. Proto pokud spustíte následující vzorový kód, získáte 1,2,3 pouze proto, že source2 (který vytváří 4,5,6) se ignoruje.

var source1 = Observable.Range(1, 3);
var source2 = Observable.Range(4, 3);
source1.Catch(source2)
       .Subscribe(Console.WriteLine);
Console.ReadLine();

Nakonec se podíváme na OnErrorResumeNext. Tento operátor se přesune na source2 , i když source1 ho kvůli chybě nejde dokončit. I když source1 v následujícím příkladu představuje sekvenci, která končí s výjimkou (pomocí operátoru Throw), odběratel obdrží hodnoty (1,2,3) publikované nástrojem source2. Pokud tedy očekáváte, že některá ze zdrojových sekvencí způsobí chybu, je bezpečnější vsadit se na OnErrorResumeNext, aby bylo zaručeno, že odběratel stále obdrží určité hodnoty.

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();

Všimněte si, že aby všechny tyto operátory kombinace fungovaly, musí být všechny pozorovatelné sekvence stejného typu T.

Projekce

Operátor Select může přeložit každý prvek pozorovatelné sekvence do jiné podoby.

V následujícím příkladu promítáme sekvenci celých čísel do řetězců o délce n v uvedeném pořadí.

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();

V následující ukázce, která je rozšířením příkladu převodu událostí .NET, který jsme viděli v tématu Přemostění s existujícími událostmi .NET , jsme pomocí operátoru Select projektovali datový typ IEventPattern<MouseEventArgs> do typu Point . Tímto způsobem transformujeme posloupnost událostí přesunu myši na datový typ, který lze analyzovat a dále zpracovávat, jak je vidět v další části "Filtrování".

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);

Nakonec se podívejme na operátor SelectMany. Operátor SelectMany má mnoho přetížení, z nichž jedno přebírá argument funkce selektoru. Tato selektorová funkce je vyvolána u každé hodnoty vysílané zdrojem pozorovatelným zdrojem. Pro každou z těchto hodnot je selektor promítá do mini pozorovatelné sekvence. Na konci operátor SelectMany zplošťuje všechny tyto minisekvence do jedné výsledné sekvence, která se pak odešle odběrateli.

Pozorovatelný vrácený z SelectMany publikuje OnCompleted po zdrojové sekvenci a všechny mini pozorovatelné sekvence vytvořené selektorem jsou dokončeny. Aktivuje chybu PřiChybě, když došlo k chybě ve zdrojovém datovém proudu, když byla vyvolán výjimka funkcí selektoru nebo když došlo k chybě v některé z minimálních pozorovatelných sekvencí.

V následujícím příkladu nejprve vytvoříme zdrojovou sekvenci, která každých 5 sekund vytvoří celé číslo, a rozhodneme se vzít pouze první 2 vytvořené hodnoty (pomocí operátoru Take). Potom použijeme SelectMany k promítaní každého z těchto celých čísel pomocí jiné posloupnosti {100, 101, 102}. Tímto způsobem se vytvoří {100, 101, 102} dvě mini pozorovatelné sekvence a {100, 101, 102}. Ty jsou nakonec zploštělé do jediného proudu celých čísel {100, 101, 102, 100, 101, 102} a předané pozorovateli.

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();

Filtrování

V následujícím příkladu použijeme operátor Generate k vytvoření jednoduché pozorovatelné posloupnosti čísel. Operátor Generate má několik přetížení. V našem příkladu přebírá počáteční stav (v našem příkladu 0), podmíněnou funkci k ukončení (méně než 10krát), iterátor (+1) a volič výsledků (čtvercová funkce aktuální hodnoty). a vytiskněte pouze ty menší než 15 pomocí operátorů Where a 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();

Následující příklad je rozšířením příkladu projekce, který jste viděli dříve v tomto tématu. V této ukázce jsme použili operátor Select k promítání datového typu IEventPattern<MouseEventArgs> do typu Point . V následujícím příkladu pomocí operátoru Where a Select vybereme jenom pohyby myši, které nás zajímají. V tomto případě vyfiltrujeme přesunutí myši na ty, které jsou nad prvním bisectorem (kde jsou souřadnice x a y stejné).

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);

Operace na základě času

Operátory vyrovnávací paměti můžete použít k provádění operací založených na čase.

Ukládání pozorovatelné sekvence do vyrovnávací paměti znamená, že hodnoty pozorovatelné sekvence jsou vloženy do vyrovnávací paměti na základě zadaného časového rozsahu nebo podle prahové hodnoty počtu. To je zvlášť užitečné v situacích, kdy očekáváte, že sekvence vysune obrovské množství dat, a odběratel nemá prostředky ke zpracování těchto hodnot. Ukládáním výsledků do vyrovnávací paměti na základě času nebo počtu a vrácením posloupnosti hodnot pouze při překročení kritérií (nebo po dokončení zdrojové sekvence) může odběratel zpracovávat volání OnNext vlastním tempem. 

V následujícím příkladu nejprve vytvoříme jednoduchou posloupnost celých čísel pro každou sekundu. Pak použijeme operátor Vyrovnávací paměť a určíme, že každá vyrovnávací paměť bude obsahovat 5 položek ze sekvence. OnNext se volá, když je vyrovnávací paměť plná. Pak součet vyrovnávací paměti sečteme pomocí operátoru Sum. Vyrovnávací paměť se automaticky vyprázdní a zahájí se další cyklus. Výtisk bude 10, 35, 60… mít hodnotu 10=0+1+2+3+4, 35=5+6+7+8+9 atd.

var seq = Observable.Interval(TimeSpan.FromSeconds(1));
var bufSeq = seq.Buffer(5);
bufSeq.Subscribe(values => Console.WriteLine(values.Sum()));
Console.ReadKey();

Můžeme také vytvořit vyrovnávací paměť se zadaným časovým rozsahem. V následujícím příkladu bude vyrovnávací paměť uchovávat položky, které se nahromadily po dobu 3 sekund. Výtisk bude 3, 12, 21... ve kterém 3=0+1+2, 12=3+4+5 atd.

var seq = Observable.Interval(TimeSpan.FromSeconds(1));
var bufSeq = seq.Buffer(TimeSpan.FromSeconds(3));
bufSeq.Subscribe(value => Console.WriteLine(value.Sum()));  
Console.ReadKey();

Všimněte si, že pokud používáte vyrovnávací paměť nebo okno, musíte se před filtrováním ujistit, že sekvence není prázdná.

Operátory LINQ podle kategorií

Téma LINQ Operators by Categories (Operátory LINQ podle kategorií ) obsahuje seznam všech hlavních operátorů LINQ implementovaných pozorovatelným typem podle jejich kategorií; konkrétně: vytváření, převod, kombinování, funkční, matematické, časové, výjimky, různé, výběr a primitiva.

Viz také

Reference

Pozorovatelné

Koncepty

Operátory LINQ podle kategorií