if you worked on some larger wpf applications you might be familiar with this. Because ResourceDictionaries are always instantiated, everytime they are found in an XAML we might end up having one resource dictionary multiple times in memory. So the above mentioned solution seems like a very good alternative. In fact for our current project this trick did a lot ... Memory consumption from 800mb down to 44mb, which is a really huge impact. Unfortunately this solution comes at a cost, which i would like to show here, and hopefully find a way to avoid it while still use the SharedResourceDictionary
.
I made a small example to visualize the problem with a shared resource dictionary.
Just create a simple WPF Application. Add one resource Xaml
Shared.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="myBrush" Color="Yellow"/>
</ResourceDictionary>
Now add a UserControl. The codebehind is just the default, so i just show the xaml
MyUserControl.xaml
<UserControl x:Class="Leak.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SharedResourceDictionary="clr-namespace:Articy.SharedResourceDictionary" Height="128" Width="128">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Leak;component/Shared.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Rectangle Fill="{StaticResource myBrush}"/>
</Grid>
</UserControl>
The Window code behind looks something like this
Window1.xaml.cs
// [ ... ]
public Window1()
{
InitializeComponent();
myTabs.ItemsSource = mItems;
}
private ObservableCollection<string> mItems = new ObservableCollection<string>();
private void OnAdd(object aSender, RoutedEventArgs aE)
{
mItems.Add("Test");
}
private void OnRemove(object aSender, RoutedEventArgs aE)
{
mItems.RemoveAt(mItems.Count - 1);
}
And the window xaml like this
Window1.xaml
<Window.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type System:String}">
<Leak:MyUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="Add" Click="OnAdd"/>
<Button Content="Remove" Click="OnRemove"/>
</StackPanel>
<TabControl x:Name="myTabs" ContentTemplate="{StaticResource myTemplate}">
</TabControl>
</DockPanel>
</Grid>
</Window>
I know the program is not perfect and propably could be made easier but while figuring out a way to show the problem this is what i came up with. Anyway:
Start this and you check the memory consumption, if you have a memory profiler this becomes much easier. Add (with showing it by clicking on the tab) and remove a page and you will see everything works fine. Nothing leaks.
Now in the UserControl.Resources
section use the SharedResourceDictionary
instead of the ResourceDictionary
to include the Shared.xaml. You will see that the MyUserControl
will be kept in memory after you removed a page, and the MyUserControl
in it.
I figured this happens to everything that is instantiated via XAML like converters, user controls etc. Strangely this won't happen to Custom controls. My guess is, because nothing is really instantiated on custom controls, data templates and so on.
So first how we can avoid that? In our case using SharedResourceDictionary is a must, but the memory leaks makes it impossible to use it productively. The Leak can be avoided using CustomControls instead of UserControls, which is not always practically. So why are UserControls strong referenced by a ResourceDictionary? I wonder why nobody experienced this before, like i said in an older question, it seems like we use resource dictionaries and XAML absolutely wrong, otherwise i wonder why they are so inefficent.
I hope somebody can shed some light on this matter.
Thanks in advance Nico
I'm running into the same issue of needing shared resource directories in a large-ish WPF project. Reading the source article and the comments, I incorporated a couple fixes to the SharedDirectory class as suggested in the comments, which seem to have removed the strong reference (stored in _sourceUri) and also make the designer work correctly. I tested your example and it works, both in the designer and MemProfiler successfully noting no held references. I'd love to know if anyone has improved it further, but this is what i'm going with for now:
public class SharedResourceDictionary : ResourceDictionary
{
/// <summary>
/// Internal cache of loaded dictionaries
/// </summary>
public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
new Dictionary<Uri, ResourceDictionary>();
/// <summary>
/// Local member of the source uri
/// </summary>
private Uri _sourceUri;
/// <summary>
/// Gets or sets the uniform resource identifier (URI) to load resources from.
/// </summary>
public new Uri Source
{
get {
if (IsInDesignMode)
return base.Source;
return _sourceUri;
}
set
{
if (IsInDesignMode)
{
try
{
_sourceUri = new Uri(value.OriginalString);
}
catch
{
// do nothing?
}
return;
}
try
{
_sourceUri = new Uri(value.OriginalString);
}
catch
{
// do nothing?
}
if (!_sharedDictionaries.ContainsKey(value))
{
// If the dictionary is not yet loaded, load it by setting
// the source of the base class
base.Source = value;
// add it to the cache
_sharedDictionaries.Add(value, this);
}
else
{
// If the dictionary is already loaded, get it from the cache
MergedDictionaries.Add(_sharedDictionaries[value]);
}
}
}
private static bool IsInDesignMode
{
get
{
return (bool)DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty,
typeof(DependencyObject)).Metadata.DefaultValue;
}
}
}