Compartilhar via


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 removed

  • Anonymous
    November 22, 2007
    In preparing for my presentation at TechEd, I got a chance to try out a couple of new things with DSL

  • Anonymous
    November 22, 2007
    In preparing for my presentation at TechEd , I got a chance to try out a couple of new things with DSL