C# 我可以内联指定我的显式类型比较器吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/188120/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 17:15:42  来源:igfitidea点击:

Can I specify my explicit type comparator inline?

c#.netlinqlambda

提问by Coderer

So .NET 3.0/3.5 provides us with lots of new ways to query, sort, and manipulate data, thanks to all the neat functions supplied with LINQ. Sometimes, I need to compare user-defined types that don't have a built-in comparison operator. In many cases, the comparison is really simple -- something like foo1.key ?= foo2.key. Rather than creating a new IEqualityComparer for the type, can I simply specify the comparison inline using anonymous delegates/lambda functions? Something like:

因此,.NET 3.0/3.5 为我们提供了许多查询、排序和操作数据的新方法,这要归功于 LINQ 提供的所有简洁功能。有时,我需要比较没有内置比较运算符的用户定义类型。在许多情况下,比较非常简单——类似于 foo1.key ?= foo2.key。我可以使用匿名委托/lambda 函数简单地指定内联比较,而不是为该类型创建一个新的 IEqualityComparer 吗?就像是:

var f1 = ...,
    f2 = ...;
var f3 = f1.Except(
           f2, new IEqualityComparer(
             (Foo a, Foo b) => a.key.CompareTo(b.key)
           ) );

I'm pretty sure the above doesn't actually work. I just don't want to have to make something as "heavy" as a whole class just to tell the program how to compare apples to apples.

我很确定上述内容实际上不起作用。我只是不想为了告诉程序如何将苹果与苹果进行比较而把整个班级的东西都做得“重”。

采纳答案by Jon Skeet

My MiscUtillibrary contains a ProjectionComparer to build an IComparer<T> from a projection delegate. It would be the work of 10 minutes to make a ProjectionEqualityComparer to do the same thing.

我的MiscUtil库包含一个 ProjectionComparer,用于从投影委托构建 IComparer<T>。让 ProjectionEqualityComparer 做同样的事情需要 10 分钟。

EDIT: Here's the code for ProjectionEqualityComparer:

编辑:这是 ProjectionEqualityComparer 的代码:

using System;
using System.Collections.Generic;

/// <summary>
/// Non-generic class to produce instances of the generic class,
/// optionally using type inference.
/// </summary>
public static class ProjectionEqualityComparer
{
    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// </summary>
    /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting 
    /// each element to its key, and comparing keys</returns>
    public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }

    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// The ignored parameter is solely present to aid type inference.
    /// </summary>
    /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="ignored">Value is ignored - type may be used by type inference</param>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting
    /// each element to its key, and comparing keys</returns>
    public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>
        (TSource ignored,
         Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }

}

/// <summary>
/// Class generic in the source only to produce instances of the 
/// doubly generic class, optionally using type inference.
/// </summary>
public static class ProjectionEqualityComparer<TSource>
{
    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// </summary>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting each element to its key,
    /// and comparing keys</returns>        
    public static ProjectionEqualityComparer<TSource, TKey> Create<TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }
}

/// <summary>
/// Comparer which projects each element of the comparison to a key, and then compares
/// those keys using the specified (or default) comparer for the key type.
/// </summary>
/// <typeparam name="TSource">Type of elements which this comparer 
/// will be asked to compare</typeparam>
/// <typeparam name="TKey">Type of the key projected
/// from the element</typeparam>
public class ProjectionEqualityComparer<TSource, TKey> : IEqualityComparer<TSource>
{
    readonly Func<TSource, TKey> projection;
    readonly IEqualityComparer<TKey> comparer;

    /// <summary>
    /// Creates a new instance using the specified projection, which must not be null.
    /// The default comparer for the projected type is used.
    /// </summary>
    /// <param name="projection">Projection to use during comparisons</param>
    public ProjectionEqualityComparer(Func<TSource, TKey> projection)
        : this(projection, null)
    {
    }

    /// <summary>
    /// Creates a new instance using the specified projection, which must not be null.
    /// </summary>
    /// <param name="projection">Projection to use during comparisons</param>
    /// <param name="comparer">The comparer to use on the keys. May be null, in
    /// which case the default comparer will be used.</param>
    public ProjectionEqualityComparer(Func<TSource, TKey> projection, IEqualityComparer<TKey> comparer)
    {
        if (projection == null)
        {
            throw new ArgumentNullException("projection");
        }
        this.comparer = comparer ?? EqualityComparer<TKey>.Default;
        this.projection = projection;
    }

