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