I am trying to combine a couple of packages. UnitsNet containing all specific structs, and Microsoft Mvvm that hasa some nice Set<T> functions for properties with INotifyPropertyChanged. One of those Set<T> functions asks for an IEqualityComparer<T>.
I know that my QuantityEqualityComparer is the same for every struct from UnitsNet. And I saw that IEqualityComparer<in T> is contravariant. So I thought I understood that the following example should be possible. But the equality comparer is not accepted in this generic method.
It doesn't seem to be possible to cast _bar to IQuantity, and I also tried to call Set<IQuantity>(). But both statements get rejected by the compiler.
The solution I have implemented now is a caching mechanism that will create specific IEqualityComparers with reflection, but it seems like an overkill.
You can find the example here to play with it: https://dotnetfiddle.net/N7vfc9
using System;
using System.Collections.Generic;
public class Program
{
private static Length _bar;
public static void Main()
{
// Error: cannot convert from QuantityEqualityComparer to IEqualityComparer<Length>
Set(ref _bar, new Length(), new QuantityEqualityComparer());
}
// from Mvvm
public static bool Set<T>(ref T field, T value, IEqualityComparer<T> comparer)
{
return true;
}
}
public class QuantityEqualityComparer : IEqualityComparer<IQuantity>
{
public bool Equals(IQuantity x, IQuantity y)
{
// custom implementation
}
public int GetHashCode(IQuantity obj)
{
// custom implementation
}
}
// from UnitsNet
public interface IQuantity
{
}
public struct Length : IQuantity, IEquatable<Length>
{
public bool Equals(Length other)
{
return true;
}
}
CodePudding user response:
Calling your QuantityEqualityComparer.Equals method would require the length struct to be boxed, which is why contravariance conversion fails.
Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.
Though this might be similar to what you already have, this is what I would start with;
public static bool Set<T>(ref T field, T value)
where T:IQuantity
=> Set<T>(ref field, value, QuantityEqualityComparer<T>.Instance);
public class QuantityEqualityComparer<T> : IEqualityComparer<T>
where T:IQuantity
{
public static QuantityEqualityComparer<T> Instance = new();
//...
}
Then use reflection or .Compile an expression tree to call this .Set<T> method.
