2008-11-12 5 views
35

이 방법 서명을 감안할 때 연산 식? 이것은 단순한 표현식에서만 작동 할 필요가 있습니다. 즉, "지원되는"모든 것이 단순한 MemberExpressions 일 것입니다. 예를 들어 c => c.ID입니다.가장 효율적인 방법은

예를 호출 할 수 있습니다

AreTheSame<User>(u1 => u1.ID, u2 => u2.ID); --> would return true 
+0

근본적인 질문은 표현식이 익명 형식과 비슷한지 여부입니다. 즉, 동일한 표현식을 정의하더라도 표현식 트리가 항상 런타임에서 캐시되므로 항상 기본 정의가 하나만 존재한다는 것입니다.이것은 플라이 웨이트 패턴과 유사하며 문자열이 C# 및 익명 클래스로 구현되는 방식과 유사합니다. – jpierson

답변

27

흠 ... 난 당신이 각각의 노드 유형과 구성원을 확인 트리를 구문 분석해야 할 것 같아요. 나는 ... 예를 노크 것이다

UPDATE
using System; 
using System.Linq.Expressions; 
class Test { 
    public string Foo { get; set; } 
    public string Bar { get; set; } 
    static void Main() 
    { 
     bool test1 = FuncTest<Test>.FuncEqual(x => x.Bar, y => y.Bar), 
      test2 = FuncTest<Test>.FuncEqual(x => x.Foo, y => y.Bar); 
    } 

} 
// this only exists to make it easier to call, i.e. so that I can use FuncTest<T> with 
// generic-type-inference; if you use the doubly-generic method, you need to specify 
// both arguments, which is a pain... 
static class FuncTest<TSource> 
{ 
    public static bool FuncEqual<TValue>(
     Expression<Func<TSource, TValue>> x, 
     Expression<Func<TSource, TValue>> y) 
    { 
     return FuncTest.FuncEqual<TSource, TValue>(x, y); 
    } 
} 
static class FuncTest { 
    public static bool FuncEqual<TSource, TValue>(
     Expression<Func<TSource,TValue>> x, 
     Expression<Func<TSource,TValue>> y) 
    { 
     return ExpressionEqual(x, y); 
    } 
    private static bool ExpressionEqual(Expression x, Expression y) 
    { 
     // deal with the simple cases first... 
     if (ReferenceEquals(x, y)) return true; 
     if (x == null || y == null) return false; 
     if ( x.NodeType != y.NodeType 
      || x.Type != y.Type) return false; 

     switch (x.NodeType) 
     { 
      case ExpressionType.Lambda: 
       return ExpressionEqual(((LambdaExpression)x).Body, ((LambdaExpression)y).Body); 
      case ExpressionType.MemberAccess: 
       MemberExpression mex = (MemberExpression)x, mey = (MemberExpression)y; 
       return mex.Member == mey.Member; // should really test down-stream expression 
      default: 
       throw new NotImplementedException(x.NodeType.ToString()); 
     } 
    } 
} 
+1

uhmmm 6 분 및 계수 .... :) – kenny

+2

이상하게도 표현식 코드는 간단하지 않습니다! –

+2

표현은 나를 괴롭 히고, 그들은 너무 강력하지만 아직 실종되어 있습니다. +1 –

26

: 그것은 배열, 새로운 사업자 및 기타 물건을 지원하고 더 우아한으로하는 AST를 비교, 그래서 내가 코드를 업데이트 한 때문에 내 솔루션에 관심 방법.

여기에 마크의 코드의 개선 된 버전이며, 지금 그것은 nuget package로 사용할 수 : 그것은 전체 AST를 비교하지 않는

public static class LambdaCompare 
{ 
    public static bool Eq<TSource, TValue>(
     Expression<Func<TSource, TValue>> x, 
     Expression<Func<TSource, TValue>> y) 
    { 
     return ExpressionsEqual(x, y, null, null); 
    } 

    public static bool Eq<TSource1, TSource2, TValue>(
     Expression<Func<TSource1, TSource2, TValue>> x, 
     Expression<Func<TSource1, TSource2, TValue>> y) 
    { 
     return ExpressionsEqual(x, y, null, null); 
    } 

    public static Expression<Func<Expression<Func<TSource, TValue>>, bool>> Eq<TSource, TValue>(Expression<Func<TSource, TValue>> y) 
    { 
     return x => ExpressionsEqual(x, y, null, null); 
    } 

