C # Expression Comparison

Say I have the following expressions in 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 infer that the second expression is a subset of the first at runtime? Without listing or anything else. I just have expressions, and I'm trying to figure out if these expressions intersect or contain each other.

+7
c # lambda
source share
5 answers

You will need to decompose each expression into all possible inherited types (MethodCallExpression, ConditionalExpression, etc.), then go through each decomposition in parallel and check all possible parameters ... This will be a little longer than the code ... You can inspire yourself with ExpressionEqualityComparer

+5
source share

If you can list your collections, you can first put items in a HashSet<T> , and then run a HashSet<T>.IsSubSet on it

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

Otherwise, the problem is an insoluble problem in general. Although there is a heuristic that can work in many cases. You could, for example, try to use code contracts that seek to output this at compile time.

Evidence:

Formal option: two Turing machines (methods, delegates, pointers) are given, each line contained in the first language is contained in the second?
Insoluble
proof: if it is decidable, EQ TM will be decidable: just first check if the first Turing machine is a subset of the second and vice versa. If both are subsets, we know that they accept the same set of strings.

In other words, if you could do this, you could also deduce if two functions produce the same result that cannot be implemented .
+2
source share

It all depends on how you weigh, which is equal, which is more important when comparing expressions, etc. For example, if you have a completely different filter, you won’t be able to find out the difference in queries before executing it.

To provide complete control over your comparison, create a filter class with some properties that you can use to filter, and then build expressions and compare using this class, rather than using visitors. You can prepare a general function for comparing ints, int pairs (for ranges), etc.

I have not tested the code below, but this 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 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; } } 
+2
source share

Look at the spec design template.

Once it is implemented, your specification in this case will become

 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 as follows:

 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)); 
+1
source share

You can try the following:

 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 } } 
0
source share

All Articles