Thursday, November 26, 2009

After Microsoft Technology Summit 2009

I would like to thank all of the attendees who came to my session - "Workflow Foundation 4.0" on the MTS2009 conference. Thanks for all evaluations and comments! The videos from the conference has just been published. Here is my webcast presentation (polish version).

RxSandbox

Rx is awesome :)

I have just started a new project called RxSandbox. It will be a very simple application that help to understand how Rx works, how each operator is implemented. But lets start from the beginning. Few days ago I was playing with Rx operators such as Zip, Merge, Wait, etc. writing a very short chunk of code for each operator. I was wondering how do they work in details, if they cache the values from sources, when exactly is OnCompleted executed on the observers and so on. The code could look like this:

a.Zip(b, (x, y) => x + " - " + y)

I wanted to a and b be the implementation of IObservable<string>. But what is the easiest way to create the implementation for them? I just wanted to be focused on writing code using operators so I needed some infrastructure for creating sample observable data sources. The RxSandbox is the solution! When you write something like this:

Expression<Func<ManualObservable<string>, ManualObservable<string>,
    IObservable<string>>> zipExpression
        = (a, b) => a.Zip(b, (x, y) => x + " - " + y);
        
Control control = RxExpressionVisualizer.CreateControl(zipExpression);

RxSandbox will automatically generate testing UI control:

image

In the code above ManualObservable means that the user can manually define the values sent to the expression by writing them in a text box. Other possible could be RandomObservable, IntervalObservable and there are many more options. The UI control will be different for each type of Observable source. The only reason why the Expression<Func<...>> type has been used here is that this allows us to display the body of expression, but of course this is just an option. In general, the test case is a method taking some Observable sources and returning observable collection. It can be some really complicated code snippet with many statements as well.

Possible scenarios working with RxSanbox:

  • Run RxSandbox application, create new project in VS, write Rx expression, compile it, RxSandbox automatically  finds new version of dll file and loads it, you are ready to test it (it may be MEF, VS AddIn not just of stand along application)
  • Run RxSandbox, write your Rx expression inside RxSandbox, start testing it (the easy way to do this is to use ExpressionTextBox control from WF4.0 which allows us to write single VB expression and expose it as an Linq expression type)
  • Drawing marble diagrams live during testing and also presenting the definitions of the operators as marble diagrams (of course with pac-mans and hearts :) - )
  • Supporting Silverlight version
  • Many many more....

I hope you got the idea. Here you can find the first prototype.

Friday, November 20, 2009

Puzzle

What will be displayed on the screen when we write it (and why) ?

Action<string> a = Console.WriteLine;
var b = a;
var c = (a += Console.WriteLine);

a("Rx");
b("rulez");
c("!");

Hint: delegate type is:
- reference type
- immutable type

Thursday, November 12, 2009

Linq to ICollectionView

Currently I'm working on the project written in Silverlight and I have encountered an interface called ICollectionView and its standard implementation PagedCollectionView. In short, this components allow us to create the view of collection of items with filtering, grouping and sorting functionality. The idea behind this interface is very similar to DataView/DataTable mechanism. DataTable is responsible for storing data and DataView is just an appropriately configured proxy (only filtering and sorting in this case) which can be bound to UI controls. Let's look at a very simple example:

public class Number
{
    public int Value { get; set; }
    public int Random { get; set; }

    public static Number[] GetAll()
    {
        var random = new Random();
        return
            (from n in Enumerable.Range(1,5)
             from m in Enumerable.Repeat(n, n)
             select new Number {Value = n, Random = random.Next(10)}).ToArray();            
    }
}

// Silverlight
public class LinqToICollectionView : UserControl
{
    public LinqToICollectionView()
    {
        Number[] numbers = Number.GetAll();

        Content = new StackPanel
        {
            Orientation = Orientation.Horizontal,
            Children =
            {
                new DataGrid { ItemsSource = new PagedCollectionView(numbers).SetConfiguration()},
            }
        };
    }
}

