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 IEqualityComparer
s 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.