如何从HashSet <T>检索实际项目?

我已经阅读了这个问题 ,为什么这是不可能的,但还没有find解决问题的办法。

我想从.NET HashSet<T>检索一个项目。 我正在寻找一个有这个签名的方法:

 /// <summary> /// Determines if this set contains an item equal to <paramref name="item"/>, /// according to the comparison mechanism that was used when the set was created. /// The set is not changed. If the set does contain an item equal to /// <paramref name="item"/>, then the item from the set is returned. /// </summary> bool TryGetItem<T>(T item, out T foundItem); 

用这种方法search一个项目的集合将是O(1)。 从HashSet<T>检索项目的唯一方法是枚举O(n)的所有项目。

我还没有find任何解决方法,这个问题,然后做我自己的HashSet<T>或使用一个Dictionary<K, V> 。 任何其他的想法?

注意:
我不想检查HashSet<T>包含该项目。 我想获得存储在HashSet<T>的项目的引用,因为我需要更新它(而不是由另一个实例replace它)。 我将传递给TryGetItem的项目是相等的(根据传递给构造函数的比较机制),但它不会是相同的引用。

这实际上是一组collections中的一个巨大的遗漏。 你只需要一个键字典或一个允许检索对象引用的HashSet。 有这么多人问过,为什么没有固定超出我的意思。

如果没有第三方库,最好的解决方法是使用Dictionary<T, T>键的值与值相同,因为Dictionary将其条目存储为散列表。 性能方面与HashSet相同,但是当然浪费内存(每个条目的指针大小)。

 Dictionary<T, T> myHashedCollection; ... if(myHashedCollection.ContainsKey[item]) item = myHashedCollection[item]; //replace duplicate else myHashedCollection.Add(item, item); //add previously unknown item ... //work with unique item 

怎么样重载string相等比较器:

  class StringEqualityComparer : IEqualityComparer<String> { public string val1; public bool Equals(String s1, String s2) { if (!s1.Equals(s2)) return false; val1 = s1; return true; } public int GetHashCode(String s) { return s.GetHashCode(); } } public static class HashSetExtension { public static bool TryGetValue(this HashSet<string> hs, string value, out string valout) { if (hs.Contains(value)) { valout=(hs.Comparer as StringEqualityComparer).val1; return true; } else { valout = null; return false; } } } 

然后声明HashSet为:

 HashSet<string> hs = new HashSet<string>(new StringEqualityComparer()); 

另一个技巧将通过访问HashSet的内部函数InternalIndexOf来完成reflection。 请记住,字段名是硬编码的,所以如果在即将到来的.NET版本中发生这些变化,这将会中断。 下面的解决scheme只支持一种特定的types(例如,string),但也可以通用我猜。

 public static class Extensions { private static Func<HashSet<string>, string, string> getHashSetInternalValue; static Extensions() { ParameterExpression targetExp = Expression.Parameter(typeof(HashSet<string>), "target"); ParameterExpression itemExp = Expression.Parameter(typeof(string), "item"); var slotsExp = Expression.Field(targetExp, typeof(HashSet<string>).GetField("m_slots", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)); var indexExp = Expression.Call(targetExp, typeof(HashSet<string>).GetMethod("InternalIndexOf", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance), itemExp); var slotExp = Expression.ArrayAccess(slotsExp, indexExp); var valueExp = Expression.Field(slotExp, "value"); var testExp = Expression.GreaterThanOrEqual(indexExp, Expression.Constant(0)); var conditionExp = Expression.Condition(testExp, valueExp, Expression.Constant(null, typeof(string))); getHashSetInternalValue = Expression.Lambda<Func<HashSet<string>, string, string>>(conditionExp, new[] { targetExp, itemExp }).Compile(); } /// <summary> /// Gets the internal item value equal to <paramref name="item"/> or null if <paramref name="item"/> is not contained /// </summary> public static string GetInternalValue(this HashSet<string> hashet, string item) { return getHashSetInternalValue(hashet, item); } } 

testing:

 var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "TABLE" }; var intern = set.GetInternalValue("table"); Console.WriteLine(intern); // prints "TABLE" 

在这种情况下,SortedSet可能会有O(log n)查找时间,如果使用这个选项的话。 仍然不是O(1),但至less更好。