    private static bool ExpressionsEqual(Expression x, Expression y, LambdaExpression rootX, LambdaExpression rootY) 
    { 
     if (ReferenceEquals(x, y)) return true; 
     if (x == null || y == null) return false; 

     var valueX = TryCalculateConstant(x); 
     var valueY = TryCalculateConstant(y); 

     if (valueX.IsDefined && valueY.IsDefined) 
      return ValuesEqual(valueX.Value, valueY.Value); 

     if (x.NodeType != y.NodeType 
      || x.Type != y.Type) 
     { 
      if (IsAnonymousType(x.Type) && IsAnonymousType(y.Type)) 
       throw new NotImplementedException("Comparison of Anonymous Types is not supported"); 
      return false; 
     } 

     if (x is LambdaExpression) 
     { 
      var lx = (LambdaExpression)x; 
      var ly = (LambdaExpression)y; 
      var paramsX = lx.Parameters; 
      var paramsY = ly.Parameters; 
      return CollectionsEqual(paramsX, paramsY, lx, ly) && ExpressionsEqual(lx.Body, ly.Body, lx, ly); 
     } 
     if (x is MemberExpression) 
     { 
      var mex = (MemberExpression)x; 
      var mey = (MemberExpression)y; 
      return Equals(mex.Member, mey.Member) && ExpressionsEqual(mex.Expression, mey.Expression, rootX, rootY); 
     } 
     if (x is BinaryExpression) 
     { 
      var bx = (BinaryExpression)x; 
      var by = (BinaryExpression)y; 
      return bx.Method == @by.Method && ExpressionsEqual(bx.Left, @by.Left, rootX, rootY) && 
        ExpressionsEqual(bx.Right, @by.Right, rootX, rootY); 
     } 
     if (x is UnaryExpression) 
     { 
      var ux = (UnaryExpression)x; 
      var uy = (UnaryExpression)y; 
      return ux.Method == uy.Method && ExpressionsEqual(ux.Operand, uy.Operand, rootX, rootY); 
     } 
     if (x is ParameterExpression) 
     { 
      var px = (ParameterExpression)x; 
      var py = (ParameterExpression)y; 
      return rootX.Parameters.IndexOf(px) == rootY.Parameters.IndexOf(py); 
     } 
     if (x is MethodCallExpression) 
     { 
      var cx = (MethodCallExpression)x; 
      var cy = (MethodCallExpression)y; 
      return cx.Method == cy.Method 
        && ExpressionsEqual(cx.Object, cy.Object, rootX, rootY) 
        && CollectionsEqual(cx.Arguments, cy.Arguments, rootX, rootY); 
     } 
     if (x is MemberInitExpression) 
     { 
      var mix = (MemberInitExpression)x; 
      var miy = (MemberInitExpression)y; 
      return ExpressionsEqual(mix.NewExpression, miy.NewExpression, rootX, rootY) 
        && MemberInitsEqual(mix.Bindings, miy.Bindings, rootX, rootY); 
     } 
     if (x is NewArrayExpression) 
     { 
      var nx = (NewArrayExpression)x; 
      var ny = (NewArrayExpression)y; 
      return CollectionsEqual(nx.Expressions, ny.Expressions, rootX, rootY); 
     } 
     if (x is NewExpression) 
     { 
      var nx = (NewExpression)x; 
      var ny = (NewExpression)y; 
      return 
       Equals(nx.Constructor, ny.Constructor) 
       && CollectionsEqual(nx.Arguments, ny.Arguments, rootX, rootY) 
       && (nx.Members == null && ny.Members == null 
        || nx.Members != null && ny.Members != null && CollectionsEqual(nx.Members, ny.Members)); 
     } 
     if (x is ConditionalExpression) 
     { 
      var cx = (ConditionalExpression)x; 
      var cy = (ConditionalExpression)y; 
      return 
       ExpressionsEqual(cx.Test, cy.Test, rootX, rootY) 
       && ExpressionsEqual(cx.IfFalse, cy.IfFalse, rootX, rootY) 
       && ExpressionsEqual(cx.IfTrue, cy.IfTrue, rootX, rootY); 
     } 

     throw new NotImplementedException(x.ToString()); 
    } 

    private static Boolean IsAnonymousType(Type type) 
    { 
     Boolean hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any(); 
     Boolean nameContainsAnonymousType = type.FullName.Contains("AnonymousType"); 
     Boolean isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType; 

     return isAnonymousType; 
    } 

