Reactive Programming, Posting # 10
In the previous posting on Reactive Extensions, we created an application that calls on the Bing translation service to translate a phrase into Japanese, and then back into English. This allowed the introduction of the SelectMany operator.
In this posting we’ll ask the Bing Translation service for a list of every language it knows about, and then we’ll translate a phrase into each language in turn. This will highlight the chaining aspect of SelectMany. (Click on image to see full size).
To begin, create another WPF application. The Xaml is the same as in the previous example, except that this time we’ll replace the TextBlock with a ListBox, making it easier to see the results.
<TextBox Name="Input" Text="This is a test" Margin="0,0,0,124" Grid.RowSpan="2" /> <ListBox VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="10,17,10,10" Name="Output" Grid.Row="1" /> <Button Name="Go" Content="Go" Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center" />
You’ll need to create the BingTranslator web service reference as you did in the previous example, and the Translate method is identical. This time we’ll add a new method, ListOfAllLanguages that will return an Observable of an array of strings, each string being a language that Bing knows about.
private IObservable<string[ ]> ListOfAllLanguages( ) { var lsc = new LanguageServiceClient( ); var allLangs = Observable.FromAsyncPattern< string, string[ ]>( lsc.BeginGetLanguagesForTranslate, lsc.EndGetLanguagesForTranslate ); return allLangs( APP_ID ); }
A significant change is how we handle the Click event on the Go button. In this case we want to capture what is Input each time the button is pressed. We do that by creating a Subject of string named inputText and then assigning to that Subject whatever is in the Text property of the Input TextBlock each time the button is pressed.
private Subject<string> inputText = new Subject<string>( ); public MainWindow( ) { InitializeComponent( ); Go.Click += ( o, e ) => inputText.OnNext( this.Input.Text );
The documentation on Subject states that it represents an object that is both an observable sequence (as any Observable) and also an observer. In this case, it is observing the input text, and notifying its observers in the call to OnNext which notifies all subscribed observers with the value.
Combining two streams
We want to combine the values observed in the Subject with the values that we’ll be getting back as a result of calling ListOfAllLanguages. To do that, we use the CombineLatest method of Observable. The key feature of CombineLatest is that it will give us a value each time either of the observables changes; that is if either the string we’re examining changes or there is a new value returned from ListOfAllLanguages, CombineLatest will return the latest pair. We can then feed that pair into a struct,
Observable.CombineLatest( inputText, ListOfAllLanguages( ), ( text, langs ) => new { text, langs } )
The result from CombineLatest is a series of IObservables which we now want to recombine so that each element in a new struct will have the phrase and one of the languages. We can do this with SelectMany and a LINQ statement, the result of which we’ll turn back into yet another Observable,
.SelectMany( x => x.langs.Select( y => new { x.text, lang = y } ).ToObservable( ) )
We can now use SelectMany again to take this Observable and call translate on each member of the structure, asking each to be translated from English to the selected language
.SelectMany( x => Translate( x.text, "en", x.lang ) )
We can now chain in a call to ObserveOnDispatcher to marshal the results to the UI thread and then call Subscribe to place the results into the ListBox,
.ObserveOnDispatcher( ) .Subscribe( x => Output.Items.Add( x ) );
It is the ability of SelectMany to flatten multiple observable collections into a single Observable that allows this chaining.
Once again, to provide context, here is the complete source file,
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using Translation3.BingTranslator; namespace Translation3 { public partial class MainWindow : Window { const string APP_ID = "8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2"; private Subject<string> inputText = new Subject<string>( ); public MainWindow( ) { InitializeComponent( ); Go.Click += ( o, e ) => inputText.OnNext( this.Input.Text ); Observable.CombineLatest( inputText, ListOfAllLanguages( ), ( text, langs ) => new { text, langs } ) .SelectMany( x => x.langs.Select( y => new { x.text, lang = y } ).ToObservable( ) ) .SelectMany( x => Translate( x.text, "en", x.lang ) ) .ObserveOnDispatcher( ) .Subscribe( x => Output.Items.Add( x ) ); } private IObservable<string> Translate( string text, string from, string to ) { var lsc = new LanguageServiceClient( ) as LanguageService; var myFunc = Observable.FromAsyncPattern<string, string, string, string, string, string, string>( lsc.BeginTranslate, lsc.EndTranslate ); return myFunc( APP_ID, text, from, to, null, null ); } private IObservable<string[ ]> ListOfAllLanguages( ) { var lsc = new LanguageServiceClient( ); var allLangs = Observable.FromAsyncPattern< string, string[ ]>( lsc.BeginGetLanguagesForTranslate, lsc.EndGetLanguagesForTranslate ); return allLangs( APP_ID ); } } }
For more on SelectMany be sure to read Exercise 8 in the Hands On Lab.
A buddy of mine asked me to see this website and I am delighted I did. I got the info I require quickly and it was really beneficial.
FYI – there is no source file on this post. Great series. Thanks
Great comments and examples. Your posts are outstanding ,,,,,
However, it took 15 clicks to find this, and it was not what I was looking for. The Bing search function is awful, and the help for Visual Basic seems to not go where you ask it to.
It claims Hosting over 1400 Code Samples , yet the page supposedly does not exist.
I think there is an issue that may need to be addressed in the get help and search functions for Visual Basic and C# in Visual Studio 2010.
Thank you,
Robert Browning
ps, very few of the searches I have done on Bing have ever returned usable results.