public static class Configurator
{
    public static ICollectionView SetConfiguration(this ICollectionView view)
    {
        // filtering
        view.Filter = (object o) => ((Number)o).Value < 5;
        // grouping
        view.GroupDescriptions.Add(new PropertyGroupDescription("Value"));
        // sorting
        view.SortDescriptions.Add(new SortDescription("Value", ListSortDirection.Descending));
        view.SortDescriptions.Add(new SortDescription("Random", ListSortDirection.Ascending));
        return view;
    }     
}

image

But wait a minute, we said ... filtering, ordering, grouping ? Let's use LINQ query to configure ICollectionView:

public static class Configurator
{
    public static ICollectionView SetConfigurationWithLinq(this ICollectionView view)
    {
        var q =
            from n in new View<Number>()
            where n.Value < 5
            orderby n.Value descending, n.Random
            group n by n.Value;
        q.Apply(view);
        return view;
    }        
}

The whole implementation consists of 3 simple classes: View<T>, OrderedView<T> and GroupedView<T>.

public class View<T>
{
    public IEnumerable<GroupDescription> GroupDescriptions { get { ... } }
    public IEnumerable<SortDescription> SortDescriptions { get { ... } }
    public Func<T,bool> Filter { get { ... } }
    
    public View<T> Where(Func<T, bool> func) { ... }
    public SortedView<T> OrderBy<T2>(Expression<Func<T, T2>> func) { ... }
    public SortedView<T> OrderByDescending<T2>(Expression<Func<T, T2>> func) { ... }
    public GroupedView<T> GroupBy<T2>(Expression<Func<T, T2>> func) { ... }
    
    public void Apply(ICollectionView collectionView) { ... }
}
public sealed class SortedView<T> : View<T>
{    
    public SortedView<T> ThenBy<T2>(Expression<Func<T, T2>> func) { ... }
    public SortedView<T> ThenByDescending<T2>(Expression<Func<T, T2>> func)  { ... }
}
public sealed class GroupedView<T> : View<T>
{
    public GroupedView<T> ThenBy<T2>(Expression<Func<T, T2>> func) { ... }
}

Methods for sorting and grouping which take an expression tree as a parameter analyze the tree looking for indicated members (fields or properties) and collect appropriate SortDescription and GroupDescription objects. Where method takes a delegate type which is combined via logical and operator with existing filter delegate set previously (in case when Where method is called many times). Of course the same mechanism work also in WPF.

Number[] numbers = Number.GetAll();

// WPF
new Window
{
    Content = new StackPanel
    {
        Orientation = Orientation.Horizontal,
        Children =
        {
            CreateListView(new CollectionViewSource { Source = numbers }.View.SetConfiguration()),
            CreateListView(new CollectionViewSource { Source = numbers }.View.SetConfigurationWithLinq())
        }
    }
}
.ShowDialog();


private static ListView CreateListView(ICollectionView view)
{
    return new ListView
    {
        GroupStyle = { GroupStyle.Default },
        View = new GridView
        {
            Columns =
            {
                new GridViewColumn { Header = "Value", DisplayMemberBinding = new Binding("Value") },
                new GridViewColumn { Header = "Random", DisplayMemberBinding = new Binding("Random") },
            }
        },
        ItemsSource = view
    };
}

At the end I'd like to mention one interesting thing. We only support filtering, sorting and grouping and don't support for instance projection, joining and so on. That's way this code should compile:

var v1 = // only filtering specified
    from n in new View<Number>() 
    where n.Value < 5 
    select n;

var v2 = // grouping (last grouping definition overrides previous ones)
    from n in new View<Number>()
    group n by n.Random into s 
    group s by s.Value;

but this will not:

var v3 = // joining is not supported
    from p in new View<Number>()
    join pp in new[] { 1, 2, 3, 4 } on p.Random equals pp
    select p;

var v4 = // projection is not supported
    from p in new View<Number>()
    where p.Value > 5
    select p.Random;

var v5 = // at least one filtering, grouping or sorting definition must be specified
    from p in new View<Number>()
    select p;

var v6 = // Numer type is the only valid type of grouped element
    from p in new View<Number>()
    group p.Random by p.Value;

As a homework I leave you a question: why it works this way ? :)

Sources