    private static bool MemberInitsEqual(ICollection<MemberBinding> bx, ICollection<MemberBinding> by, LambdaExpression rootX, LambdaExpression rootY) 
    { 
     if (bx.Count != by.Count) 
     { 
      return false; 
     } 

     if (bx.Concat(by).Any(b => b.BindingType != MemberBindingType.Assignment)) 
      throw new NotImplementedException("Only MemberBindingType.Assignment is supported"); 

     return 
      bx.Cast<MemberAssignment>().OrderBy(b => b.Member.Name).Select((b, i) => new { Expr = b.Expression, b.Member, Index = i }) 
      .Join(
        by.Cast<MemberAssignment>().OrderBy(b => b.Member.Name).Select((b, i) => new { Expr = b.Expression, b.Member, Index = i }), 
        o => o.Index, o => o.Index, (xe, ye) => new { XExpr = xe.Expr, XMember = xe.Member, YExpr = ye.Expr, YMember = ye.Member }) 
        .All(o => Equals(o.XMember, o.YMember) && ExpressionsEqual(o.XExpr, o.YExpr, rootX, rootY)); 
    } 

    private static bool ValuesEqual(object x, object y) 
    { 
     if (ReferenceEquals(x, y)) 
      return true; 
     if (x is ICollection && y is ICollection) 
      return CollectionsEqual((ICollection)x, (ICollection)y); 

     return Equals(x, y); 
    } 

    private static ConstantValue TryCalculateConstant(Expression e) 
    { 
     if (e is ConstantExpression) 
      return new ConstantValue(true, ((ConstantExpression)e).Value); 
     if (e is MemberExpression) 
     { 
      var me = (MemberExpression)e; 
      var parentValue = TryCalculateConstant(me.Expression); 
      if (parentValue.IsDefined) 
      { 
       var result = 
        me.Member is FieldInfo 
         ? ((FieldInfo)me.Member).GetValue(parentValue.Value) 
         : ((PropertyInfo)me.Member).GetValue(parentValue.Value); 
       return new ConstantValue(true, result); 
      } 
     } 
     if (e is NewArrayExpression) 
     { 
      var ae = ((NewArrayExpression)e); 
      var result = ae.Expressions.Select(TryCalculateConstant); 
      if (result.All(i => i.IsDefined)) 
       return new ConstantValue(true, result.Select(i => i.Value).ToArray()); 
     } 
     if (e is ConditionalExpression) 
     { 
      var ce = (ConditionalExpression)e; 
      var evaluatedTest = TryCalculateConstant(ce.Test); 
      if (evaluatedTest.IsDefined) 
      { 
       return TryCalculateConstant(Equals(evaluatedTest.Value, true) ? ce.IfTrue : ce.IfFalse); 
      } 
     } 

     return default(ConstantValue); 
    } 

    private static bool CollectionsEqual(IEnumerable<Expression> x, IEnumerable<Expression> y, LambdaExpression rootX, LambdaExpression rootY) 
    { 
     return x.Count() == y.Count() 
       && x.Select((e, i) => new { Expr = e, Index = i }) 
        .Join(y.Select((e, i) => new { Expr = e, Index = i }), 
         o => o.Index, o => o.Index, (xe, ye) => new { X = xe.Expr, Y = ye.Expr }) 
        .All(o => ExpressionsEqual(o.X, o.Y, rootX, rootY)); 
    } 

    private static bool CollectionsEqual(ICollection x, ICollection y) 
    { 
     return x.Count == y.Count 
       && x.Cast<object>().Select((e, i) => new { Expr = e, Index = i }) 
        .Join(y.Cast<object>().Select((e, i) => new { Expr = e, Index = i }), 
         o => o.Index, o => o.Index, (xe, ye) => new { X = xe.Expr, Y = ye.Expr }) 
        .All(o => Equals(o.X, o.Y)); 
    } 

    private struct ConstantValue 
    { 
     public ConstantValue(bool isDefined, object value) 
      : this() 
     { 
      IsDefined = isDefined; 
      Value = value; 
     } 

     public bool IsDefined { get; private set; } 

     public object Value { get; private set; } 
    } 
} 

참고. 대신 상수 표현식을 축소하고 AST가 아닌 값을 비교합니다. 람다가 지역 변수에 대한 참조를 가지고있을 때 모의 검증에 유용합니다. 그의 경우 변수는 그 값에 의해 비교됩니다.

단위 테스트 :

[TestClass] 
public class Tests 
{ 
    [TestMethod] 
    public void BasicConst() 
    { 
     var f1 = GetBasicExpr1(); 
     var f2 = GetBasicExpr2(); 
     Assert.IsTrue(LambdaCompare.Eq(f1, f2)); 
    } 

