6

の結果を生成するために使用される ID を持つListBoxアイテムのコレクションにバインドしていますGetHashCode()。新しいアイテムが追加されると、最初にデータベースに保存されるまで ID 0 になります。ListBoxこれは私が不平を言う原因です。その理由は、アイテムが最初に使用されたときに、ハッシュコードが変更されることを予期しないListBox内部に格納されているためだと思います。Dictionary

保存されていないアイテムをコレクションから削除することでこれを修正できます (この段階で UI に通知して辞書から削除する必要があります)。DB に保存し、コレクションに追加し直します。Save(BusinessObject obj)これは面倒で、メソッドからコレクションに常にアクセスできるとは限りません。誰かがこの問題の別の解決策を持っていますか?

編集ブラムの答えに応えて:

MVVM を使用しているため、バインディングを使用するようにコードを変更しました。問題を再現するには、[追加] をクリックし、アイテムを選択して [保存] をクリックし、繰り返してから、選択を試みます。ListBoxこれは、がまだ internal の古いハッシュコードを保持していることを示していると思い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++;
        }
    }
}

EDIT 2いくつかのテストの後、いくつかのことを発見しました:

  • アイテムのハッシュ コードを変更しても問題ListBoxないようです。

  • 選択したアイテムのハッシュ コードを変更すると、その機能がListBox壊れ
    ます。

選択が行われると (単一または複数選択モード)、IList ListBox.SelectedItemsが更新されます。選択に追加されたアイテムは追加され、選択にSelectedItems含まれなくなったアイテムは削除されます。

アイテムが選択されている間にアイテムのハッシュ コードが変更された場合、それを から削除する方法はありませんSelectedItems。を手動で呼び出してもSelectedItems.Remove(item)-1SelectedItems.Clear()に設定SelectedIndexしても効果はなく、アイテムは に残り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]);

繰り返しますが、コードです。を変更したとしIdますkey1。ハッシュコードも変更されました:

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

繰り返しますが、外部コードです。ここでは、次の方法でいくつかのデータを見つけようとしますkey1:

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

もちろん、 とをオーバーライドする可変型を設計し、可変データのハッシュ コードを計算することもできます。しかし、実際にはそうすべきではありません (自分が何をしているのかが明確にわかっている場合を除きます)。GetHashCodeEquals

外部コードがまたは内部的に使用しないことを保証することはできません。Dictionary<TKey, TValue>HashSet<T>

于 2013-05-28T10:11:06.997 に答える
1

あなたのコードの問題は、オーバーライドしなかったことだと思いますEquals

ListBoxを使用Equalsしてアイテムを検索するため、true を返す複数の Equals がある場合、複数のアイテムに一致し、単純に混乱します。
の項目は、ListBox等しいことに基づいて一意である必要があります。aを List Int32 または List 文字列に
バインドして値のいずれかを繰り返そうとすると、同じ問題が発生します。ListBox

文句を言うとき。それはどのように不平を言うのですか?

以下のこの簡単な例で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 に答える