it-swarm.com.de

Übergeben Sie einen Lambda-Ausdruck anstelle von IComparer oder IEqualityComparer oder einer Einzelmethodenschnittstelle?

Ich habe zufällig einen Code gesehen, in dem dieser Typ einen Lambda-Ausdruck an eine ArrayList.Sort (IComparer hier) oder eine IEnumerable.SequenceEqual (IEnumerable-Liste, IEqualityComparer hier) übergeben hat, in der ein IComparer oder ein IEqualityComparer erwartet wurde.

Ich kann mir nicht sicher sein, ob ich es gesehen habe oder ob ich nur träume. Und ich kann anscheinend keine Erweiterung für eine dieser Sammlungen finden, die einen Func <> oder einen Delegaten in ihren Methodensignaturen akzeptiert.

Gibt es eine solche Überlast-/Erweiterungsmethode? Oder, wenn nicht, ist es möglich, so herumzuspielen und einen Algorithmus (Lesedelegierten) zu übergeben, bei dem eine Schnittstelle mit nur einer Methode erwartet wird?

Update Vielen Dank an alle. Das ist was ich dachte. Ich muss geträumt haben. Ich weiß, wie man eine Konvertierung schreibt. Ich war mir nur nicht sicher, ob ich so etwas gesehen hatte oder dachte, ich hätte es gesehen.

Noch ein Update Schau mal, hier habe ich eine solche Instanz gefunden. Ich habe doch nicht geträumt. Schau mal was dieser Typ hier macht . Was gibt?

Und hier ist ein weiteres Update: Ok, ich verstehe. Der Typ benutzt das Comparison<T> Überlastung. Nett. Nett, aber total anfällig für Irreführung. Guter Gedanke. Vielen Dank.

55
Water Cooler v2

Ich googelte auch im Internet nach einer Lösung, fand aber keine befriedigende. Also habe ich eine generische EqualityComparerFactory erstellt:

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);
    }
}

Die Idee ist, dass die CreateComparer-Methode zwei Argumente akzeptiert: einen Delegaten an GetHashCode (T) und einen Delegaten an Equals (T, T).

Beispiel:

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

Ich bin mir nicht sicher, was wirklich nützlich ist, da ich in den meisten Fällen in der Basisbibliothek davon ausgehe, dass ein IComparer überladen ist und einen Vergleich erwartet ... aber nur für den Datensatz:

in .NET 4.5 haben sie eine Methode hinzugefügt, um einen IComparer aus einem Vergleich zu erhalten: Comparer.Create

so können Sie Ihr Lambda an ihn weitergeben und einen IComparer erhalten.

22
Xose Lluis

Sie können ein Lambda für eine Array.Sort-Methode bereitstellen, da eine Methode erforderlich ist, die zwei Objekte vom Typ T akzeptiert und eine Ganzzahl zurückgibt. Als solches könnten Sie ein Lambda der folgenden Definition angeben (a, b) => a.CompareTo(b). Ein Beispiel für eine absteigende Sortierung eines Integer-Arrays:

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));
    }
}

benutze es so

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

Sie können es nicht direkt übergeben, aber Sie können dies tun, indem Sie eine LambdaComparer -Klasse definieren, die einen Func<T,T,int> Ausschließt und diesen dann in ihrem CompareTo verwendet.

Es ist nicht ganz so prägnant, aber Sie können es durch einige kreative Erweiterungsmethoden für Func verkürzen.

5
Stephan

Ich stimme für die Traumtheorie.

Sie können keine Funktion übergeben, bei der ein Objekt erwartet wird: Ableitungen von System.Delegate (was Lambdas sind) implementieren diese Schnittstellen nicht.

Was Sie wahrscheinlich gesehen haben, ist eine Verwendung der Converter<TInput, TOutput> Delegat, der durch ein Lambda modelliert werden kann. Array.ConvertAll verwendet eine Instanz dieses Delegaten.

3
codekaizen

Diese Methoden haben keine Überladungen, die einen Delegaten anstelle einer Schnittstelle akzeptieren, sondern:

  • Normalerweise können Sie einen einfacheren Sortierschlüssel über den Delegaten zurückgeben, den Sie an Enumerable.OrderBy Übergeben haben.
  • Ebenso können Sie Enumerable.Select Aufrufen, bevor Sie Enumerable.SequenceEqual Aufrufen.
  • Es sollte einfach sein, einen Wrapper zu schreiben, der IEqualityComparer<T> In Form von Func<T, T, bool> Implementiert.
  • Mit F # können Sie diese Art von Schnittstelle in Form eines Lambda implementieren :)
3
Tim Robinson

Falls Sie diese Funktion für die Verwendung mit Lambda und möglicherweise zwei verschiedenen Elementtypen benötigen:

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