3

私は最近、いくつかの異なる方法でこの質問をしましたが、変化するものへの参照を保持するときに Dictionary をどのよう<T,U>に処理する必要があるかを示す答えが得られませんT.GetHashCode()。この質問の目的上、「状態」とは、 がチェックされているときにもチェックされるプロパティとフィールドを指しEquals()ます。すべての public、internal、および protected メンバーが含まれていると想定します。

私がC#オブジェクトを持っているとすれば

  • GetHashCode と Equals をオーバーライドします

  • このオブジェクトはキー値としてディクショナリに保存されます (ディクショナリはこの時点で GetHashCode 値を読み取ることに注意してください)。

  • Key でオブジェクトを検索し、値を変更します。(この値を変更すると、カスタム equals 関数が変更され、場合によっては gethashcode が変更されます)

私の質問は、GetHashCode は何を反映する必要があるかということです。この関数の戻り値は、オブジェクトの元の状態または変更された状態を反映する必要がありますか?

サンプルコード

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TrustMap
{
    class Program
    {
       static  Dictionary<Model.TrustedEntityReference, string> testDictionary = new Dictionary<Model.TrustedEntityReference, string>();

        static void Main(string[] args)
        {
            Model.TrustedEntity te = new Model.TrustedEntity();

            te.BackTrustLink = null;
            te.ForwardTrustLink = null;
            te.EntryName = "test1";

            var keyValue =  new Model.TrustedEntityReference()
            {
                HierarchyDepth = 1,
               trustedEntity = te 
            };

            testDictionary.Add(keyValue, "some data");

            // Now that I have a reference to the Key outside the object
            te.EntryName = "modified data";

            // Question: how should TE respond to the change, considering that it's a part of a dictionary now?
            //           If this is a implementation error, how should I track of objects that are stored as Keys that shouldn't be modified like I did in the previous line?

        }
    }

}

namespace Model
{
    public class TrustedEntity
    {
        public TrustedEntity()
        {
            this.BackTrustLink = new List<TrustedEntityReference>();
            this.ForwardTrustLink = new List<TrustedEntityReference>();
        }

        public List<TrustedEntityReference> BackTrustLink { get; set; }

        public string EntryName { get; set; }

        public List<TrustedEntityReference> ForwardTrustLink { get; set; }

    }

    public class TrustedEntityReference 
    {
        public int HierarchyDepth { get; set; }
        public TrustedEntity trustedEntity {get; set; }

        public override bool Equals(object obj)
        {
            if (obj.GetType() != trustedEntity.GetType())
                return false;

            TrustedEntity typedObj = (TrustedEntity)obj;

            if (typedObj.BackTrustLink != null)
            { 
                if (trustedEntity.BackTrustLink != typedObj.BackTrustLink)
                    return false;
            }

            if (typedObj.ForwardTrustLink != null)
            {
                if (trustedEntity.ForwardTrustLink != typedObj.ForwardTrustLink)
                    return false;
            }

            if (trustedEntity.EntryName != typedObj.EntryName)
                return false;

            return true;
        }

        /// <summary>
        /// If the hash-code for two items does not match, they may never be considered equal
        /// Therefore equals may never get called.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {

            // if two things are equal (Equals(...) == true) then they must return the same value for GetHashCode()
            // if the GetHashCode() is equal, it is not necessary for them to be the same; this is a collision, and Equals will be called to see if it is a real equality or not.
           // return base.GetHashCode();
            return StackOverflow.System.HashHelper.GetHashCode<int, TrustedEntity>(this.HierarchyDepth, this.trustedEntity);
        }
    }


}

namespace StackOverflow.System
{
    /// <summary>
    /// Source https://stackoverflow.com/a/2575444/328397
    /// 
    /// Also it has extension method to provide a fluent interface, so you can use it like this:
///public override int GetHashCode()
///{
///    return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
///}
///or like this:

///public override int GetHashCode()
///{
///    return 0.CombineHashCode(Manufacturer)
///        .CombineHashCode(PartN)
///        .CombineHashCode(Quantity);
///}
    /// </summary>
    public static class HashHelper
    {
        public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
        {
            unchecked
            {
                return 31 * arg1.GetHashCode() + arg2.GetHashCode();
            }
        }

        public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
        {
            unchecked
            {
                int hash = arg1.GetHashCode();
                hash = 31 * hash + arg2.GetHashCode();
                return 31 * hash + arg3.GetHashCode();
            }
        }

        public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3,
            T4 arg4)
        {
            unchecked
            {
                int hash = arg1.GetHashCode();
                hash = 31 * hash + arg2.GetHashCode();
                hash = 31 * hash + arg3.GetHashCode();
                return 31 * hash + arg4.GetHashCode();
            }
        }

