I need dictionary for which key needs to be two guid order of guid doesn't matter. I have two solutions which are mentioned below, I want to know which one is better and which one should I go if in future I need to add 3rd guid as well as part of key. Performance do matter but if difference is not much then should be fine.
First Solution
var guids = guids
.OrderBy(g => g.ToString("N")) // Ordering so key always come as sorted
.ToList();
dict[Tuple.Create(guids[0], guids[1]) = "RandomObj";
Second Solution
public class GuidsModel
{
public GuidsModel(Guid guid1, Guid guid2)
{
Guid1= guid1;
Guid2= guid2;
}
public Guid Guid1 { get; private set; }
public Guid Guid2 { get; private set; }
}
public class GuidsModelComparer : IEqualityComparer<GuidsModel>
{
public static GuidsModelComparer Default = new GuidsModelComparer();
private static IEqualityComparer<HashSet<Guid>> _hashSetComparer = HashSet<Guid>.CreateSetComparer();
public bool Equals(GuidsModel x, GuidsModel y)
{
return _hashSetComparer.Equals(new HashSet<Guid> { x.Guid1, x.Guid2}, new HashSet<Guid> { y.Guid1, y.Guid2});
}
public int GetHashCode(GuidsModel obj)
{
if (obj == null)
return 0;
return _hashSetComparer.GetHashCode(new HashSet<Guid> { obj.Guid1, obj.Guid2});
}
// usage
var dict = new Dictionary<GuidsModel, object>(GuidsModelComparer.Default);
dict[new GuidsModel(guids[0], guids[1])] = "RandomObj";
It can heavily depend on the context, but I would say that if you want to access the dictionary in more than one place encapsulating the guid "sorting" logic in one place would make more sense. Also personally I would just implement Equals and GetHashcode in the model itself instead of creating custom comparer.
public class GuidsModel
{
public GuidsModel(Guid guid1, Guid guid2)
{
if (guid1.CompareTo(guid2) > 0) // when there will be more than 2 guids can switch to ordering collection i.e. new [] {guid1, guid2, guid3}.OrderBy()...
{
Guid1 = guid1;
Guid2 = guid2;
}
else
{
Guid1 = guid2;
Guid2 = guid1;
}
}
public Guid Guid1 { get; private set; }
public Guid Guid2 { get; private set; }
private bool Equals(GuidsModel other)
{
return Guid1.Equals(other.Guid1) && Guid2.Equals(other.Guid2);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((GuidsModel)obj);
}
public override int GetHashCode()
{
return HashCode.Combine(Guid1, Guid2);
}
}
As far as I understood you need a HashSet<Something> and this something holds two values of same type and the HashSet should also match, if the order is different.
Getting this ignore order is quite complicated. But if you use a factory method to create the instances and this factory method takes care of the ordering, then it gets much simpler:
// Some class that holds two Guids, but it respects ordering
public record GuidPair(Guid First, Guid Second);
public static class Create
{
public static GuidPair GuidPair(Guid first, Guid second)
{
// Order guids somehow in factory method to ensure
// correct equality comparison
if (first.CompareTo(second) > 0)
return new GuidPair(second, first);
else
return new GuidPair(first, second);
}
}
public static class Program
{
public static void Main(string[] args)
{
// Some source for Guid values.
var values = Enumerable.Range(0, 100)
.Select(_ => Guid.NewGuid());
// Create a HashSet with arbitrary values.
var hashes = values.Zip(values)
.Select(pair => Create.GuidPair(pair.First, pair.Second))
.ToHashSet();
// Pick some "random" element
var someEntry = hashes.Skip(17).First();
// Create explicitly a new item with values swapped (with factory method).
var toCompare = Create.GuidPair(someEntry.Second, someEntry.First);
// Check if default HashSet comparer works.
var isAvailable = hashes.Contains(toCompare);
// Prints "Found { first = Guid, second = Guid }: true"
Console.WriteLine($"Found {toCompare}: {isAvailable}");
Console.ReadKey();
}
}