Using LINQ to write constraints in OCL style
I wanted to investigate using LINQ to write constraints "OCL-style". I made a "minimal language" and added a new NamedDomainClass called Property, embedded in ExampleElement. Then I wrote the following validation method to check that all of the Properties attached to an ExampleElement are uniquely named:
[ValidationState(ValidationState.Enabled)]
public partial class ExampleElement
{
[ValidationMethod(ValidationCategories.Menu | ValidationCategories.Save)]
private void TestExampleElement(ValidationContext context)
{
var propnames = from p in this.Properties select p.Name;
var distinctnames = propnames.Distinct<string>();
if (propnames.Count<string>() != distinctnames.Count<string>())
{
context.LogError("Non-unique property names", "Error 1");
}
}
}
A lot tighter than writing it in good old C#, I think. Then I thought about "flattened sets", i.e. navigating across more than one relationship and creating a single collection containing the results. So I created SubProperty embedded in Property, and extended the constraint like this:
[ValidationState(ValidationState.Enabled)]
public partial class ExampleElement
{
[ValidationMethod(ValidationCategories.Menu | ValidationCategories.Save)]
private void TestExampleElement(ValidationContext context)
{
var propnames = from p in this.Properties select p.Name;
var distinctnames = propnames.Distinct<string>();
if (propnames.Count<string>() != distinctnames.Count<string>())
{
context.LogError("Non-unique property names", "Error 1");
}
var subproperties = this.Properties.Aggregate(Enumerable.Empty<SubProperty>(),
(agg, p) => agg.Union<SubProperty>(p.SubProperties));
var subpropnames = from p in subproperties select p.Name;
var distinctsubpropnames = subpropnames.Distinct<string>();
if (subpropnames.Count<string>() != distinctsubpropnames.Count<string>())
{
context.LogError("Non-unique sub property names", "Error 2");
}
}
}
Not bad: but that line to calculate and flatten the subproperties is a bit complicated. So, inspired by OCL, I defined a new extension method like this:
public static class C
{
public static IEnumerable<U> Collect<T, U>(this IEnumerable<T> source, Func<T, IEnumerable<U>> func)
{
return source.Aggregate(Enumerable.Empty<U>(), (agg, p) => agg.Union<U>(func(p)));
}
}
And now the subproperties line looks like this:
var subproperties = this.Properties.Collect(p => p.SubProperties);
I think I may be using that Collect method again!
Comments
Anonymous
August 23, 2007
PingBack from http://msdnrss.thecoderblogs.com/2007/08/23/using-linq-to-write-constraints-in-ocl-style/Anonymous
August 23, 2007
Two questions:
- Doesn't LINQ's SelectMany do what you want to do (flatten collections)?
- Out of curiosity, why do you explicitly specify the type parameter to methods like Count (Count<string>() instead of just count())? Thanks!
Anonymous
August 23, 2007
SelectMany - I will have to try that. The type parameters to Count and Distinct are unnecessary, as you point out. -- SteveAnonymous
August 23, 2007
Oh one other style question (I'm very curious about how people view functional programming): You wrote: var propnames = from p in this.Properties select p.Name; Instead of: var propnames = this.Properties.Select(p => p.Name); Any particular reason?Anonymous
August 23, 2007
Really no reason at all. I haven't decided which I prefer yet. I suppose to be consistent with OCL I would go for the second style. You are correct about SelectMany - it does just the same as my Collect. So Collect is unnecessary. I guess the name confused me. Still, writing the extension method was a good learning experience.Anonymous
August 23, 2007
Cool! I love seeing functional styles; please keep blogging things like this. Hopefully, more people will see why this is vastly superior to writing it imperatively. In fact, do you have a snippet of what it would look like without the C# 3.0 features, for comparison?Anonymous
August 23, 2007
I guess doing it the “old way” would typically look like this: [ValidationMethod(ValidationCategories.Menu | ValidationCategories.Save)] private void TestExampleElementTheOldWay(ValidationContext context) { List<string> uniquePropertyNames = new List<string>(); foreach (Property p in this.Properties) { if (uniquePropertyNames.Contains(p.Name)) { context.LogError("Non-unique property names (old way)", "Error 3"); } else { uniquePropertyNames.Add(p.Name); } } List<string> uniqueSubPropertyNames = new List<string>(); foreach (Property p in this.Properties) { foreach (SubProperty sp in p.SubProperties) { if (uniqueSubPropertyNames.Contains(sp.Name)) { context.LogError("Non-unique sub property names (old way)", "Error 4"); } else { uniqueSubPropertyNames.Add(sp.Name); } } } }