How to retrieve actual item from HashSet?

前端 未结 11 560
花落未央
花落未央 2020-12-02 17:55

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. I

11条回答
  •  我在风中等你
    2020-12-02 18:29

    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;
        }
    }
    

提交回复
热议问题