6

ListBox绑定到一组项目,这些项目的 ID 用于生成GetHashCode(). 添加新项目时,它的 ID 为 0,直到它首次保存到我们的数据库中。这导致我ListBox抱怨;我相信原因是因为当一个项目第一次被 a 使用时,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++;
        }
    }
}

编辑 2 经过一些测试,我发现了一些事情:

  • 更改 a 中项目的哈希码ListBox似乎可以正常工作。

  • 更改所选项目的哈希码ListBox会破坏
    其功能。

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

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

4

2 回答 2

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);
    }
}

在您的代码中的某处,您创建了几个这种类型的实例:

        // 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 };

这是一些外部代码,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]);

再次,您的代码。假设,你改变Idkey1. 哈希码也发生了变化:

        // let's change the hashcode of key1:
        key1.Id = 4;

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

Console.WriteLine(dictionary[key1]); // ooops! key1 was not found in dictionary

当然,您可以设计可变类型,覆盖GetHashCodeand Equals,并计算可变数据的哈希码。但是你不应该这样做,真的(除了那些你肯定知道你在做什么的情况)。

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

于 2013-05-28T10:11:06.997 回答
1

我怀疑您的代码的问题在于它没有覆盖Equals.

ListBox用于Equals查找一个项目,因此如果有多个返回 true 的 Equals 则它匹配多个项目并且只是简单的混乱。
中的项目ListBox必须基于 Equals 是唯一的。
如果您尝试将 a 绑定ListBox到 List Int32 或 List 字符串并重复任何值,那么它会遇到同样的问题。

当你说抱怨。它是如何抱怨的?

在下面这个简单的示例中ListView,即使更改了GetHashCode.

你实施了INotifyPropertyChanged吗?

<Window x:Class="ListViewGetHashCode.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 Grid.Row="0" Orientation="Horizontal">
            <Button Click="Button_Click" Content="Button"/>
            <Button Click="Button_Click2" Content="Add"/>
            <Button Grid.Row="0" Click="Button_Click_Save" Content="Save"/>
        </StackPanel>
        <ListBox Grid.Row="1" ItemsSource="{Binding BindingList}" DisplayMemberPath="ID" SelectedItem="{Binding Selected}" VirtualizingStackPanel.VirtualizationMode="Standard"/>
        <!--<ListBox Grid.Row="1" x:Name="lbHash" ItemsSource="{Binding}" DisplayMemberPath="ID"/>--> 
    </Grid>
</Window>

using System.ComponentModel;
using System.Collections.ObjectModel;

namespace ListViewGetHashCode
{
    public partial class MainWindow : Window
    {
        ObservableCollection<ListItem> li = new ObservableCollection<ListItem>();
        private Int32 saveId = 100;
        private Int32 tempId = -1;
        public MainWindow()
        {
            this.DataContext = this;
            for (Int32 i = 1; i < saveId; i++) li.Add(new ListItem(i));

            InitializeComponent();

        }
        public ObservableCollection<ListItem> BindingList { get { return li; } }
        public ListItem Selected { get; set; }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Int32 counter = 0;
            foreach (ListItem l in li)
            {
                l.ID = -l.ID;
                counter++;
                if (counter > 100) break;
            }
        }
        private void Button_Click2(object sender, RoutedEventArgs e)
        {          
            //li.Add(new ListItem(0)); // this is where it breaks as items were not unique
            li.Add(new ListItem(tempId));
            tempId--;
        }   
        private void Button_Click_Save(object sender, RoutedEventArgs e)
        {
            if (Selected != null && Selected.ID <= 0)
            {
                Selected.ID = saveId;
                saveId++;
            }
        }
    }
    public class ListItem : Object, INotifyPropertyChanged
    {
        private Int32 id;
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }  
        public Int32 ID 
        {
            get 
            { 
                return (id < 0) ? 0 : id;
                //if you want users to see 0 and not the temp id 
                //internally much use id
                //return id;
            }
            set
            {
                if (id == value) return;
                id = value;
                NotifyPropertyChanged("ID");
            }
        }
        public override bool Equals(object obj)
        {
            if (obj is ListItem)
            {
                ListItem comp = (ListItem)obj;
                return (comp.id == this.id);
            }
            else return false;
        }
        public bool Equals(ListItem comp)
        {
            return (comp.id == this.id);
        }
        public override int GetHashCode()
        {
            System.Diagnostics.Debug.WriteLine("GetHashCode " + id.ToString());
            return id;
            //can even return 0 as the hash for negative but it will only slow 
            //things downs
            //if (id > 0) return id;
            //else return 0;
        }
        public ListItem(Int32 ID) { id = ID; }
    }
}
于 2013-05-28T15:06:14.587 回答