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
Another Trick would do Reflection, by accessing the internal function InternalIndexOf
of HashSet. Keep in mind the fieldnames are hardcoded, so if those change in upcoming .NET versions this will break.
Note: If you use Mono, you should change field name from m_slots
to _slots
.
internal static class HashSetExtensions<T>
{
public delegate bool GetValue(HashSet<T> source, T equalValue, out T actualValue);
public static GetValue TryGetValue { get; }
static HashSetExtensions() {
var targetExp = Expression.Parameter(typeof(HashSet<T>), "target");
var itemExp = Expression.Parameter(typeof(T), "item");
var actualValueExp = Expression.Parameter(typeof(T).MakeByRefType(), "actualValueExp");
var indexVar = Expression.Variable(typeof(int), "index");
// ReSharper disable once AssignNullToNotNullAttribute
var indexExp = Expression.Call(targetExp, typeof(HashSet<T>).GetMethod("InternalIndexOf", BindingFlags.NonPublic | BindingFlags.Instance), itemExp);
var truePart = Expression.Block(
Expression.Assign(
actualValueExp, Expression.Field(
Expression.ArrayAccess(
// ReSharper disable once AssignNullToNotNullAttribute
Expression.Field(targetExp, typeof(HashSet<T>).GetField("m_slots", BindingFlags.NonPublic | BindingFlags.Instance)), indexVar),
"value")),
Expression.Constant(true));
var falsePart = Expression.Constant(false);
var block = Expression.Block(
new[] { indexVar },
Expression.Assign(indexVar, indexExp),
Expression.Condition(
Expression.GreaterThanOrEqual(indexVar, Expression.Constant(0)),
truePart,
falsePart));
TryGetValue = Expression.Lambda<GetValue>(block, targetExp, itemExp, actualValueExp).Compile();
}
}
public static class Extensions
{
public static bool TryGetValue2<T>(this HashSet<T> source, T equalValue, out T actualValue) {
if (source.Count > 0) {
if (HashSetExtensions<T>.TryGetValue(source, equalValue, out actualValue)) {
return true;
}
}
actualValue = default;
return false;
}
}
Test:
var x = new HashSet<int> { 1, 2, 3 };
if (x.TryGetValue2(1, out var value)) {
Console.WriteLine(value);
}
SortedSet would probably have O(log n) lookup time in that circumstance, if using that is an option. Still not O(1), but at least better.
What about overloading the string equality comparer:
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;
}
}
}
And then declare the HashSet as:
HashSet<string> hs = new HashSet<string>(new StringEqualityComparer());
Ok, so, you can do it like this
YourObject x = yourHashSet.Where(w => w.Name.Contains("strin")).FirstOrDefault();
This is to get a new Instance of the selected object. In order to update your object, then you should use:
yourHashSet.Where(w => w.Name.Contains("strin")).FirstOrDefault().MyProperty = "something";
Now .NET Core 2.0 has this exact method.
HashSet.TryGetValue(T, T) Method