我最近用几种不同的方式问过这个问题,但没有得到一个答案来告诉我<T,U>
当我持有对发生变化的事物的引用时需要如何处理字典T.GetHashCode()
。对于这个问题,“状态”是指在检查时也会检查的属性和字段Equals()
。假设所有公共、内部和受保护的成员都包括在内。
鉴于我有一个 C# 对象
覆盖 GetHashCode 和 Equals
该对象作为Key值保存到Dictionary中(注意我的理解是Dictionary此时会读取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
这是否意味着我需要从 Dictionary 中删除该对象并重新添加它?这是正确/最好的方法吗?