Home > Net >  Calling a generic function with a contravariant IEqualityComparer<T>
Calling a generic function with a contravariant IEqualityComparer<T>

Time:05-12

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.

source

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.

  • Related