关于.net:在IEqualityComparer中包装委托

关于.net:在IEqualityComparer中包装委托

Wrap a delegate in an IEqualityComparer

几个Linq.Enumerable函数采用IEqualityComparer< T >。 是否有一个方便的包装类适应delegate(T,T)=>bool来实现IEqualityComparer< T >? 编写一个很容易(如果你忽略了定义正确的哈希码的问题),但我想知道是否有开箱即用的解决方案。

具体来说,我想在Dictionary上进行集合操作,仅使用Keys来定义成员资格(同时根据不同的规则保留值)。


关于GetHashCode的重要性

其他人已经评论过任何自定义IEqualityComparer< T >实现应该包含GetHashCode方法的事实;但是没有人愿意在任何细节上解释原因。

这就是原因。您的问题特别提到了LINQ扩展方法;几乎所有这些都依赖于哈希码才能正常工作,因为它们在内部利用哈希表来提高效率。

Distinct为例。如果所有使用的是Equals方法,请考虑此扩展方法的含义。如果只有Equals,如何确定某个项目是否已按序列扫描?您枚举了您已查看的整个值集合并检查匹配项。这将导致Distinct使用最坏情况的O(N2)算法而不是O(N)算法!

幸运的是,事实并非如此。 Distinct不只是使用Equals;它也使用GetHashCode。实际上,如果没有提供正确的GetHashCodeIEqualityComparer< T >,它绝对无法正常工作。下面是一个说明这一点的人为例子。

说我有以下类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Value
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public Value(string name, int number)
    {
        Name = name;
        Number = number;
    }

    public override string ToString()
    {
        return string.Format("{0}: {1}", Name, Number);
    }
}

现在说我有一个List,我想找到所有具有不同名称的元素。这是使用自定义相等比较器的Distinct的完美用例。那么让我们使用Aku的答案中的Comparer< T >类:

1
var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);

现在,如果我们有一堆具有相同Name属性的Value元素,它们应该全部折叠成Distinct返回的一个值,对吧?让我们来看看...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var values = new List<Value>();

var random = new Random();
for (int i = 0; i < 10; ++i)
{
    values.Add("x", random.Next());
}

var distinct = values.Distinct(comparer);

foreach (Value x in distinct)
{
    Console.WriteLine(x);
}

输出:

1
2
3
4
5
6
7
8
9
10
x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

嗯,这没用,是吗?

那么GroupBy呢?我们试试看:

1
2
3
4
5
6
7
8
9
10
var grouped = values.GroupBy(x => x, comparer);

