web-dev-qa-db-fra.com

Passer une expression lambda à la place de IComparer ou IEqualityComparer ou de toute interface à méthode unique?

Il se trouve que j'ai vu du code où ce type a passé une expression lambda à un ArrayList.Sort (IComparer ici) ou un IEnumerable.SequenceEqual (liste IEnumerable, IEqualityComparer ici) où un IComparer ou un IEqualityComparer était attendu.

Je ne peux pas être sûr si je l'ai vu ou si je rêve. Et je n'arrive pas à trouver une extension sur aucune de ces collections qui accepte un Func <> ou un délégué dans leurs signatures de méthode.

Existe-t-il une telle méthode de surcharge/extension? Ou, sinon, est-il possible de contourner comme ça et de passer un algorithme (lire délégué) où une interface à méthode unique est attendue?

Mise à jour Merci à tous. C'est ce que je pensais. Je devais rêver. Je sais comment écrire une conversion. Je ne savais simplement pas si j'avais vu quelque chose comme ça ou si je pensais juste l'avoir vu.

Encore une autre mise à jour Regardez, ici, j'ai trouvé une telle instance. Je ne rêvais pas après tout. Regardez ce que ce gars fait ici . Ce qui donne?

Et voici une autre mise à jour: D'accord, je comprends. Le gars utilise le Comparison<T> surcharge. Agréable. Sympa, mais totalement enclin à vous induire en erreur. Bien cependant. Merci.

55
Water Cooler v2

Je cherchais également sur le Web une solution, mais je n'en ai trouvé aucune satisfaisante. J'ai donc créé un EqualityComparerFactory générique:

using System;
using System.Collections.Generic;

/// <summary>
/// Utility class for creating <see cref="IEqualityComparer{T}"/> instances 
/// from Lambda expressions.
/// </summary>
public static class EqualityComparerFactory
{
    /// <summary>Creates the specified <see cref="IEqualityComparer{T}" />.</summary>
    /// <typeparam name="T">The type to compare.</typeparam>
    /// <param name="getHashCode">The get hash code delegate.</param>
    /// <param name="equals">The equals delegate.</param>
    /// <returns>An instance of <see cref="IEqualityComparer{T}" />.</returns>
    public static IEqualityComparer<T> Create<T>(
        Func<T, int> getHashCode,
        Func<T, T, bool> equals)
    {
        if (getHashCode == null)
        {
            throw new ArgumentNullException(nameof(getHashCode));
        }

        if (equals == null)
        {
            throw new ArgumentNullException(nameof(equals));
        }

        return new Comparer<T>(getHashCode, equals);
    }

    private class Comparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, int> _getHashCode;
        private readonly Func<T, T, bool> _equals;

        public Comparer(Func<T, int> getHashCode, Func<T, T, bool> equals)
        {
            _getHashCode = getHashCode;
            _equals = equals;
        }

        public bool Equals(T x, T y) => _equals(x, y);

        public int GetHashCode(T obj) => _getHashCode(obj);
    }
}

L'idée est que la méthode CreateComparer prend deux arguments: un délégué à GetHashCode (T) et un délégué à Equals (T, T)

Exemple:

class Person
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var list1 = new List<Person>(new[]{
            new Person { Id = 1, FirstName = "Walter", LastName = "White" },
            new Person { Id = 2, FirstName = "Jesse", LastName = "Pinkman" },
            new Person { Id = 3, FirstName = "Skyler", LastName = "White" },
            new Person { Id = 4, FirstName = "Hank", LastName = "Schrader" },
        });

        var list2 = new List<Person>(new[]{
            new Person { Id = 1, FirstName = "Walter", LastName = "White" },
            new Person { Id = 4, FirstName = "Hank", LastName = "Schrader" },
        });


        // We're comparing based on the Id property
        var comparer = EqualityComparerFactory.Create<Person>(
            a => a.Id.GetHashCode(),
            (a, b) => a.Id==b.Id);
        var intersection = list1.Intersect(list2, comparer).ToList();
    }
}
23
AcidJunkie

