嗯,正在學Rx,學了一輪後實際應用時發現還有不懂的地方,再重讀一次,順便簡單的翻譯下…翻譯不出來的或是覺得不重要的就以”…”符號替換,或顯示原文。
當然,辭不達義的地方也會有,請包含…
Inspection
要知道我們並不能僅靠過濾冗餘的部份來取得所需的資料,有時候我們還需要從序列中取得相關或符合預期條件的部份,它是否符合特定條件?某個特定值是否存在?我想找到某個值!
上一章我們瞭解透過filter去縮減我們的來源可觀察序列,現在我們要看有關inspection的運算子,這些運算子大部份都會將來源序列縮減到單一值的序列,這些方法的回傳值雖然無法滿足我們對catamorphism的定義 - 非純量值(仍是IObservable<T>
),但仍符合我們要將序列縮減至單一值的期待。
這系列的方法對inspection一個現有的序列很有幫助,每一種都會回傳包含一單一結果值的可觀察序列,原生的非同步方式再次證明它們的用處,且易於使用,請看下列介紹。
Any
我們先來看Any擴充函式的無參數覆載,如果來源序列沒有推送任何值就結束,它只回傳有一值為false的可觀察序列;而若來源序列的第一個值已被推送,則Any的可觀察序回傳true並結束。若第一個被推送的是錯誤,那它也會推送相同錯誤。
var subject = new Subject<int>();
subject.Subscribe(
Console.WriteLine,
() => Console.WriteLine("Subject completed"));
var any = subject.Any();
any.Subscribe(
b => Console.WriteLine("The subject has any values? {0}", b));
subject.OnNext(1);
subject.OnCompleted();
輸出:
1
The subject has any values? True
subject completed
如果我們把OnNext(1)拿掉,輸出如下:
subject completed
The subject has any values? False
Any只關心來源序列的第一個值,若是推送的是一個錯誤,它直接送出錯誤,否則就推送true。
var subject = new Subject<int>();
subject.Subscribe(Console.WriteLine,
ex => Console.WriteLine("subject OnError : {0}", ex),
() => Console.WriteLine("Subject completed"));
var any = subject.Any();
any.Subscribe(
b => Console.WriteLine("The subject has any values? {0}", b),
ex => Console.WriteLine(".Any() OnError : {0}", ex),
() => Console.WriteLine(".Any() completed"));
subject.OnError(new Exception());
輸出:
subject OnError : System.Exception: Fail
.Any() OnError : System.Exception: Fail
Any另有一個需代入一個判斷式的覆載,就像Where一樣。
subject.Any(i => i > 2);
//Functionally equivalent to
subject.Where(i => i > 2).Any();
試著自己實作上述兩種Any擴充方法當做練習,或許答案不是那麼明顯,但我們之前學到的函式已夠你實現類似功能。
用Observable.Create來建立Any擴充函式:
public static IObservable<bool> MyAny<T>(
this IObservable<T> source)
{
return Observable.Create<bool>(
o =>
{
var hasValues = false;
return source
.Take(1)
.Subscribe(
_ => hasValues = true,
o.OnError,
() =>
{
o.OnNext(hasValues);
o.OnCompleted();
});
});
}
public static IObservable<bool> MyAny<T>(
this IObservable<T> source,
Func<T, bool> predicate)
{
return source
.Where(predicate)
.MyAny();
}
All
擴充函式All()跟Any一樣,除了所有的值都需要符合判斷式,若是任一值的判斷結果為false,輸出序列會馬上結束,如果來源序列為空,All函式會傳回true,如同Any函式,錯誤也會一樣被送出。
var subject = new Subject<int>();
subject.Subscribe(
Console.WriteLine, () => Console.WriteLine("Subject completed"));
var all = subject.All(i => i < 5);
all.Subscribe(
b => Console.WriteLine("All values less than 5? {0}", b));
subject.OnNext(1);
subject.OnNext(2);
subject.OnNext(6);
subject.OnNext(2);
subject.OnNext(1);
subject.OnCompleted();
Output:
1
2
6
All values less than 5? False
all completed
2
1
subject completed
早期有用Rx的人可能會注意到IsEmpty擴充函式不見了,因為現在你可以用All()函式實作相同功能。
//IsEmpty() is deprecated now.
//var isEmpty = subject.IsEmpty();
var isEmpty = subject.All(_ => false);
Contains
Contains擴充函式可以很明顯的以Any函式來覆載之。Contains函式的行為就像Any,然而它的目標使用的是IComparable而不是判斷式,且是設計為尋找特定值而不是依據條件判斷結果而定。I believe that these are not overloads of Any for consistency with IEnumerable.
var subject = new Subject<int>();
subject.Subscribe(
Console.WriteLine,
() => Console.WriteLine("Subject completed"));
var contains = subject.Contains(2);
contains.Subscribe(
b => Console.WriteLine("Contains the value 2? {0}", b),
() => Console.WriteLine("contains completed"));
subject.OnNext(1);
subject.OnNext(2);
subject.OnNext(3);
subject.OnCompleted();
Output:
1
2
Contains the value 2? True
contains completed
3
Subject completed
Contains函式另有一個可讓你自行指定IEqualityComparer<T>
型態參數的覆載,在你需要時會很有用。
DefaultIfEmpty
DefaultIfEmpty函式在來源序列是空的時候會回傳預設值或是Default(T),Default(T)的值若是結構則為0,類別則為null,如果來源不為空,則所有值會直接傳出。
這個範例中來源序列產生值,所以DefaultIfEmpty的結果就是來源的值。
var subject = new Subject<int>();
subject.Subscribe(
Console.WriteLine,
() => Console.WriteLine("Subject completed"));
var defaultIfEmpty = subject.DefaultIfEmpty();
defaultIfEmpty.Subscribe(
b => Console.WriteLine("defaultIfEmpty value: {0}", b),
() => Console.WriteLine("defaultIfEmpty completed"));
subject.OnNext(1);
subject.OnNext(2);
subject.OnNext(3);
subject.OnCompleted();
Output:
1
defaultIfEmpty value: 1
2
defaultIfEmpty value: 2
3
defaultIfEmpty value: 3
Subject completed
defaultIfEmpty completed
如果來源為空,我們可以使用型別的預設值(如在int則為0)或提供我們自己的值,如範例中的42:
var subject = new Subject<int>();
subject.Subscribe(
Console.WriteLine,
() => Console.WriteLine("Subject completed"));
var defaultIfEmpty = subject.DefaultIfEmpty();
defaultIfEmpty.Subscribe(
b => Console.WriteLine("defaultIfEmpty value: {0}", b),
() => Console.WriteLine("defaultIfEmpty completed"));
var default42IfEmpty = subject.DefaultIfEmpty(42);
default42IfEmpty.Subscribe(
b => Console.WriteLine("default42IfEmpty value: {0}", b),
() => Console.WriteLine("default42IfEmpty completed"));
subject.OnCompleted();
Output:
Subject completed
defaultIfEmpty value: 0
defaultIfEmpty completed
default42IfEmpty value: 42
default42IfEmpty completed
ElementAt
ElementAt函式讓我們可以用索引挑選值,如同IEnumerable<T>
是以0開始。
var subject = new Subject<int>();
subject.Subscribe(
Console.WriteLine,
() => Console.WriteLine("Subject completed"));
var elementAt1 = subject.ElementAt(1);
elementAt1.Subscribe(
b => Console.WriteLine("elementAt1 value: {0}", b),
() => Console.WriteLine("elementAt1 completed"));
subject.OnNext(1);
subject.OnNext(2);
subject.OnNext(3);
subject.OnCompleted();
Output
1
2
elementAt1 value: 2
elementAt1 completed
3
subject completed
由於我們無法確認可觀察序列到底會有多長,所以可以假定這個函式可能會造成問題。如果來源序列只有5個值,然後你想跟它要第六個值,ArgumentOutOfRangeException會在來源序列結束後被推送,因此我們有三個選擇:
- 優雅的處理OnError
- 使用Skip(5).Take(1);這會跳過前5個值,並只取第六個值,如果序列的元素少於六個,我們只會得到一個空序列,而不會發生錯誤。
- 使用ElementAtOrDefault
ElementAtOrDefault擴充函式在這種索引值大於範圍的狀況下,會推送Default(T)值,而目前沒有提供你自設預設值的選項。
SequenceEqual
Finally SequenceEqual extension method is perhaps a stretch to put in a chapter that starts off talking about catamorphism and fold, but it does serve well for the theme of inspection. 這個函式讓我們可以比較兩個可觀察序列。當任一序列產生值時,此值會和另一序列的快取值相互比較,確保兩個序列是值相同,長度也相同。這也表示若是來源序列一產生不一樣的值時,結果序列會為false,而在兩個來源序皆相同且結束後,結果序列會為true。
var subject1 = new Subject<int>();
subject1.Subscribe(
i=>Console.WriteLine("subject1.OnNext({0})", i),
() => Console.WriteLine("subject1 completed"));
var subject2 = new Subject<int>();
subject2.Subscribe(
i=>Console.WriteLine("subject2.OnNext({0})", i),
() => Console.WriteLine("subject2 completed"));
var areEqual = subject1.SequenceEqual(subject2);
areEqual.Subscribe(
i => Console.WriteLine("areEqual.OnNext({0})", i),
() => Console.WriteLine("areEqual completed"));
subject1.OnNext(1);
subject1.OnNext(2);
subject2.OnNext(1);
subject2.OnNext(2);
subject2.OnNext(3);
subject1.OnNext(3);
subject1.OnCompleted();
subject2.OnCompleted();
Output:
subject1.OnNext(1)
subject1.OnNext(2)
subject2.OnNext(1)
subject2.OnNext(2)
subject2.OnNext(3)
subject1.OnNext(3)
subject1 completed
subject2 completed
areEqual.OnNext(True)
areEqual completed
本章討論了一組可讓我們檢查可觀察序列的函式,且一般都會回傳一單值的序列。我們將繼續研究可減少我們的序列的函式,直到我們碰到不好理解的functional fold功能。
Written with StackEdit.
沒有留言:
張貼留言