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

后端 未结 11 1186
-上瘾入骨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 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.

提交回复
热议问题