        public static int GetHashCode<T>(T[] list)
        {
            unchecked
            {
                int hash = 0;
                foreach (var item in list)
                {
                    hash = 31 * hash + item.GetHashCode();
                }
                return hash;
            }
        }

        public static int GetHashCode<T>(IEnumerable<T> list)
        {
            unchecked
            {
                int hash = 0;
                foreach (var item in list)
                {
                    hash = 31 * hash + item.GetHashCode();
                }
                return hash;
            }
        }

        /// <summary>
        /// Gets a hashcode for a collection for that the order of items 
        /// does not matter.
        /// So {1, 2, 3} and {3, 2, 1} will get same hash code.
        /// </summary>
        public static int GetHashCodeForOrderNoMatterCollection<T>(
            IEnumerable<T> list)
        {
            unchecked
            {
                int hash = 0;
                int count = 0;
                foreach (var item in list)
                {
                    hash += item.GetHashCode();
                    count++;
                }
                return 31 * hash + count.GetHashCode();
            }
        }

        /// <summary>
        /// Alternative way to get a hashcode is to use a fluent 
        /// interface like this:<br />
        /// return 0.CombineHashCode(field1).CombineHashCode(field2).
        ///     CombineHashCode(field3);
        /// </summary>
        public static int CombineHashCode<T>(this int hashCode, T arg)
        {
            unchecked
            {
                return 31 * hashCode + arg.GetHashCode();
            }
        }
    }

}

Jon Skeet からのこの回答に基づいて(以前の質問に対して)

「最終的にキーの値を変更するプロパティを変更したら、どうすればよいですか?」- 自分

.

「基本的に、あなたは詰め物をしています。辞書でそのキーを再び見つけることはできません (または、少なくともおそらく不可能です)。できる限り慎重にこれを避ける必要があります。個人的には、通常、辞書キーの有力な候補は、不変性の有力な候補でもあります。」-JS

これは、辞書からオブジェクトを削除して、再度追加する必要があるということですか? これは適切な/最善の方法ですか?

4

2 に答える 2

2

明確にするために、キーと値のペアのキー部分を変更しています。

質問が明確になったので、答えは比較的簡単です。

これは、辞書からオブジェクトを削除して、再度追加する必要があるということですか?

はい。ただし、変更するに削除する必要があります。したがって、次のように記述します。

testDictionary.Add(keyValue, "some data");
// Do whatever...

testDictionary.Remove(keyValue);
te.EntryName = "modified data";
testDictionary.Add(keyValue, "some data"); // Or a different value...

ただし、一般的には、不変のデータ構造のみを辞書のキーとして使用する方がはるかにリスクが低くなります。

また、現在、あなたのメソッドは関連する 2 つのリストの参照Equalsの等価性に依存していることにも注意してください。それは本当にあなたが望むものですか? また、でオーバーライドしてないため、同じリストで新しいを作成したとしても、必要な結果が得られません。基本的に、どのような等価演算が必要かは不明です。それを自分自身で明確にし、理想的には、関連するデータの不変表現を作成する必要があります。GetHashCodeTrustedEntityTrustedEntity

于 2012-10-17T22:31:01.967 に答える
1

変更する必要があるかどうかという問題GetHashCode()は、ちょっと厄介です。私は6つの公理を提案します:

  1. すべてのオブジェクトは、常にそれ自体と等しいことが観察される必要があります。
  2. あるオブジェクトが別のオブジェクトと同等であることが観察された場合、両方のオブジェクトは、互いに同等であると報告する必要があります。
  3. あるオブジェクトが別のオブジェクトと等しくないことが観察されたことがあれば、両方のオブジェクトが自分自身を別のオブジェクトと等しくないと報告する必要があります。
  4. 1 つのオブジェクトが別のオブジェクトと等しいことが観察され、いずれかが 3 番目のオブジェクトと等しいことが観察された場合、両方がその 3 番目のオブジェクトと等しいことを永遠に報告する必要があります。
  5. あるオブジェクトが別のオブジェクトと等しいことが観察されたことがあり、いずれかが 3 番目のオブジェクトと等しくないことが観察された場合、両方がその 3 番目のオブジェクトと等しくないと報告する必要があります。
  6. オブジェクトのハッシュ コードの観察は、これまでに異なるハッシュ コードを返したすべてのオブジェクトと等しくないという観察を表します。

オブジェクトのハッシュ コードが変更されないという要件は、公理の 1 つではなく、ポイント 1 と 6 から導き出されます。以前の観測とは異なるオブジェクトのハッシュ コードの観測は、オブジェクトがそれ自体と等しくないという観測を構成します。

于 2013-06-16T02:51:23.573 に答える