이 방법 서명을 감안할 때 연산 식? 이것은 단순한 표현식에서만 작동 할 필요가 있습니다. 즉, "지원되는"모든 것이 단순한 MemberExpressions 일 것입니다. 예를 들어 c => c.ID입니다.가장 효율적인 방법은
예를 호출 할 수 있습니다
AreTheSame<User>(u1 => u1.ID, u2 => u2.ID); --> would return true
이 방법 서명을 감안할 때 연산 식? 이것은 단순한 표현식에서만 작동 할 필요가 있습니다. 즉, "지원되는"모든 것이 단순한 MemberExpressions 일 것입니다. 예를 들어 c => c.ID입니다.가장 효율적인 방법은
예를 호출 할 수 있습니다
AreTheSame<User>(u1 => u1.ID, u2 => u2.ID); --> would return true
흠 ... 난 당신이 각각의 노드 유형과 구성원을 확인 트리를 구문 분석해야 할 것 같아요. 나는 ... 예를 노크 것이다
UPDATEusing 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());
}
}
}
uhmmm 6 분 및 계수 .... :) – kenny
이상하게도 표현식 코드는 간단하지 않습니다! –
표현은 나를 괴롭 히고, 그들은 너무 강력하지만 아직 실종되어 있습니다. +1 –
: 그것은 배열, 새로운 사업자 및 기타 물건을 지원하고 더 우아한으로하는 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 };
}
}
정규 솔루션은 좋은 것입니다. 그동안 나는 IEqualityComparer<Expression>
버전을 만들었습니다. 이것은 다소 장황한 구현이므로 I created a gist for it입니다.
포괄적 인 추상 구문 트리 비교자를위한 것입니다. 이를 위해, Try
및 Switch
및 Block
과 같이 C#에서 아직 지원되지 않는 표현식을 포함하여 모든 표현식 유형을 비교합니다. 내가 비교하지 않는 유일한 유형은 내 제한된 지식으로 인해 Goto
, Label
, Loop
및 DebugInfo
입니다.
ConstantExpression
을 처리하는 방법뿐만 아니라 매개 변수와 lambda의 이름을 비교할 것인지 여부와 방법을 지정할 수 있습니다.
문맥에 따라 매개 변수를 위치에 따라 추적합니다. lambdas 및 catch 블록 변수 매개 변수 내부의 Lambdas가 지원됩니다.
나는이 오래된 질문 알아요,하지만 난 내 자신의 식 트리 같음 비교 압연 - https://github.com/yesmarket/yesmarket.Linq.Expressions
구현은 두 개의 식 트리가 같은지 여부를 확인하기 위해 ExpressionVisitor 클래스를 많이 사용하게합니다. 표현식 트리의 노드가 가로 지르면 개별 노드가 동일한 지 비교됩니다.
근본적인 질문은 표현식이 익명 형식과 비슷한지 여부입니다. 즉, 동일한 표현식을 정의하더라도 표현식 트리가 항상 런타임에서 캐시되므로 항상 기본 정의가 하나만 존재한다는 것입니다.이것은 플라이 웨이트 패턴과 유사하며 문자열이 C# 및 익명 클래스로 구현되는 방식과 유사합니다. – jpierson