    /// <summary>
    /// Compares the two specified values for equality by applying the projection
    /// to each value and then using the equality comparer on the resulting keys. Null
    /// references are never passed to the projection.
    /// </summary>
    public bool Equals(TSource x, TSource y)
    {
        if (x == null && y == null)
        {
            return true;
        }
        if (x == null || y == null)
        {
            return false;
        }
        return comparer.Equals(projection(x), projection(y));
    }

    /// <summary>
    /// Produces a hash code for the given value by projecting it and
    /// then asking the equality comparer to find the hash code of
    /// the resulting key.
    /// </summary>
    public int GetHashCode(TSource obj)
    {
        if (obj == null)
        {
            throw new ArgumentNullException("obj");
        }
        return comparer.GetHashCode(projection(obj));
    }
}

And here's a sample use:

这是一个示例用法:

var f3 = f1.Except(f2, ProjectionEqualityComparer<Foo>.Create(a => a.key));

回答by Sam Saffron

I find providing extra helpers on IEnumerable is a cleaner way to do this.

我发现在 IEnumerable 上提供额外的助手是一种更简洁的方法。

See: this question

见:这个问题

So you could have:

所以你可以有:

var f3 = f1.Except(
           f2, 
             (a, b) => a.key.CompareTo(b.key)
            );

If you define the extension methods properly

如果您正确定义了扩展方法

回答by mike

here is a simple helper class that should do what you want

这是一个简单的助手类,应该可以做你想做的

public class EqualityComparer<T> : IEqualityComparer<T>
{
    public EqualityComparer(Func<T, T, bool> cmp)
    {
        this.cmp = cmp;
    }
    public bool Equals(T x, T y)
    {
        return cmp(x, y);
    }

    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }

    public Func<T, T, bool> cmp { get; set; }
}

you can use it like this:

你可以这样使用它:

processed.Union(suburbs, new EqualityComparer<Suburb>((s1, s2)
    => s1.SuburbId == s2.SuburbId));

回答by Jeremy Thomas

This project does something similar: AnonymousComparer - lambda compare selector for Linq, it has Extensions for LINQ Standard Query Operators as well.

这个项目做了类似的事情:AnonymousComparer - Linq 的 lambda 比较选择器,它也有 LINQ 标准查询运算符的扩展。

回答by Tamas Ionut

Why not something like:

为什么不是这样的:

    public class Comparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, T, bool> _equalityComparer;

        public Comparer(Func<T, T, bool> equalityComparer)
        {
            _equalityComparer = equalityComparer;
        }

        public bool Equals(T first, T second)
        {
            return _equalityComparer(first, second);
        }

        public int GetHashCode(T value)
        {
            return value.GetHashCode();
        }
    }

and then you could do for instance something like (e.g. in the case of Intersectin IEnumerable<T>):

然后你可以例如像(例如,在的情况下做IntersectIEnumerable<T>):

list.Intersect(otherList, new Comparer<T>( (x, y) => x.Property == y.Property));

The Comparerclass can be put in a utilities project and used wherever is needed.

Comparer类可以放在一个公共事业项目,被需要的地方使用。

I only now see the Sam Saffron's answer (which is very similar to this one).

我现在才看到 Sam Saffron 的回答(与这个非常相似)。

回答by mheyman

For small sets, you can do:

对于小集合,您可以执行以下操作:

f3 = f1.Where(x1 => f2.All(x2 => x2.key != x1.key));

For large sets, you will want something more efficient in the search like:

对于大型集合,您将需要更高效的搜索,例如:

var tmp = new HashSet<string>(f2.Select(f => f.key));
f3 = f1.Where(f => tmp.Add(f.key));

But, here, the Typeof keymust implement IEqualityComparer(above I assumed it was a string). So, this doesn't really answer your question about using a lambda in this situation but it does use less code then some of the answers that do.

但是,在这里,Typeof必须实现IEqualityComparer(上面我假设它是 a string)。因此,这并不能真正回答您在这种情况下使用 lambda 的问题,但它确实使用了比某些答案少的代码。

