A hidden bug in Windows Store’s ComboBox

Since some time ago I’ve been using Immutable Collections as much as I could due to their immutability contract – nothing ever changes. So far so good, but today I’ve stumbled upon an odd and occasional (at least at beginning) bug with Windows Store/8.1 ComboBox bound to an ImmutableList<T> through ItemsSource property.

I was sporadically seeing crashes in application mostly when ComboBox was somehow in between changes on the page. The random crashes is really the worst type of problem one can imagine. The Exception wasn’t saying much but it put me on the right track. It said that it couldn’t cast ComboBoxItem to T (of which ImmutableList I was using) in ImmutableList<T>.IList.IndexOf(object item) method.

Luckily I’ve found a way to crash it easily – just fill more than 10 items as ItemSource, focus ComboBox and press up or down. It would crash after 5 or so changes each time. Being able to repro the problems asap and 100% reliable is a big benefit. Huge actually.

Then I’ve exprerimented with a normal List<T> instead of an immutable one and it worked without any problem. Odd, I thought.

Now, luckily Immutable Collections are open source on github as a part of corefx. I was being ready for a pull request if I could fix the bug. I was cloning the repository in no time and then tried to reference sources instead of NuGet package. There were two obstacle on my way:

  1. The version of Immutable Collections in corefx is using .net 4.5 which is not a good framework for a Windows Store app. Hence I created a blank Windows Store library and copied all sources there. That worked almost perfectly.
  2. Exceptions were using localized strings from a static class SR which I really didn’t have time to figure out where it comes from. I merely commented out these exceptions and replaced them with new Exception();

At this point the project compiled and I was ready to debug with sources. And soon it stopped in the offending method: ImmutableList<T>.IList.IndexOf(object item). The thing is that ComboBox was passing an ComboBoxItem (even though I was using items of type T) and ImmutableList<T> doesn’t care to fail softly when it gets an incompatible item type (note that IList isn’t generic). As, I discovered later, is the case with List<T>: it would return –1 when an incompatible type is passed whereas ImmutablList<T> would just crash.

 

However, I figured out that this is not a problem of ImmutableList<T> but of rather poor implementation of ComboBox in Windows Store (and perhaps of other similar types). Now, to make it worse, there are no sources for Windows Store ComboBox since it is a C++ dude and is virtually unfixable in the time I’d need the fix if I were to report the error somehow.

So the only solution for the time being is to switch to List<T> and avoid ImmutableList<T> even though it doesn’t mutate ever.

You’ve been warned!

 

Here is the repro code, just create a Windows Store app, get ImmutablCollections from NuGet and paste this xaml:

<ComboBox x:Name="combo" VerticalAlignment="Center" FontSize="70" Width="500"
           SelectedValue="{Binding ItemId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Id">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Text}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

and this codebehind:

public sealed partial class MainPage : Page
    {    
        Tubo tubo;
        public MainPage()
        {
            this.InitializeComponent();
            List<Item> items = Enumerable.Range(1, 20)
                 .Select(i => new Item { Id = i, Text = i.ToString() }).ToList();
            tubo = new Tubo();
            tubo.ItemId = 5;
            DataContext = tubo;

            combo.ItemsSource = items.ToImmutableList();
        }
    }

public class Item
{
    public int Id { get; set; }
    public string Text { get; set; }
}

public class Tubo
{
    public int ItemId { get; set; }
}
Update: I've checked with Windows 10 preview and VS2015CTP and the same bug is still there. Yuck.