What fluent interfaces have you made or seen in C# that were very valuable? What was so great about them?

后端 未结 11 1188
-上瘾入骨i
-上瘾入骨i 2021-01-31 05:42

\"Fluent interfaces\" is a fairly hot topic these days. C# 3.0 has some nice features (particularly extension methods) that help you make them.

FYI, a fluent API means

相关标签:
11条回答
  • 2021-01-31 05:47

    Method Naming

    Fluent interfaces lend themselves to readability as long as the method names are chosen sensibly.

    With that in mind, I'd like to nominate this particular API as "anti-fluent":

    System.Type.IsInstanceOfType

    It's a member of System.Type and takes an object, and returns true if the object is an instance of the type. Unfortunately, you naturally tend to read it from left to right like this:

    o.IsInstanceOfType(t);  // wrong
    

    When it's actually the other way:

    t.IsInstanceOfType(o);  // right, but counter-intuitive
    

    But not all methods could possibly be named (or positioned in the BCL) to anticipate how they might appear in "pseudo-English" code, so this isn't really a criticism. I'm just pointing out another aspect of fluent interfaces - the choosing of method names in order to cause the least surprise.

    Object Initializers

    With many of the examples given here, the only reason a fluent interface is being used is so that several properties of a newly allocated object can be initialized within a single expression.

    But C# has a language feature that very often makes this unnecessary - object initializer syntax:

    var myObj = new MyClass
                {
                    SomeProperty = 5,
                    Another = true,
                    Complain = str => MessageBox.Show(str),
                };
    

    This perhaps would explain why expert C# users are less familiar with the term "fluent interface" for chaining calls on the same object - it isn't needed quite so often in C#.

    As properties can have hand-coded setters, this is an opportunity to call several methods on the newly constructed object, without having to make each method return the same object.

    The limitations are:

    • A property setter can only accept one argument
    • A property setter cannot be generic

    I would like it if we could call methods and enlist in events, as well as assign to properties, inside an object initializer block.

    var myObj = new MyClass
                {
                    SomeProperty = 5,
                    Another = true,
                    Complain = str => MessageBox.Show(str),
                    DoSomething()
                    Click += (se, ev) => MessageBox.Show("Clicked!"),
                };
    

    And why should such a block of modifications only be applicable immediately after construction? We could have:

    myObj with
    {
        SomeProperty = 5,
        Another = true,
        Complain = str => MessageBox.Show(str),
        DoSomething(),
        Click += (se, ev) => MessageBox.Show("Clicked!"),
    }
    

    The with would be a new keyword that operates on an object of some type and produces the same object and type - note that this would be an expression, not a statement. So it would exactly capture the idea of chaining in a "fluent interface".

    So you could use initializer-style syntax regardless of whether you'd got the object from a new expression or from an IOC or factory method, etc.

    In fact you could use with after a complete new and it would be equivalent to the current style of object initializer:

    var myObj = new MyClass() with
                {
                    SomeProperty = 5,
                    Another = true,
                    Complain = str => MessageBox.Show(str),
                    DoSomething(),
                    Click += (se, ev) => MessageBox.Show("Clicked!"),
                };
    

    And as Charlie points out in the comments:

    public static T With(this T with, Action<T> action)
    {
        if (with != null)
            action(with);
        return with;
    }
    

    The above wrapper simply forces a non-returning action to return something, and hey presto - anything can be "fluent" in that sense.

    Equivalent of initializer, but with event enlisting:

    var myObj = new MyClass().With(w =>
                {
                    w.SomeProperty = 5;
                    w.Another = true;
                    w.Click += (se, ev) => MessageBox.Show("Clicked!");
                };
    

    And on a factory method instead of a new:

    var myObj = Factory.Alloc().With(w =>
                {
                    w.SomeProperty = 5;
                    w.Another = true;
                    w.Click += (se, ev) => MessageBox.Show("Clicked!");
                };
    

    I couldn't resist giving it the "maybe monad"-style check for null as well, so if you have something that might return null, you can still apply With to it and then check it for null-ness.

    0 讨论(0)
  • 2021-01-31 05:48

    In addition to the ones specified here, the popuplar RhinoMocks unit test mock framework uses fluent syntax to specify expectations on mock objects:

    // Expect mock.FooBar method to be called with any paramter and have it invoke some method
    Expect.Call(() => mock.FooBar(null))
        .IgnoreArguments()
        .WhenCalled(someCallbackHere);
    
    // Tell mock.Baz property to return 5:
    SetupResult.For(mock.Baz).Return(5);
    
    0 讨论(0)
  • 2021-01-31 05:48

    SubSonic 2.1 has a decent one for the query API:

    DB.Select()
      .From<User>()
      .Where(User.UserIdColumn).IsEqualTo(1)
      .ExecuteSingle<User>();
    

    tweetsharp makes extensive use of a fluent API too:

    var twitter = FluentTwitter.CreateRequest()
                  .Configuration.CacheUntil(2.Minutes().FromNow())
                  .Statuses().OnPublicTimeline().AsJson();
    

    And Fluent NHibernate is all the rage lately:

    public class CatMap : ClassMap<Cat>  
    {  
      public CatMap()  
      {  
        Id(x => x.Id);  
        Map(x => x.Name)  
          .WithLengthOf(16)  
          .Not.Nullable();  
        Map(x => x.Sex);  
        References(x => x.Mate);  
        HasMany(x => x.Kittens);  
      }  
    }  
    

    Ninject uses them too, but I couldn't find an example quickly.

    0 讨论(0)
  • 2021-01-31 05:51

    The Criteria API in NHibernate has a nice fluent interface which allows you to do cool stuff like this:

    Session.CreateCriteria(typeof(Entity))
        .Add(Restrictions.Eq("EntityId", entityId))
        .CreateAlias("Address", "Address")
        .Add(Restrictions.Le("Address.StartDate", effectiveDate))
        .Add(Restrictions.Disjunction()
            .Add(Restrictions.IsNull("Address.EndDate"))
            .Add(Restrictions.Ge("Address.EndDate", effectiveDate)))
        .UniqueResult<Entity>();
    
    0 讨论(0)
  • 2021-01-31 05:52

    Here's one I made just yesterday. Further thought may lead me to change the approach, but even if so, the "fluent" approach let me accomplish something I otherwise could not have.

    First, some background. I recently learned (here on StackOverflow) a way to pass a value to a method such that the method would be able to determine both the name and the value. For example, one common use is for parameter validation. For example:

    public void SomeMethod(Invoice lastMonthsInvoice)
    {
         Helper.MustNotBeNull( ()=> lastMonthsInvoice);
    }
    

    Note there's no string containing "lastMonthsInvoice", which is good because strings suck for refactoring. However, the error message can say something like "The parameter 'lastMonthsInvoice' must not be null." Here's the post that explains why this works and points to the guy's blog post.

    But that is just background. I'm using the same concept, but in a different way. I am writing some unit tests, and I want to dump certain property values out to the console so they show up in the unit test output. I got tired of writing this:

    Console.WriteLine("The property 'lastMonthsInvoice' has the value: " + lastMonthsInvoice.ToString());
    

    ... because I have to name the property as a string and then refer to it. So I made it where I could type this:

    ConsoleHelper.WriteProperty( ()=> lastMonthsInvoice );
    

    And get this output:

    Property [lastMonthsInvoice] is: <whatever ToString from Invoice
    

    produces>

    Now, here's where a fluent approach allowed me to do something I otherwise couldn't do.

    I wanted to make ConsoleHelper.WriteProperty take a params array, so it could dump many such property values to the console. To do that, its signature would look like this:

    public static void WriteProperty<T>(params Expression<Func<T>>[] expr)
    

    So I could do this:

    ConsoleHelper.WriteProperty( ()=> lastMonthsInvoice, ()=> firstName, ()=> lastName );
    

    However, that doesn't work due to type inference. In other words, all of these expressions do not return the same type. lastMonthsInvoice is an Invoice. firstName and lastName are strings. They cannot be used in the same call to WriteProperty, because T is not the same across all of them.

    This is where the fluent approach came to the rescue. I made WriteProperty() return something. The type it returned is something I can call And() on. This gives me this syntax:

    ConsoleHelper.WriteProperty( ()=> lastMonthsInvoice)
         .And( ()=> firstName)
         .And( ()=> lastName);
    

    This is a case where the fluent approach allowed something that otherwise would not have been possible (or at least not convenient).

    Here's the full implementation. As I said, I wrote it yesterday. You'll probably see room for improvement or maybe even better approaches. I welcome that.

    public static class ConsoleHelper
    {
        // code where idea came from ...
        //public static void IsNotNull<T>(Expression<Func<T>> expr)
        //{
        // // expression value != default of T
        // if (!expr.Compile()().Equals(default(T)))
        // return;
    
        // var param = (MemberExpression)expr.Body;
        // throw new ArgumentNullException(param.Member.Name);
        //}
    
        public static PropertyWriter WriteProperty<T>(Expression<Func<T>> expr)
        {
            var param = (MemberExpression)expr.Body;
            Console.WriteLine("Property [" + param.Member.Name + "] = " + expr.Compile()());
            return null;
        }
    
        public static PropertyWriter And<T>(this PropertyWriter ignored, Expression<Func<T>> expr)
        {
            ConsoleHelper.WriteProperty(expr);
            return null;
        }
    
        public static void Blank(this PropertyWriter ignored)
        {
            Console.WriteLine();
        }
    }
    
    public class PropertyWriter
    {
        /// <summary>
        /// It is not even possible to instantiate this class. It exists solely for hanging extension methods off.
        /// </summary>
        private PropertyWriter() { }
    }
    
    0 讨论(0)
  • 2021-01-31 05:54

    This is actually the first time I've heard the term "fluent interface." But the two examples that come to mind are LINQ and immutable collections.

    Under the covers LINQ is a series of methods, most of which are extension methods, which take at least one IEnumerable and return another IEnumerable. This allows for very powerful method chaining

    var query = someCollection.Where(x => !x.IsBad).Select(x => x.Property1);
    

    Immutable types, and more specifically collections have a very similar pattern. Immutable Collections return a new collection for what would be normally a mutating operation. So building up a collection often turns into a series of chained method calls.

    var array = ImmutableCollection<int>.Empty.Add(42).Add(13).Add(12);
    
    0 讨论(0)
提交回复
热议问题