Je ne sais pas trop à quoi cela sert vraiment, car je pense que dans la plupart des cas dans la bibliothèque de base qui attend un IComparer, il y a une surcharge qui attend une comparaison ... mais juste pour mémoire:

dans .Net 4.5, ils ont ajouté une méthode pour obtenir un IComparer à partir d'une comparaison: Comparer.Create

afin que vous puissiez lui passer votre lambda et obtenir un IComparer.

22
Xose Lluis

Vous pouvez fournir un lambda pour une méthode Array.Sort, car elle nécessite une méthode qui accepte deux objets de type T et renvoie un entier. En tant que tel, vous pouvez fournir un lambda de la définition suivante (a, b) => a.CompareTo(b). Un exemple pour faire un tri décroissant d'un tableau entier:

int[] array = { 1, 8, 19, 4 };

// descending sort 
Array.Sort(array, (a, b) => -1 * a.CompareTo(b));
11
Anthony Pegram
public class Comparer2<T, TKey> : IComparer<T>, IEqualityComparer<T>
{
    private readonly Expression<Func<T, TKey>> _KeyExpr;
    private readonly Func<T, TKey> _CompiledFunc
    // Constructor
    public Comparer2(Expression<Func<T, TKey>> getKey)
    {
        _KeyExpr = getKey;
        _CompiledFunc = _KeyExpr.Compile();
    } 

    public int Compare(T obj1, T obj2)
    {
        return Comparer<TKey>.Default.Compare(_CompiledFunc(obj1), _CompiledFunc(obj2));
    }

    public bool Equals(T obj1, T obj2)
    { 
        return EqualityComparer<TKey>.Default.Equals(_CompiledFunc(obj1), _CompiledFunc(obj2));
    }

    public int GetHashCode(T obj)
    {
         return EqualityComparer<TKey>.Default.GetHashCode(_CompiledFunc(obj));
    }
}

l'utiliser comme ça

ArrayList.Sort(new Comparer2<Product, string>(p => p.Name));
6
STO

Vous ne pouvez pas le transmettre directement, mais vous pouvez le faire en définissant une classe LambdaComparer qui exclut un Func<T,T,int> puis l'utilise dans son CompareTo.

Ce n'est pas aussi concis mais vous pouvez le raccourcir grâce à des méthodes d'extension créatives sur Func.

5
Stephan

Je vote pour la théorie du rêve.

Vous ne pouvez pas passer une fonction où un objet est attendu: les dérivés de System.Delegate (qui sont les lambdas) n'implémentent pas ces interfaces.

Ce que vous avez probablement vu est une utilisation de la Converter<TInput, TOutput> délégué, qui peut être modélisé par un lambda. Array.ConvertAll utilise une instance de ce délégué.

3
codekaizen

Ces méthodes n'ont pas de surcharge qui acceptent un délégué au lieu d'une interface, mais:

  • Vous pouvez normalement retourner une clé de tri plus simple via le délégué que vous passez à Enumerable.OrderBy
  • De même, vous pouvez appeler Enumerable.Select avant d'appeler Enumerable.SequenceEqual
  • Il devrait être simple d'écrire un wrapper qui implémente IEqualityComparer<T> en terme de Func<T, T, bool>
  • F # vous permet d'implémenter ce type d'interface en termes de lambda :)
3
Tim Robinson

Si vous avez besoin de cette fonction pour l'utiliser avec lambda et éventuellement deux types d'éléments différents:

static class IEnumerableExtensions
{
    public static bool SequenceEqual<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, bool> comparer)
    {
        if (first == null)
            throw new NullReferenceException("first");

        if (second == null)
            throw new NullReferenceException("second");

        using (IEnumerator<T1> e1 = first.GetEnumerator())
        using (IEnumerator<T2> e2 = second.GetEnumerator())
        {
            while (e1.MoveNext())
            {
                if (!(e2.MoveNext() && comparer(e1.Current, e2.Current)))
                    return false;
            }

            if (e2.MoveNext())
                return false;
        }

        return true;
    }
}
1
ezolotko