You might rely on the optimizer and shorten the second solution to:

您可能会依赖优化器并将第二个解决方案缩短为:

f3 = f1.Where(x1 => (new HashSet<string>(f2.Select(x2 => x2.key))).Add(x1.key));

but, I haven't run tests to know if it runs at the same speed. And that one liner might be too clever to maintain.

但是,我还没有运行测试来知道它是否以相同的速度运行。那个班轮可能太聪明而无法维护。

回答by kofifus

Like the other answers but more concise c# 7:

像其他答案一样,但更简洁的 c# 7:

public class LambdaComparer<T> : IEqualityComparer<T> {
  private readonly Func<T, T, bool> lambdaComparer;
  private readonly Func<T, int> lambdaHash;
  public LambdaComparer(Func<T, T, bool> lambdaComparer) : this(lambdaComparer, o => o.GetHashCode()) {}
  public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash) { this.lambdaComparer = lambdaComparer; this.lambdaHash = lambdaHash; }
  public bool Equals(T x, T y) => lambdaComparer is null ? false : lambdaComparer(x, y);
  public int GetHashCode(T obj) => lambdaHash is null ? 0 : lambdaHash(obj);
}

then:

然后:

var a=List<string> { "a", "b" };
var b=List<string> { "a", "*" };
return a.SequenceEquals(b, new LambdaComparer<string>((s1, s2) => s1 is null ? s2 is null : s1 == s2 || s2 == "*");  

回答by OriolBG

Building on other answers the creation of a generic comparer was the one I liked most. But I got a problem with Linq Enumerable.Union(msdn .Net reference) which was that its using the GetHashCode directly without taking into account the Equals override.

在其他答案的基础上,创建通用比较器是我最喜欢的。但是我对 Linq Enumerable.Unionmsdn .Net 参考)有一个问题,那就是它直接使用 GetHashCode 而不考虑 Equals 覆盖。

That took me to implement the Comparer as:

这让我将比较器实现为:

public class Comparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, int> _hashFunction;

    public Comparer(Func<T, int> hashFunction)
    {
        _hashFunction = hashFunction;
    }

    public bool Equals(T first, T second)
    {
        return _hashFunction(first) == _hashFunction(second);
    }

    public int GetHashCode(T value)
    {
        return _hashFunction(value);
    }
}

Using it like this:

像这样使用它:

list.Union(otherList, new Comparer<T>( x => x.StringValue.GetHashCode()));

Note that comparison might give some false positive since information being compared is mapped to an intvalue.

请注意,比较可能会产生一些误报,因为被比较的信息被映射到一个int值。

回答by WhiteleyJ

So I know this is a workaround to your question, but when I find that I've run into the situation you have here (Combining a list and filtering duplicates), and Distinct needs an IEquityComparer that I don't have, I usually go with a Concat -> Group -> Select.

所以我知道这是您的问题的一种解决方法,但是当我发现我遇到了您在此处遇到的情况(组合列表并过滤重复项)并且 Distinct 需要一个我没有的 IEquityComparer 时,我通常会去使用 Concat -> 组 -> 选择。

Original

原来的

var f1 = ...,
    f2 = ...;
var f3 = f1.Except(
           f2, new IEqualityComparer(
             (Foo a, Foo b) => a.key.CompareTo(b.key)
           ) );

New

新的

var f1 = ...,
    f2 = ...;
var distinctF = f1
    .Concat(f2)                       // Combine the lists
    .GroupBy(x => x.key)              // Group them up by our equity comparison key
    .Select(x => x.FirstOrDefault()); // Just grab one of them.

Note that in the GroupBy() you have the opportunity to add logic to create hybrid keys like:

请注意,在 GroupBy() 中,您有机会添加逻辑来创建混合键,例如:

.GroupBy(f => new Uri(f.Url).PathAndQuery)  

As well as in the Select() if you want to want to specify which list the resulting item comes from you can say:

以及在 Select() 中,如果您想指定结果项目来自哪个列表,您可以说:

.Select(x => x.FirstOrDefault(y => f1.Contains(y))

Hope that helps!

希望有帮助!