foreach (IGrouping<Value> g in grouped)
{
    Console.WriteLine("[KEY: '{0}']", g);
    foreach (Value x in g)
    {
        Console.WriteLine(x);
    }
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[KEY = 'x: 1346013431']
x: 1346013431
[KEY = 'x: 1388845717']
x: 1388845717
[KEY = 'x: 1576754134']
x: 1576754134
[KEY = 'x: 1104067189']
x: 1104067189
[KEY = 'x: 1144789201']
x: 1144789201
[KEY = 'x: 1862076501']
x: 1862076501
[KEY = 'x: 1573781440']
x: 1573781440
[KEY = 'x: 646797592']
x: 646797592
[KEY = 'x: 655632802']
x: 655632802
[KEY = 'x: 1206819377']
x: 1206819377

再说一遍:没有用。

如果你考虑一下,Distinct在内部使用HashSet< T >(或等效物),而GroupBy在内部使用类似Dictionary>的东西是有意义的。这可以解释为什么这些方法不起作用?我们试试这个:

1
2
3
4
5
6
var uniqueValues = new HashSet<Value>(values, comparer);

foreach (Value x in uniqueValues)
{
    Console.WriteLine(x);
}

输出:

1
2
3
4
5
6
7
8
9
10
x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

是的......开始有意义吗?

希望从这些示例中可以清楚地看出为什么在任何IEqualityComparer< T >实现中包含适当的GetHashCode非常重要。

原始答案

扩展orip的答案:

这里可以做一些改进。

  • 首先,我采用Func代替Func;这样可以防止在实际的keyExtractor本身中装入值类型键。
  • 其次,我实际上添加了where TKey : IEquatable约束;这将阻止Equals调用中的装箱(object.Equals采用object参数;您需要一个IEquatable实现来获取TKey参数而不装箱它)。显然,这可能会造成太严格的限制,因此您可以创建没有约束的基类和带有它的派生类。
  • 以下是生成的代码的外观:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class KeyEqualityComparer<T, TKey> : IEqualityComparer< T >
    {
        protected readonly Func<T, TKey> keyExtractor;

        public KeyEqualityComparer(Func<T, TKey> keyExtractor)
        {
            this.keyExtractor = keyExtractor;
        }

        public virtual bool Equals(T x, T y)
        {
            return this.keyExtractor(x).Equals(this.keyExtractor(y));
        }

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

    public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
        where TKey : IEquatable<TKey>
    {
        public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
            : base(keyExtractor)
        { }

        public override bool Equals(T x, T y)
        {
            // This will use the overload that accepts a TKey parameter
            // instead of an object parameter.
            return this.keyExtractor(x).Equals(this.keyExtractor(y));
        }
    }

    当您想要自定义相等性检查时,99%的时间您有兴趣定义要比较的键,而不是比较本身。

    这可能是一个优雅的解决方案(Python的列表排序方法的概念)。

    用法:

    1
    2
    3
    4
    var foo = new List<string> {"abc","de","DE" };

    // case-insensitive distinct
    var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );

    KeyEqualityComparer类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class KeyEqualityComparer< T > : IEqualityComparer< T >
    {
        private readonly Func<T, object> keyExtractor;

        public KeyEqualityComparer(Func<T,object> keyExtractor)
        {
            this.keyExtractor = keyExtractor;
        }

        public bool Equals(T x, T y)
        {
            return this.keyExtractor(x).Equals(this.keyExtractor(y));
        }

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


    我担心没有这样的包装盒开箱即用。然而,创建一个并不难:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class Comparer< T >: IEqualityComparer< T >
    {
        private readonly Func<T, T, bool> _comparer;

        public Comparer(Func<T, T, bool> comparer)
        {
            if (comparer == null)
                throw new ArgumentNullException("comparer");

            _comparer = comparer;
        }

        public bool Equals(T x, T y)
        {
            return _comparer(x, y);
        }

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

    ...

    Func<int, int, bool> f = (x, y) => x == y;
    var comparer = new Comparer<int>(f);
    Console.WriteLine(comparer.Equals(1, 1));
    Console.WriteLine(comparer.Equals(1, 2));

    通常情况下,我会通过在答案上评论@Sam来解决这个问题(我已经对原始帖子进行了一些编辑,以便在不改变行为的情况下对其进行清理。)

    以下是我对@Sam的答案的重复,对[IMNSHO]关键修复默认的散列策略: -

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class FuncEqualityComparer< T > : IEqualityComparer< T >
    {
        readonly Func<T, T, bool> _comparer;
        readonly Func<T, int> _hash;

        public FuncEqualityComparer( Func<T, T, bool> comparer )
            : this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
        {
        }

        public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
        {
            _comparer = comparer;
            _hash = hash;
        }

        public bool Equals( T x, T y )
        {
            return _comparer( x, y );
        }

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

    与丹涛的答案相同,但有一些改进:

  • 依靠EqualityComparer<>.Default进行实际比较,以避免对已实现IEquatable<>的值类型(struct)进行装箱。

  • 由于EqualityComparer<>.Default使用它不会在null.Equals(something)上爆炸。

  • 提供了围绕IEqualityComparer<>的静态包装器,它将有一个静态方法来创建比较器实例 - 简化调用。相比

    1
    Equality<Person>.CreateComparer(p => p.ID);

    1
    new EqualityComparer<Person, int>(p => p.ID);
  • 添加了重载以指定密钥的IEqualityComparer<>

  • 班级:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public static class Equality< T >
    {
        public static IEqualityComparer< T > CreateComparer<V>(Func<T, V> keySelector)
        {
            return CreateComparer(keySelector, null);
        }

        public static IEqualityComparer< T > CreateComparer<V>(Func<T, V> keySelector,
                                                             IEqualityComparer<V> comparer)
        {
            return new KeyEqualityComparer<V>(keySelector, comparer);
        }

        class KeyEqualityComparer<V> : IEqualityComparer< T >
        {
            readonly Func<T, V> keySelector;
            readonly IEqualityComparer<V> comparer;

            public KeyEqualityComparer(Func<T, V> keySelector,
                                       IEqualityComparer<V> comparer)
            {
                if (keySelector == null)
                    throw new ArgumentNullException("keySelector");

                this.keySelector = keySelector;
                this.comparer = comparer ?? EqualityComparer<V>.Default;
            }

            public bool Equals(T x, T y)
            {
                return comparer.Equals(keySelector(x), keySelector(y));
            }

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

    你可以像这样使用它:

    1
    2
    3
    4
    var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
    var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
    var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
    var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);

    人是一个简单的类:

    1
    2
    3
    4
    5
    6
    class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public DateTime Birthday { get; set; }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class FuncEqualityComparer< T > : IEqualityComparer< T >
    {
        readonly Func<T, T, bool> _comparer;
        readonly Func<T, int> _hash;

        public FuncEqualityComparer( Func<T, T, bool> comparer )
            : this( comparer, t => t.GetHashCode())
        {
        }

        public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
        {
            _comparer = comparer;
            _hash = hash;
        }

        public bool Equals( T x, T y )
        {
            return _comparer( x, y );
        }

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

    随着扩展: -

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static class SequenceExtensions
    {
        public static bool SequenceEqual< T >( this IEnumerable< T > first, IEnumerable< T > second, Func<T, T, bool> comparer )
        {
            return first.SequenceEqual( second, new FuncEqualityComparer< T >( comparer ) );
        }

        public static bool SequenceEqual< T >( this IEnumerable< T > first, IEnumerable< T > second, Func<T, T, bool> comparer, Func<T, int> hash )
        {
            return first.SequenceEqual( second, new FuncEqualityComparer< T >( comparer, hash ) );
        }
    }


    orip的答案很棒。

    这里有一个小扩展方法,使它更容易:

    1
    2
    3
    4
    5
    public static IEnumerable< T > Distinct< T >(this IEnumerable< T > list, Func<T, object>    keyExtractor)
    {
        return list.Distinct(new KeyEqualityComparer< T >(keyExtractor));
    }
    var distinct = foo.Distinct(x => x.ToLower())

    (德语文本)的实现使用lambda表达式实现IEqualityCompare
    关心空值并使用扩展方法生成IEqualityComparer。

    要在Linq联合中创建IEqualityComparer,您只需编写即可

    1
    persons1.Union(persons2, person => person.LastName)

    比较器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class LambdaEqualityComparer<TSource, TComparable> : IEqualityComparer<TSource>
    {
      Func<TSource, TComparable> _keyGetter;

      public LambdaEqualityComparer(Func<TSource, TComparable> keyGetter)
      {
        _keyGetter = keyGetter;
      }

      public bool Equals(TSource x, TSource y)
      {
        if (x == null || y == null) return (x == null && y == null);
        return object.Equals(_keyGetter(x), _keyGetter(y));
      }

      public int GetHashCode(TSource obj)
      {
        if (obj == null) return int.MinValue;
        var k = _keyGetter(obj);
        if (k == null) return int.MaxValue;
        return k.GetHashCode();
      }
    }

    您还需要添加扩展方法以支持类型推断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static class LambdaEqualityComparer
    {
           // source1.Union(source2, lambda)
            public static IEnumerable<TSource> Union<TSource, TComparable>(
               this IEnumerable<TSource> source1,
               IEnumerable<TSource> source2,
                Func<TSource, TComparable> keySelector)
            {
                return source1.Union(source2,
                   new LambdaEqualityComparer<TSource, TComparable>(keySelector));
           }
       }

    我要回答我自己的问题。要将字典视为集合,最简单的方法似乎是将set操作应用于dict.Keys,然后使用Enumerable.ToDictionary(...)转换回Dictionaries。


    orip的答案很棒。扩展orip的答案:

    我认为解决方案的关键是使用"扩展方法"来传输"匿名类型"。

    1
    2
    3
    4
    5
    6
    7
        public static class Comparer
        {
          public static IEqualityComparer< T > CreateComparerForElements< T >(this IEnumerable< T > enumerable, Func<T, object> keyExtractor)
          {
            return new KeyEqualityComparer< T >(keyExtractor);
          }
        }

    用法:

    1
    2
    3
    var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
    n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););
    n = n.Distinct(x=>new{Vchr=x.Vchr,Id=x.Id}).ToList();

    只需一个优化:
    我们可以使用开箱即用的EqualityComparer进行价值比较,而不是委托它。

    这也可以使实现更清晰,因为实际的比较逻辑现在保留在GetHashCode()和Equals()中,您可能已经过载了。

    这是代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MyComparer< T > : IEqualityComparer< T >
    {
      public bool Equals(T x, T y)
      {
        return EqualityComparer< T >.Default.Equals(x, y);
      }

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

    不要忘记在对象上重载GetHashCode()和Equals()方法。

    这篇文章帮助我:c#比较两个通用值

    苏希尔


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static Dictionary<TKey, TValue> Distinct<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> selector)
      {
         Dictionary<TKey, TValue> result = null;
         ICollection collection = items as ICollection;
         if (collection != null)
            result = new Dictionary<TKey, TValue>(collection.Count);
         else
            result = new Dictionary<TKey, TValue>();
         foreach (TValue item in items)
            result[selector(item)] = item;
         return result;
      }

    这使得可以选择具有lambda的属性,如下所示:.Select(y => y.Article).Distinct(x => x.ArticleID);


    我不知道现有的课程,但有点像:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class MyComparer< T > : IEqualityComparer< T >
    {
      private Func<T, T, bool> _compare;
      MyComparer(Func<T, T, bool> compare)
      {
        _compare = compare;
      }

      public bool Equals(T x, Ty)
      {
        return _compare(x, y);
      }

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

    注意:我还没有实际编译和运行它,因此可能存在拼写错误或其他错误。


    推荐阅读

      excel怎么用乘法函数

      excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、首先用鼠标选中要计算的单元格。2、然后选中单元格后点击左上方工具栏的fx公

      excel中乘法函数是什么?

      excel中乘法函数是什么?,乘法,函数,什么,打开表格,在C1单元格中输入“=A1*B1”乘法公式。以此类推到多个单元。1、A1*B1=C1的Excel乘法公式

      标准差excel用什么函数?

      标准差excel用什么函数?,函数,标准,什么,在数据单元格的下方输入l标准差公式函数公式“=STDEVPA(C2:C6)”。按下回车,求出标准公差值。详细

      大家给几个i5装机方案

      大家给几个i5装机方案,装机,方案,大家,i5 760——2000MSI H57——500-1500推荐:HD4870 x2(不是两块,是两个显示核心,是一个型号)-1200-1400

      excel常用函数都有哪些?

      excel常用函数都有哪些?,函数,哪些,常用,1、SUM函数:SUM函数的作用是求和。函数公式为=sum()例如:统计一个单元格区域:=sum(A1:A10)  统计多个