Using LINQ to write constraints in OCL style v4
Based on much appreciated feedback from my earlier posts, here is my latest version. This one is more compact and expressive, and the Duplicates function scales roughly linearly so I don't think that any performance has been sacrificed for expressive power.
[ValidationState(ValidationState.Enabled)]
public partial class ExampleElement
{
[ValidationMethod(ValidationCategories.Menu | ValidationCategories.Save)]
private void TestExampleElement(ValidationContext context)
{
var nonUniquePropNames = this.Properties.Select(p => p.Name).Duplicates();
nonUniquePropNames.IfAny(pns =>
context.LogError(String.Format("Non-unique property names: {0}", pns.CommaSeparatedList()), "Error 1", this));
var nonUniqueSubPropNames = this.Properties.SelectMany(p => p.SubProperties).Select(p => p.Name).Duplicates();
nonUniqueSubPropNames.IfAny(spns =>
context.LogError(String.Format("Non-unique sub property names: {0} ", spns.CommaSeparatedList()), "Error 2", this));
}
}
public static class C
{
public static HashSet<T> Duplicates<T>(this IEnumerable<T> source)
{
HashSet<T> items = new HashSet<T>();
HashSet<T> duplicates = new HashSet<T>();
foreach (T item in source)
{
if (!items.Add(item))
duplicates.Add(item);
}
return duplicates;
}
public static string CommaSeparatedList(this IEnumerable<string> source)
{
// source is not empty
return source.Aggregate((agg, s) => agg + ", " + s);
}
public static void IfAny<T>(this IEnumerable<T> source, Action<IEnumerable<T>> act)
{
if (source.Count() > 0) act(source);
}
}
Comments
Anonymous
September 02, 2007
Looking very nice. But, the whole "collection, if any, log error" is repeated twice. What do you think about using inner functions? var logNonUnique = Action((IEnumerable<string> allNames, string title, string errorName) => allNames.Duplicate().IfAny(dups => context.LogError(String.Format("Non-unique {0} names: {1}", title, dups.CommaSeparatedList()), errorName, this))); logNonUnique(this.Properties.Select(p => p.Name), "property", "Error 1"); logNonUnique(this.Properties.SelectMany(p => p.SubProperties).Select(p => p.Name), "sub property", "Error 2"); The Action() syntax requires this stupid helper function to make C# infer the types: static Action<A0, A1, A2> Action<A0, A1, A2>(Action<A0, A1, A2> f) { return f; } Why that is not included in the BCL is beyond me. I think this example might also start to demonstrate why currying can be powerful (think how it'd look like that...).Anonymous
September 02, 2007
Michael - nice idea! Might be a bit of overkill for this example, although I understand the motive. To be built in, that Action helper function would have to be defined for <A0>, <A0, A1>, <A0, A1, A2>, etc. Not sure what you have in mind for the currying? In this example you could perhaps change the syntax to look something like this logNonUnique(this.Properties.Select(p => p.Name).Duplicates()) ("property") ("Error 1"); but I shudder to think what helper stuff I'll have to create to get that working.Anonymous
September 04, 2007
The comment has been removedAnonymous
November 22, 2007
In preparing for my presentation at TechEd, I got a chance to try out a couple of new things with DSLAnonymous
November 22, 2007
In preparing for my presentation at TechEd , I got a chance to try out a couple of new things with DSL