WPF ListBox和具有更改Hashcode的项目

Cod*_*095 5 c# wpf listbox mvvm

我有一个ListBox项目集合,它们具有用于生成结果的ID GetHashCode().添加新项目时,它的ID为0,直到它首次保存到我们的数据库中.这导致我ListBox抱怨; 我相信原因是因为当一个项目首次被ListBox它使用时,它存储在一个Dictionary不希望哈希码改变的内部.

我可以通过从集合中删除未保存的项来解决这个问题(我必须在此阶段通知UI将其从字典中删除),保存到数据库,然后将其添加回集合中.这很麻烦,我并不总是可以从我的Save(BusinessObject obj)方法访问该集合.有没有人有这个问题的替代解决方案?

编辑在回应Blam的回答:

我正在使用MVVM,所以我修改了代码以使用绑定.要重现问题,请单击"添加",选择项目,单击"保存","重复",然后尝试进行选择.我认为这表明它ListBox仍然保留在其内部的旧哈希码中Dictionary,因此冲突的键错误.

<Window x:Class="ListBoxHashCode.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
            <Button Click="Button_Click_Add" Content="Add"/>
            <Button Click="Button_Click_Save" Content="Save Selected"/>
        </StackPanel>
        <ListBox Grid.Row="1" ItemsSource="{Binding List}" DisplayMemberPath="ID" SelectedItem="{Binding Selected}"/> 
    </Grid>
</Window>

public partial class MainWindow : Window {

    public ObservableCollection<ListItem> List { get; private set; }        
    public ListItem Selected { get; set; }
    private Int32 saveId;

    public MainWindow() {
        this.DataContext = this;            
        this.List = new ObservableCollection<ListItem>();
        this.saveId = 100;
        InitializeComponent();
    }

    private void Button_Click_Add(object sender, RoutedEventArgs e) {
        this.List.Add(new ListItem(0));
    }

    private void Button_Click_Save(object sender, RoutedEventArgs e) {
        if (Selected != null && Selected.ID == 0) {
            Selected.ID = saveId;
            saveId++;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑2经过一些测试,我发现了一些东西:

  • 更改一个项目的哈希码ListBox似乎工作正常.

  • 改变的哈希码所选择的项目ListBox中断
    它的功能.

当进行选择(单个或多个选择模式)时,IList ListBox.SelectedItems更新.添加到选择中的项目将被添加到,SelectedItems并且将删除选择中不再包含的项目.

如果项目的哈希码在选中时更改,则无法将其从中删除SelectedItems.即使是手动调用SelectedItems.Remove(item),SelectedItems.Clear()并且设置SelectedIndex为-1都没有效果,并且该项目仍保留在IList.这会导致在下次选择异常后抛出异常,因为我相信它会再次添加SelectedItems.

Den*_*nis 3

有人有解决这个问题的替代方案吗?

对象的哈希码在对象的生命周期内不得更改。您不应使用可变数据进行哈希码计算。

更新

没想到我的回答会引起这么大的讨论。这里有一些详细的解释,可能会对OP有所帮助。

让我们看一下代码中定义的一些可变实体类型,它会GetHashCode覆盖Equals. 平等是建立在Id平等的基础上的:

class Mutable : IEquatable<Mutable>
{
    public int Id { get; set; }

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

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

        var mutable = obj as Mutable;
        if (mutable == null)
        {
            return false;
        }

        return this.Equals(mutable);
    }

    public bool Equals(Mutable other)
    {
        return Id.Equals(other.Id);
    }
}
Run Code Online (Sandbox Code Playgroud)

您在代码中的某个位置创建了此类型的多个实例:

        // here's some mutable entities with hash-code, calculated using mutable data:
        var key1 = new Mutable { Id = 1 };
        var key2 = new Mutable { Id = 2 };
        var key3 = new Mutable { Id = 3 };
Run Code Online (Sandbox Code Playgroud)

这是一些外部代码,用于Dictionary<Mutable, string>其内部目的:

        // let's use them as a key for the dictionary:
        var dictionary = new Dictionary<Mutable, string>
        {
            { key1, "John" },
            { key2, "Mary" },
            { key3, "Peter" }
        };

        // everything is ok, all of the keys are located properly:
        Console.WriteLine(dictionary[key1]);
        Console.WriteLine(dictionary[key2]);
        Console.WriteLine(dictionary[key3]);
Run Code Online (Sandbox Code Playgroud)

再说一次,你的代码。假设,你已经改变Idkey1。哈希码也发生了变化:

        // let's change the hashcode of key1:
        key1.Id = 4;
Run Code Online (Sandbox Code Playgroud)

再次,外部代码。在这里,它尝试通过以下方式查找一些数据key1

Console.WriteLine(dictionary[key1]); // ooops! key1 was not found in dictionary
Run Code Online (Sandbox Code Playgroud)

当然,您可以设计可变类型,它会覆盖GetHashCodeEquals,并计算可变数据的哈希码。但你真的不应该这样做(除非你明确知道你在做什么)。

不能保证任何外部代码都不会Dictionary<TKey, TValue>HashSet<T>内部使用。

  • 虽然您的答案可以解决问题,但作为一般指导,情况并非如此:来自 [Microsoft 文档](http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx)“GetHashCode 方法因为只要对象状态没有修改,对象就必须始终返回相同的哈希码[...]” - 因此状态的更改可以(并且通常*应该*)更改哈希码。也就是说,我长期以来一直认为这是“ListBox”实现的一个失败,它似乎依赖于“HashCode”来进行选择逻辑。 (5认同)
  • 微软在 MSDN (msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx) 中指出,当对象状态发生变化而可能影响调用的返回值时,GetHashCode 的值必须发生变化。 Equals(),甚至在它的示例中,它也显示了完全依赖于公共可更改值的 GetHashCode 实现。 (2认同)
  • @Dennis:任何实现非引用“等于”的对象*必须*具有一个根据其可变数据而变化的哈希码,以便正确运行。我不认为文档与这里的任何内容相矛盾,但是“ListBox”有一个未记录的依赖项。 (2认同)