简体   繁体   English

C#表达式比较

[英]C# Expression Comparison

Let's say I have following expressions on a collection: 假设我在集合上有以下表达式:

var people = new List<Person>
{
     new Person {FullName = "Some Dude", Age = 45},
     new Person {FullName = "Another Dude", Age = 28},
     new Person {FullName = "Some Other Dude", Age = 36}
 };

var filtered = people.Where(person => person.Age > 28 && person.FullName.StartsWith("So"));
var narrowlyFiltered = people.Where(person => person.Age > 36 && person.FullName.StartsWith("Some"));

Is there a way to compare these two expressions and deduce that second expression is subset of first one on runtime? 有没有办法比较这两个表达式并推断出第二个表达式是运行时第一个表达式的子集? Without enumerating or anything else. 没有枚举或其他任何东西。 I just have expressions and I am trying to find out if these expressions intersect or contains one another. 我只是有表达式,我试图找出这些表达式是否相交或相互包含。

你必须将每个Expression分解为所有可能的继承类型(MethodCallExpression,ConditionalExpression等),然后并行地进行每个分解并检查每个可能的参数......编码会有点长......你可以启发来自ExpressionEqualityComparer

In case you can enumerate your collections, you can first put the elements in a HashSet<T> and then run the HashSet<T>.IsSubSet on it: 如果您可以枚举您的集合,您可以先将这些元素放在HashSet<T> ,然后在其上运行HashSet<T>.IsSubSet

HashSet<T> hs = new HashSet<T>(filtered);
HashSet<T> hs2 = new HashSet<T>(narrowlyFiltered);
hs.IsSubSetOf(hs2); //<- booleans saying true or false

Otherwise, this problems is an undecidable problem in general. 否则,这个问题通常是一个不可判定的问题 Although there are heuristics that can work for many cases. 虽然有一些启发式方法可以适用于许多情况。 You could for instance try to use code contracts that aim to deduce this at compile time. 例如,您可以尝试使用旨在在编译时推断出这一点的代码契约。

Proof: 证明:

The formal variant is: given two Turing machines (methods, delegates, pointers), does every string contained in the first language is contained in the second? 正式变体是:给定两个图灵机(方法,委托,指针),第一语言中包含的每个字符串是否包含在第二个语言中?
Undecidable 不可判定
proof : given it was decidable, EQ TM would be decidable: simply first validate whether the first Turing machine is a subset of the second and vice versa. 证明 :鉴于它是可判定的,EQ TM将是可判定的:只需首先验证第一个图灵机是否是第二个的子集,反之亦然。 If both are subsets, we know they accept the same set of strings. 如果两者都是子集,我们知道它们接受相同的字符串集。

In other words, if you could do that, you could also deduce if two functions produce the same result, which cannot be implemented . 换句话说,如果你能做到这一点,你也可以推断出两个函数是否产生了相同的结果, 而这些结果是无法实现的

It all depends how you weight what is equal, what is more important when comparing expressions etc. For example if you have completely different filter than you won't possibly know query difference before actually executing it. 这一切都取决于你如何权衡相等,更重要的是比较表达式等。例如,如果你有完全不同的过滤器,那么在实际执行它之前你不可能知道查询差异。

To keep full control over your comparison create a filter class with some properties which can be used for filtering and then build expressions and compare using this class instead of using visitors. 要保持对比较的完全控制,请创建一个过滤器类,其中包含一些属性,这些属性可用于过滤,然后构建表达式并使用此类进行比较,而不是使用访问者。 You can prepare common function for comparing ints, int pairs (for ranges) etc. 您可以准备用于比较整数,整数对(范围)等的通用函数。

I did not check the code below but it should be a good start. 我没有检查下面的代码,但它应该是一个好的开始。

public class PersonFilter:  IComparable<PersonFilter>
{
    public int? MinAge { get; set; }

    public int? MaxAge { get; set; }

    public string NamePrefix { get; set; }

    public Expression<Predicate<Person>> Filter
    {
        return people => people.Where(person => (!MinAge.HasValue || person.Age > MinAge.Value) && 
            (!MaxAge.HasValue || person.Age < MaxAge.Value) && 
            (string.IsNullOrEmpty(NamePrefix) || person.FullName.StartsWith(NamePrefix))
    }

    // -1 if this filter is filtering more than the other
    public int CompareTo(PersonFilter other)
    {
        var balance = 0; // equal
        if(MinAge.HasValue != other.MinAge.HasValue)
        {
            balance += MinAge.HasValue ? -1 : 1;
        }
        else if(MinAge.HasValue)
        {
            balance += MinAge.Value.CompareTo(other.MinAge.Value) ?
        }
        if(string.IsNullOrEmpty(NamePrefix) != string.IsNullOrEmpty(other.NamePrefix))
        {
            balance += string.IsNullOrEmpty(NamePrefix) ? -1 : 1;
        }
        else if(!string.IsNullOrEmpty(NamePrefix))
        {
            if(NamePrefix.StartsWith(other.NamePrefix))
            {
                balance -= 1;
            }
            else if(other.NamePrefix.StartsWith(NamePrefix))
            {
                balance += 1;
            }
            else
            {
                // if NamePrefix is the same or completely different let's assume both filters are equal
            }
        }
        return balance;
    }

    public bool IsSubsetOf(PersonFilter other)
    {
        if(MinAge.HasValue != other.MinAge.HasValue)
        {
            if(other.MinAge.HasValue)
            {
                return false;
            }
        }
        else if(MinAge.HasValue && MinAge.Value < other.MinAge.Value)
        {
            return false;
        }

        if(string.IsNullOrEmpty(NamePrefix) != string.IsNullOrEmpty(other.NamePrefix))
        {
            if(!string.IsNullOrEmpty(other.NamePrefix))
            {
                return false;
            }
        }
        else if(!string.IsNullOrEmpty(NamePrefix))
        {
            if(!NamePrefix.StartsWith(other.NamePrefix))
            {
            return false;
            }
        }

        return true;
    }
}

Have a look at the Specification design pattern 看看规格设计模式

Once it's implemented then your specification in this case becomes 一旦实施,那么在这种情况下你的规范就变成了

public class PersonNamedOlderThanSpecification : CompositeSpecification<Person>
{
    private string name;
    private int age;

    public PersonNamedOlderThanSpecification(string name, int age)
    {
        this.name = name;
        this.age = age;
    }


    public override bool IsSatisfiedBy(Person entity)
    {
        return (entity.Name.Contains(this.name)) && (entity.Age > age);
    }
}

Then you can use it like this: 然后你可以像这样使用它:

var personSpecs = new PersonNamedOlderThanSpecification("So", 28);
var personSpecs2 = new PersonNamedOlderThanSpecification("Some", 36);

var filtered = people.FindAll(x => personSpecs.IsSatisfiedBy(x));
var adjusted = people.FindAll(x => personSpecs2.IsSatisfiedBy(x));

You can try this: 你可以试试这个:

var people = new List<Person>
{
     new Person {FullName = "Some Dude", Age = 45},
     new Person {FullName = "Another Dude", Age = 28},
     new Person {FullName = "Some Other Dude", Age = 36}
};

var filtered = people.Where(person => person.Age > 28 && person.FullName.StartsWith("So"));
var narrowlyFiltered = people.Where(person => person.Age > 36 && person.FullName.StartsWith("Some"));

var intersection = filtered.Intersect(narrowlyFiltered);
if (intersection != null)
{
    if (intersection.Count() > 0)
    {
        //narrowlyFiltered is subset of filtered
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM