Sis*_*phe 10 .net c# memory wpf memory-leaks
我目前在WPF TreeView上有一个奇怪的内存泄漏.当我在TreeView中选择一个项目时,相应的绑定ViewModel强烈保存在TreeView EffectiveValueEntry []集合中.问题是,当ViewModel从其父集合中删除时,它不会被释放.
这是一个重现问题的简单代码:
MainWindow.xaml
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace TreeViewMemoryLeak
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<Entry> Entries
{
get
{
if (entries == null)
{
entries = new ObservableCollection<Entry>() { new Entry() { DisplayName = "First Entry" } };
}
return entries;
}
}
private void Button_Click(object sender, RoutedEventArgs e) { entries.Clear(); }
private ObservableCollection<Entry> entries;
}
public class Entry : DependencyObject
{
public string DisplayName { get; set; }
}
}
Run Code Online (Sandbox Code Playgroud)
MainWindow.xaml.cs
<Window x:Class="TreeViewMemoryLeak.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewMemoryLeak"
Title="MainWindow" Height="350" Width="250">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Entry}">
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="delete item" Click="Button_Click" Grid.Row="0" Margin="10"/>
<TreeView x:Name="treeView" ItemsSource="{Binding Entries}" Grid.Row="1" Margin="10" BorderBrush="Black" BorderThickness="1" />
</StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)
重现问题
选择项目,然后单击按钮以清除ObservableCollection.现在检查TreeView控件上的EffectiveValueEntry []:ViewModel仍然存在,并且没有标记为垃圾回收.
好吧,我最终想出了一个相当暴力的解决方案。当删除 TreeView 中的最后一个对象时,我自己从 effectiveValues 集合中删除了引用。这可能有点矫枉过正,但至少,它有效。
public class MyTreeView : TreeView
{
protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
{
base.OnSelectedItemChanged(e);
if (Items.Count == 0)
{
var lastObjectDeleted = e.OldValue;
if (lastObjectDeleted != null)
{
var effectiveValues = EffectiveValuesGetMethod.Invoke(this, null) as Array;
if (effectiveValues == null)
throw new InvalidOperationException();
bool foundEntry = false;
int index = 0;
foreach (var effectiveValueEntry in effectiveValues)
{
var value = EffectiveValueEntryValueGetMethod.Invoke(effectiveValueEntry, null);
if (value == lastObjectDeleted)
{
foundEntry = true;
break;
}
index++;
}
if (foundEntry)
{
effectiveValues.SetValue(null, index);
}
}
}
}
protected MethodInfo EffectiveValueEntryValueGetMethod
{
get
{
if (effectiveValueEntryValueGetMethod == null)
{
var effectiveValueEntryType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.Name == "EffectiveValueEntry").FirstOrDefault();
if (effectiveValueEntryType == null)
throw new InvalidOperationException();
var effectiveValueEntryValuePropertyInfo = effectiveValueEntryType.GetProperty("Value", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance);
if (effectiveValueEntryValuePropertyInfo == null)
throw new InvalidOperationException();
effectiveValueEntryValueGetMethod = effectiveValueEntryValuePropertyInfo.GetGetMethod(nonPublic: true);
if (effectiveValueEntryValueGetMethod == null)
throw new InvalidOperationException();
}
return effectiveValueEntryValueGetMethod;
}
}
protected MethodInfo EffectiveValuesGetMethod
{
get
{
if (effectiveValuesGetMethod == null)
{
var dependencyObjectType = typeof(DependencyObject);
var effectiveValuesPropertyInfo = dependencyObjectType.GetProperty("EffectiveValues", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance);
if (effectiveValuesPropertyInfo == null)
throw new InvalidOperationException();
effectiveValuesGetMethod = effectiveValuesPropertyInfo.GetGetMethod(nonPublic: true);
if (effectiveValuesGetMethod == null)
throw new InvalidOperationException();
}
return effectiveValuesGetMethod;
}
}
#region Private fields
private MethodInfo effectiveValueEntryValueGetMethod;
private MethodInfo effectiveValuesGetMethod;
#endregion
}
Run Code Online (Sandbox Code Playgroud)