WPF: Merged dictionary parser depends on XML attribute order

Does attribute order in XAML matter? It should not, but sometimes it does. The following XAML compiles, but blows up at runtime with “Item has already been added” exception, both in .NET 3.5 and .NET 4.0:

<Window x:Class="MergedDictionaryKeyPosition.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
  <Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary>
                <Style TargetType="Button" x:Key="foo" />
                <Style TargetType="Button" x:Key="bar" />
            </ResourceDictionary>
        </ResourceDictionary.MergedDictionaries> 
   </ResourceDictionary>
  </Window.Resources>

  <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Hello" />
</Window>

What’s wrong with it? Style elements in resource dictionaries have a special feature: if explicit key is not present, TargetType (in this case typeof(Button)) will be used as a key. This allows overriding default style for a control type. It seems that in this case XAML parser sees TargetType first and ignores the explicit key, thus creating a conflict – it attempts to add two styles under the same key “Button”.

If I put x:Key in front of target type, everything works:

<Window x:Class="MergedDictionaryKeyPosition.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
  <Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary>
                <Style x:Key="foo" TargetType="Button" />
                <Style x:Key="bar" TargetType="Button" />
            </ResourceDictionary>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </Window.Resources>

  <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Hello" />
</Window>

This rule seems to apply only to merged resource dictionaries. For standalone dictionaries it is OK to have the key after the target type. The following XAML works:

<Window x:Class="MergedDictionaryKeyPosition.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
  <Window.Resources>
    <ResourceDictionary>
        <Style TargetType="Button" x:Key="foo" />
        <Style TargetType="Button" x:Key="bar" />
    </ResourceDictionary>
  </Window.Resources>

  <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Hello" />
</Window>

as does this one:

<Window x:Class="MergedDictionaryKeyPosition.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
  <Window.Resources>
     <Style TargetType="Button" x:Key="foo" />
     <Style TargetType="Button" x:Key="bar" />
  </Window.Resources>

  <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Hello" />
</Window>

I am not sure why merged resource dictionaries parser is different from regular resource dictionary parser. Also, the stack trace for the exception is quite different between .NET 3.5 and .NET 4.0, so this code must have been reworked, but the error is exactly the same in both cases. I will post this on MSDN forum and see whether Microsoft has an answer.

<rant>Unfortunately, from previous experience I know that such things are rarely fixed, typical scenario being something like “oh, resource dictionaries are obsolete now, in .NET 5 they are replaced with hashed foobars”. 🙂 </rant>

Leave a Reply

Your email address will not be published. Required fields are marked *