WPF Binding and Why you should write defensive code.

2 minute read

I'm overriding equals and implementing IEquatable on some of my objects and bind them as an ObservableCollection to the UI.

Here is a sample: (ugly code do not use)

<Window x:Class="BindingToItemsWithIEquality.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingToItemsWithIEquality"
Title="MainWindow" Height="350" Width="525"
>
<Window.DataContext>
<local:SomeContextWithCollection/>
</Window.DataContext>
<StackPanel>
<ListBox DisplayMemberPath="Name" ItemsSource="{Binding Items}"/>
<Button Click="Button_Click">clear</Button>
</StackPanel>
</Window>

//code behind
private void Button_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as SomeContextWithCollection).Items.Clear();
}

public class SomeContextWithCollection
{
public ObservableCollection<SomeIEqutable> Items { get; set; }

public SomeContextWithCollection()
{
Items = new ObservableCollection<SomeIEqutable>();
Items.Add(new SomeIEqutable() { Name = "1" });
Items.Add(new SomeIEqutable() { Name = "2" });
}
}

public class SomeIEqutable : IEquatable<SomeIEqutable>
{
public string Name { get; set; }

public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}

return Equals((SomeIEqutable)obj);
}

public bool Equals(SomeIEqutable other)
{
if (object.ReferenceEquals(this, other))
{
return true;
}

return Name == other.Name;
}

public override int GetHashCode()
{
return Name.GetHashCode();
}
}

When calling collection.Clear() I get an invalid cast inside my equals method, when trying to cast to SomeIEquatable.

This is pretty strange, object is not null and not SomeIEquatlabe, how did it get to my Equals?

The answer is WPF, when working with binding and clearing a bounded collection WPF will compare his internal new MSInternal.NamedObject(named DisconnectedItem) to your SomeIEquatable object and will fail if you try to implicitly cast it to your type.

The simple solution is to use the "as" keyword istead of cast.

If my code didn't smell from the start, I would never got to this dark place. But I'm glad it happened, now I have another excuse to write defensive code.

Comments