@ mp666的修改实现的答案,以便它可以用于任何types的HashSet,并允许重写默认的相等比较器。

 public interface IRetainingComparer<T> : IEqualityComparer<T> { T Key { get; } void ClearKeyCache(); } /// <summary> /// An <see cref="IEqualityComparer{T}"/> that retains the last key that successfully passed <see cref="IEqualityComparer{T}.Equals(T,T)"/>. /// This class relies on the fact that <see cref="HashSet{T}"/> calls the <see cref="IEqualityComparer{T}.Equals(T,T)"/> with the first parameter /// being an existing element and the second parameter being the one passed to the initiating call to <see cref="HashSet{T}"/> (eg. <see cref="HashSet{T}.Contains(T)"/>). /// </summary> /// <typeparam name="T">The type of object being compared.</typeparam> /// <remarks>This class is thread-safe but may should not be used with any sort of parallel access (PLINQ).</remarks> public class RetainingEqualityComparerObject<T> : IRetainingComparer<T> where T : class { private readonly IEqualityComparer<T> _comparer; [ThreadStatic] private static WeakReference<T> _retained; public RetainingEqualityComparerObject(IEqualityComparer<T> comparer) { _comparer = comparer; } /// <summary> /// The retained instance on side 'a' of the <see cref="Equals"/> call which successfully met the equality requirement agains side 'b'. /// </summary> /// <remarks>Uses a <see cref="WeakReference{T}"/> so unintended memory leaks are not encountered.</remarks> public T Key { get { T retained; return _retained == null ? null : _retained.TryGetTarget(out retained) ? retained : null; } } /// <summary> /// Sets the retained <see cref="Key"/> to the default value. /// </summary> /// <remarks>This should be called prior to performing an operation that calls <see cref="Equals"/>.</remarks> public void ClearKeyCache() { _retained = _retained ?? new WeakReference<T>(null); _retained.SetTarget(null); } /// <summary> /// Test two objects of type <see cref="T"/> for equality retaining the object if successful. /// </summary> /// <param name="a">An instance of <see cref="T"/>.</param> /// <param name="b">A second instance of <see cref="T"/> to compare against <paramref name="a"/>.</param> /// <returns>True if <paramref name="a"/> and <paramref name="b"/> are equal, false otherwise.</returns> public bool Equals(T a, T b) { if (!_comparer.Equals(a, b)) { return false; } _retained = _retained ?? new WeakReference<T>(null); _retained.SetTarget(a); return true; } /// <summary> /// Gets the hash code value of an instance of <see cref="T"/>. /// </summary> /// <param name="o">The instance of <see cref="T"/> to obtain a hash code from.</param> /// <returns>The hash code value from <paramref name="o"/>.</returns> public int GetHashCode(T o) { return _comparer.GetHashCode(o); } } /// <summary> /// An <see cref="IEqualityComparer{T}"/> that retains the last key that successfully passed <see cref="IEqualityComparer{T}.Equals(T,T)"/>. /// This class relies on the fact that <see cref="HashSet{T}"/> calls the <see cref="IEqualityComparer{T}.Equals(T,T)"/> with the first parameter /// being an existing element and the second parameter being the one passed to the initiating call to <see cref="HashSet{T}"/> (eg. <see cref="HashSet{T}.Contains(T)"/>). /// </summary> /// <typeparam name="T">The type of object being compared.</typeparam> /// <remarks>This class is thread-safe but may should not be used with any sort of parallel access (PLINQ).</remarks> public class RetainingEqualityComparerStruct<T> : IRetainingComparer<T> where T : struct { private readonly IEqualityComparer<T> _comparer; [ThreadStatic] private static T _retained; public RetainingEqualityComparerStruct(IEqualityComparer<T> comparer) { _comparer = comparer; } /// <summary> /// The retained instance on side 'a' of the <see cref="Equals"/> call which successfully met the equality requirement agains side 'b'. /// </summary> public T Key => _retained; /// <summary> /// Sets the retained <see cref="Key"/> to the default value. /// </summary> /// <remarks>This should be called prior to performing an operation that calls <see cref="Equals"/>.</remarks> public void ClearKeyCache() { _retained = default(T); } /// <summary> /// Test two objects of type <see cref="T"/> for equality retaining the object if successful. /// </summary> /// <param name="a">An instance of <see cref="T"/>.</param> /// <param name="b">A second instance of <see cref="T"/> to compare against <paramref name="a"/>.</param> /// <returns>True if <paramref name="a"/> and <paramref name="b"/> are equal, false otherwise.</returns> public bool Equals(T a, T b) { if (!_comparer.Equals(a, b)) { return false; } _retained = a; return true; } /// <summary> /// Gets the hash code value of an instance of <see cref="T"/>. /// </summary> /// <param name="o">The instance of <see cref="T"/> to obtain a hash code from.</param> /// <returns>The hash code value from <paramref name="o"/>.</returns> public int GetHashCode(T o) { return _comparer.GetHashCode(o); } } /// <summary> /// Provides TryGetValue{T} functionality similar to that of <see cref="IDictionary{TKey,TValue}"/>'s implementation. /// </summary> public class ExtendedHashSet<T> : HashSet<T> { /// <summary> /// This class is guaranteed to wrap the <see cref="IEqualityComparer{T}"/> with one of the <see cref="IRetainingComparer{T}"/> /// implementations so this property gives convenient access to the interfaced comparer. /// </summary> private IRetainingComparer<T> RetainingComparer => (IRetainingComparer<T>)Comparer; /// <summary> /// Creates either a <see cref="RetainingEqualityComparerStruct{T}"/> or <see cref="RetainingEqualityComparerObject{T}"/> /// depending on if <see cref="T"/> is a reference type or a value type. /// </summary> /// <param name="comparer">(optional) The <see cref="IEqualityComparer{T}"/> to wrap. This will be set to <see cref="EqualityComparer{T}.Default"/> if none provided.</param> /// <returns>An instance of <see cref="IRetainingComparer{T}"/>.</returns> private static IRetainingComparer<T> Create(IEqualityComparer<T> comparer = null) { return (IRetainingComparer<T>) (typeof(T).IsValueType ? Activator.CreateInstance(typeof(RetainingEqualityComparerStruct<>) .MakeGenericType(typeof(T)), comparer ?? EqualityComparer<T>.Default) : Activator.CreateInstance(typeof(RetainingEqualityComparerObject<>) .MakeGenericType(typeof(T)), comparer ?? EqualityComparer<T>.Default)); } public ExtendedHashSet() : base(Create()) { } public ExtendedHashSet(IEqualityComparer<T> comparer) : base(Create(comparer)) { } public ExtendedHashSet(IEnumerable<T> collection) : base(collection, Create()) { } public ExtendedHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) : base(collection, Create(comparer)) { } /// <summary> /// Attempts to find a key in the <see cref="HashSet{T}"/> and, if found, places the instance in <paramref name="original"/>. /// </summary> /// <param name="value">The key used to search the <see cref="HashSet{T}"/>.</param> /// <param name="original"> /// The matched instance from the <see cref="HashSet{T}"/> which is not neccessarily the same as <paramref name="value"/>. /// This will be set to null for reference types or default(T) for value types when no match found. /// </param> /// <returns>True if a key in the <see cref="HashSet{T}"/> matched <paramref name="value"/>, False if no match found.</returns> public bool TryGetValue(T value, out T original) { var comparer = RetainingComparer; comparer.ClearKeyCache(); if (Contains(value)) { original = comparer.Key; return true; } original = default(T); return false; } } public static class HashSetExtensions { /// <summary> /// Attempts to find a key in the <see cref="HashSet{T}"/> and, if found, places the instance in <paramref name="original"/>. /// </summary> /// <param name="hashSet">The instance of <see cref="HashSet{T}"/> extended.</param> /// <param name="value">The key used to search the <see cref="HashSet{T}"/>.</param> /// <param name="original"> /// The matched instance from the <see cref="HashSet{T}"/> which is not neccessarily the same as <paramref name="value"/>. /// This will be set to null for reference types or default(T) for value types when no match found. /// </param> /// <returns>True if a key in the <see cref="HashSet{T}"/> matched <paramref name="value"/>, False if no match found.</returns> /// <exception cref="ArgumentNullException">If <paramref name="hashSet"/> is null.</exception> /// <exception cref="ArgumentException"> /// If <paramref name="hashSet"/> does not have a <see cref="HashSet{T}.Comparer"/> of type <see cref="IRetainingComparer{T}"/>. /// </exception> public static bool TryGetValue<T>(this HashSet<T> hashSet, T value, out T original) { if (hashSet == null) { throw new ArgumentNullException(nameof(hashSet)); } if (hashSet.Comparer.GetType().IsInstanceOfType(typeof(IRetainingComparer<T>))) { throw new ArgumentException($"HashSet must have an equality comparer of type '{nameof(IRetainingComparer<T>)}' to use this functionality", nameof(hashSet)); } var comparer = (IRetainingComparer<T>)hashSet.Comparer; comparer.ClearKeyCache(); if (hashSet.Contains(value)) { original = comparer.Key; return true; } original = default(T); return false; } } 

HashSet有一个Contains(T)方法。

如果您需要自定义比较方法(例如,存储人员对象,但使用SSN进行平等比较),则可以指定IEqualityComparer

好的,你可以这样做

 YourObject x = yourHashSet.Where(w => w.Name.Contains("strin")).FirstOrDefault(); 

这是获取所选对象的新实例。 为了更新你的对象,那么你应该使用:

 yourHashSet.Where(w => w.Name.Contains("strin")).FirstOrDefault().MyProperty = "something"; 

您也可以使用ToList()方法并将索引器应用于该方法。

 HashSet<string> mySet = new HashSet(); mySet.Add("mykey"); string key = mySet.toList()[0];