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 (5)

  1. MichaelGiagnocavo says:

    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…).

  2. stevecook says:

    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.

  3. MichaelGiagnocavo says:

    Yea, it might be going a bit overboard for this particular example; but, most of the helper stuff you can use cross project. This also demonstrates why Extension Methods sorta suck: You can quickly end up with TONS of extensions and no way of organizing / scoping them. Instead, they should have had some other infix operator so you could do like this:

    myStuff |> Seq.SelectMany

    Then your extension methods are still semi-qualified and you don’t have to worry about having 200 extensions on all sorts of types. It also opens up the possibility of using method NOT marked explicitly as extensions, such as Array.BinarySearch.

    As far as built in, yes, there should be Action/Func methods for all number of generics. Even better, C# should just treat func/action as first-class citizens — there’s no really good reason to have different delegate types now that generics exist.

    Currying code isn’t that bad; it looks like this (times every combination):

    static Func<A0, Func<A1, R>> Curry<A0,A1,R>(this Func<A0,A1,R> f) {

       return arg0 => arg1 => f(arg0, arg1);

    }

    The main problem with Currying is that it’s hard to get a normal method (like sya Log.WriteError) to become a Func.

  4. In preparing for my presentation at TechEd, I got a chance to try out a couple of new things with DSL

  5. In preparing for my presentation at TechEd , I got a chance to try out a couple of new things with DSL