    [TestMethod] 
    public void PropAndMethodCall() 
    { 
     var f1 = GetPropAndMethodExpr1(); 
     var f2 = GetPropAndMethodExpr2(); 
     Assert.IsTrue(LambdaCompare.Eq(f1, f2)); 
    } 

    [TestMethod] 
    public void MemberInitWithConditional() 
    { 
     var f1 = GetMemberInitExpr1(); 
     var f2 = GetMemberInitExpr2(); 
     Assert.IsTrue(LambdaCompare.Eq(f1, f2)); 
    } 

    [TestMethod] 
    public void AnonymousType() 
    { 
     var f1 = GetAnonymousExpr1(); 
     var f2 = GetAnonymousExpr2(); 
     Assert.Inconclusive("Anonymous Types are not supported"); 
    } 

    private static Expression<Func<int, string, string>> GetBasicExpr2() 
    { 
     var const2 = "some const value"; 
     var const3 = "{0}{1}{2}{3}"; 
     return (i, s) => 
      string.Format(const3, (i + 25).ToString(CultureInfo.InvariantCulture), i + s, const2.ToUpper(), 25); 
    } 

    private static Expression<Func<int, string, string>> GetBasicExpr1() 
    { 
     var const1 = 25; 
     return (first, second) => 
      string.Format("{0}{1}{2}{3}", (first + const1).ToString(CultureInfo.InvariantCulture), first + second, 
       "some const value".ToUpper(), const1); 
    } 

    private static Expression<Func<Uri, bool>> GetPropAndMethodExpr2() 
    { 
     return u => Uri.IsWellFormedUriString(u.ToString(), UriKind.Absolute); 
    } 

    private static Expression<Func<Uri, bool>> GetPropAndMethodExpr1() 
    { 
     return arg1 => Uri.IsWellFormedUriString(arg1.ToString(), UriKind.Absolute); 
    } 

    private static Expression<Func<Uri, UriBuilder>> GetMemberInitExpr2() 
    { 
     var isSecure = true; 
     return u => new UriBuilder(u) { Host = string.IsNullOrEmpty(u.Host) ? "abc" : "def" , Port = isSecure ? 443 : 80 }; 
    } 

    private static Expression<Func<Uri, UriBuilder>> GetMemberInitExpr1() 
    { 
     var port = 443; 
     return x => new UriBuilder(x) { Port = port, Host = string.IsNullOrEmpty(x.Host) ? "abc" : "def" }; 
    } 

    private static Expression<Func<Uri, object>> GetAnonymousExpr2() 
    { 
     return u => new { u.Host , Port = 443, Addr = u.AbsolutePath }; 
    } 

    private static Expression<Func<Uri, object>> GetAnonymousExpr1() 
    { 
     return x => new { Port = 443, x.Host, Addr = x.AbsolutePath }; 
    } 
} 
+0

'AST'는 무엇을 의미합니까? – bump

+2

추상 구문 트리 – neleus

+0

위대한 일, 매력처럼 작동 – Nock

3

정규 솔루션은 좋은 것입니다. 그동안 나는 IEqualityComparer<Expression> 버전을 만들었습니다. 이것은 다소 장황한 구현이므로 I created a gist for it입니다.

포괄적 인 추상 구문 트리 비교자를위한 것입니다. 이를 위해, TrySwitchBlock과 같이 C#에서 아직 지원되지 않는 표현식을 포함하여 모든 표현식 유형을 비교합니다. 내가 비교하지 않는 유일한 유형은 내 제한된 지식으로 인해 Goto, Label, LoopDebugInfo입니다.

ConstantExpression을 처리하는 방법뿐만 아니라 매개 변수와 lambda의 이름을 비교할 것인지 여부와 방법을 지정할 수 있습니다.

문맥에 따라 매개 변수를 위치에 따라 추적합니다. lambdas 및 catch 블록 변수 매개 변수 내부의 Lambdas가 지원됩니다.

1

나는이 오래된 질문 알아요,하지만 난 내 자신의 식 트리 같음 비교 압연 - https://github.com/yesmarket/yesmarket.Linq.Expressions

구현은 두 개의 식 트리가 같은지 여부를 확인하기 위해 ExpressionVisitor 클래스를 많이 사용하게합니다. 표현식 트리의 노드가 가로 지르면 개별 노드가 동일한 지 비교됩니다.