Can you create a simple 'EqualityComparer' using a lambda expression

前端 未结 8 1478
半阙折子戏
半阙折子戏 2020-12-13 18:20

Short question:

Is there a simple way in LINQ to objects to get a distinct list of objects from a list based on a key property on the objects.

8条回答
  •  半阙折子戏
    2020-12-13 18:41

    This idea is being debated here, and while I'm hoping the .NET Core team adopt a method to generate IEqualityComparers from lambda, I'd suggest you to please vote and comment on that idea, and use the following:

    Usage:

    IEqualityComparer comp1 = EqualityComparerImpl.Create(c => c.Name);
    var comp2 = EqualityComparerImpl.Create(c => c.Name, c => c.Age);
    
    class Contact { public Name { get; set; } public Age { get; set; } }
    

    Code:

    public class EqualityComparerImpl : IEqualityComparer
    {
      public static EqualityComparerImpl Create(
        params Expression>[] properties) =>
        new EqualityComparerImpl(properties);
    
      PropertyInfo[] _properties;
      EqualityComparerImpl(Expression>[] properties)
      {
        if (properties == null)
          throw new ArgumentNullException(nameof(properties));
    
        if (properties.Length == 0)
          throw new ArgumentOutOfRangeException(nameof(properties));
    
        var length = properties.Length;
        var extractions = new PropertyInfo[length];
        for (int i = 0; i < length; i++)
        {
          var property = properties[i];
          extractions[i] = ExtractProperty(property);
        }
        _properties = extractions;
      }
    
      public bool Equals(T x, T y)
      {
        if (ReferenceEquals(x, y))
          //covers both are null
          return true;
        if (x == null || y == null)
          return false;
        var len = _properties.Length;
        for (int i = 0; i < _properties.Length; i++)
        {
          var property = _properties[i];
          if (!Equals(property.GetValue(x), property.GetValue(y)))
            return false;
        }
        return true;
      }
    
      public int GetHashCode(T obj)
      {
        if (obj == null)
          return 0;
    
        var hashes = _properties
            .Select(pi => pi.GetValue(obj)?.GetHashCode() ?? 0).ToArray();
        return Combine(hashes);
      }
    
      static int Combine(int[] hashes)
      {
        int result = 0;
        foreach (var hash in hashes)
        {
          uint rol5 = ((uint)result << 5) | ((uint)result >> 27);
          result = ((int)rol5 + result) ^ hash;
        }
        return result;
      }
    
      static PropertyInfo ExtractProperty(Expression> property)
      {
        if (property.NodeType != ExpressionType.Lambda)
          throwEx();
    
        var body = property.Body;
        if (body.NodeType == ExpressionType.Convert)
          if (body is UnaryExpression unary)
            body = unary.Operand;
          else
            throwEx();
    
        if (!(body is MemberExpression member))
          throwEx();
    
        if (!(member.Member is PropertyInfo pi))
          throwEx();
    
        return pi;
    
        void throwEx() =>
          throw new NotSupportedException($"The expression '{property}' isn't supported.");
      }
    }
    

提交回复
热议问题