5

this is what I'm trying to achieve:

config.Name("Foo")
      .Elements(() => {
                         Element.Name("element1").Height(23);
                         Element.Name("element2").Height(31);
                      })
     .Foo(23);

or like this:

  .Elements(e => {
                     e.Name("element1").Height(23);
                     e.Name("element2").Height(31);
                  })
  .Foo(3232);

this is what I have for the moment:

public class Config
{
   private string name;
   private int foo;
   private IList<Element> elements = new List<Element>();

   public Config Name(string name)
   {
      this.name = name;
      return this;
   }

   public Config Foo(int x)
   {
       this.foo = x;
   }

   ... //add method for adding elements 

   class Element
   {
      public string Name { get; set; }
      public int Height { get; set; }
   }
}

anybody knows how to do this ?

Omu
  • 69,856
  • 92
  • 277
  • 407
  • 7
    Chuck Norris, one would expect you to roundhouse kick the problem and it would sort itself out. :-) – Huske May 04 '12 at 19:05
  • You've commented on all the answers other than mine - any feelings about it? If you're *really* keen on using doing this manually, I may be able to come up with some more ideas - but I still think using object/collection initializers is a more idiomatic approach. – Jon Skeet May 05 '12 at 06:37
  • @JonSkeet I want to know how to do the stuff that I showed at the top of my question, I know this is possible, you can see it here http://demos.telerik.com/aspnet-mvc/grid (view tab) – Omu May 05 '12 at 10:38
  • @ChuckNorris: But *why* would you want to avoid the idiomatic C# approach? I would find the code you've proposed significantly harder to understand than the collection/object initializer approach. What's the *benefit* of introducing lambda expressions here? Just because someone else's API uses an idea doesn't mean it's a *good* idea... – Jon Skeet May 05 '12 at 10:42
  • @JonSkeet one benefit is that you can make the config class generic and than have the Elements strongly typed for each property of T – Omu May 05 '12 at 14:22
  • @ChuckNorris: It's not really clear what you mean - it sounds like something which should be fine with a generic class for object/collection initializers... If you're still not satisfied with any of the answers so far, I can certainly write a class which will allow the exact code of your second version. It's not what I would want to use myself, but if you *really* want it... Would you like that as a separate answer, or as part of my existing one? (It feels like it would be better as a separate one to me, but it's your call.) – Jon Skeet May 05 '12 at 19:30
  • @JonSkeet Yes, Thank You, I want to know how to do this, but that doesn't mean that this is what I'm going to use (but that's what this question is about, knowing how to this exact thing). you can do an edit or separate, doesn't matter :) – Omu May 05 '12 at 20:17
  • @ChuckNorris: I've created a separate answer. But it's horrible... oh, and I still don't see how you'd want to make it generic, whereas the collection initializer version is *easily* made generic... – Jon Skeet May 05 '12 at 21:18

6 Answers6

5
public class Config
{
   private string name;
   private IList<Element> elements = new List<Element>();
   public IList<Element> GetElements {get {return this.elements;}}
   public Config Name(string name)
   {
      this.name = name;
      return this;
   }

   public Config Elements(IEnumerable<Element> list)
   {
        foreach ( var element in list)
            elements.Add(element);
        return this;
   }

   public Config Elements(params Element[] list)
   {
        foreach ( var element in list)
            elements.Add(element);
        return this;
   }

   public Config Elements(params Expression<Func<Element>>[] funcs)
   {
        foreach (var func in funcs )
            elements.Add(func.Compile()());
        return this;
   }

   public Config Elements(params Expression<Func<IEnumerable<Element>>>[] funcs)
   {
        foreach (var func in funcs )
            foreach ( var element in func.Compile()())
                elements.Add(element);
        return this;
   }

   public class Element
   {
      public string Name { get; set; }
      public int Height { get; set; }     
      public Element() {}
      public Element(string name)
      {
         this.Name = name;
      }  
      public Element AddHeight(int height)
      {
          this.Height = height;
          return this;
      }
      public static Element AddName(string name)
      {
        return new Element(name);
      }
   }
}

usage

var cfg = new Config()
    .Name("X")
    .Elements(new [] { new Config.Element { Name = "", Height = 0} })
    .Elements(
            Config.Element.AddName("1").AddHeight(1), 
            Config.Element.AddName("2").AddHeight(2) 
            )
    .Elements(
        () => Config.Element.AddName("1").AddHeight(1)
    )
    .Elements(
        () => new[] {
                Config.Element.AddName("1").AddHeight(1),
                Config.Element.AddName("1").AddHeight(1)
               }
    )
Adrian Iftode
  • 15,465
  • 4
  • 48
  • 73
5

Here's a version which works exactly as per your second code sample. It's really ugly though - I definitely wouldn't want to use it myself. Notes at the end.

using System;
using System.Collections.Generic;

public class Config
{
    private string name;
    private int foo;
    private IList<Element> elements = new List<Element>();

    public Config Name(string name)
    {
        this.name = name;
        return this;
    }

    public Config Foo(int x)
    {
        this.foo = x;
        return this;
    }

    public Config Elements(Action<ElementBuilder> builderAction)
    {
        ElementBuilder builder = new ElementBuilder(this);
        builderAction(builder);
        return this;
    }

    public class ElementBuilder
    {
        private readonly Config config;

        internal ElementBuilder(Config config)
        {
            this.config = config;
        }

        public ElementHeightBuilder Name(string name)
        {
            Element element = new Element { Name = name };
            config.elements.Add(element);
            return new ElementHeightBuilder(element);
        }
    }

    public class ElementHeightBuilder
    {
        private readonly Element element;

        internal ElementHeightBuilder(Element element)
        {
            this.element = element;
        }

        public void Height(int height)
        {
            element.Height = height;
        }
    }

    public class Element
    {
        public string Name { get; set; }
        public int Height { get; set; }
    }
}



class Test
{
    static void Main()
    {
        Config config = new Config();

        config.Name("Foo")
            .Elements(e => {
                e.Name("element1").Height(23);
                e.Name("element2").Height(31);
            })
            .Foo(3232);
    }
}

Notes:

With this code you have to call Name first, and then optionally call Height for each element - although nothing will complain if you fail to call Height. If you changed the Elements call to either this:

  .Elements(e => {
                     e.NewElement().Name("element1").Height(23);
                     e.NewElement().Name("element2").Height(31);
                 })

or this:

  .Elements(e => {
                     e.Name("element1").Height(23).AddToConfig();
                     e.Name("element2").Height(31).AddToConfig();
                 })

then you'd end up with a more flexible situation; you could have a single ElementBuilder class which would do the right thing. The first version of this is nicer IMO.

All of this is still vastly less pleasant than the simple and effective object/collection initializers shown in my other answer, which I would strongly urge you to use. I really so no benefit in this approach - if you hadn't seen in in the Telerik API, would you have naturally wanted this? It seems from other comments as if you're drawn to the "shininess" of using lambda expressions... don't be. They're great in the right setting, but it seems to me that there are far cleaner ways of achieving this without them.

I suggest you take a step back and work out whether you really gain anything from the syntax you originally wanted to use, and think about whether you'd rather maintain the kind of code in this answer, or the code in the object/collection initializer solution.

EDIT: Here's my interpretation of Zoltar's suggestion, which gets rid of the need for the extra class:

using System;
using System.Collections.Generic;

public class Config
{
    private string name;
    private int foo;
    private IList<Element> elements = new List<Element>();

    public Config Name(string name)
    {
        this.name = name;
        return this;
    }

    public Config Foo(int x)
    {
        this.foo = x;
        return this;
    }

    public Config Elements(Action<ElementBuilder> builderAction)
    {
        ElementBuilder builder = new ElementBuilder(this);
        builderAction(builder);
        return this;
    }

    public class ElementBuilder
    {
        private readonly Config config;
        private readonly Element element;

        // Constructor called from Elements...
        internal ElementBuilder(Config config)
        {
            this.config = config;
            this.element = null;
        }

        // Constructor called from each method below
        internal ElementBuilder(Element element)
        {
            this.config = null;
            this.element = element;
        }

        public ElementBuilder Name(string name)
        {
            return Mutate(e => e.Name = name);
        }

        public ElementBuilder Height(int height)
        {
            return Mutate(e => e.Height = height);
        }

        // Convenience method to avoid repeating the logic for each
        // property-setting method
        private ElementBuilder Mutate(Action<Element> mutation)
        {
            // First mutation call: create a new element, return
            // a new builder containing it.
            if (element == null)
            {
                Element newElement = new Element();
                config.elements.Add(newElement);
                mutation(newElement);
                return new ElementBuilder(newElement);
            }
            // Subsequent mutation: just mutate the element, return
            // the existing builder
            mutation(element);
            return this;
        }
    }

    public class Element
    {
        public string Name { get; set; }
        public int Height { get; set; }
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I guess you think that this is ugly because you didn't took advantage of the lambda, as I said you could make the Config generic (or maybe the Elements method), and after have the possibility to do an element for each property, e.g. `Elements ( els => {els.Add(o => o.Prop1).Height(32).Width(22); els => els.Add(o => o.Prop2).Color("red").SomeElse(232); }` – Omu May 05 '12 at 21:49
  • also I see you made an entire class just for setting the height property, this indeed is quite ugly, I'm sure that is not the way telerik does it, if I would need to add about 20 properties than it's gonna be quite a lot of code :) – Omu May 05 '12 at 21:55
  • @JonSkeet +1 for heroic effort, but agree the Telerik API sample isn't worth emulating. You could get rid of the need for two separate builders by having one class where every operation checks to make sure the item has already been added to the collection, but that seems even worse somehow. Chuck's comment above seems to indicate he might be OK with having an Add method called as the first method in the chain. – Bryn Keller May 06 '12 at 04:59
  • @ChuckNorris: That's changed the syntax hugely (nested lambdas now!) and it's not at all clear to me what it's even meant to achieve. You wouldn't need an extra class per property - it's only there because the first call on `e` works differently to all the others, because it adds a new element as well as setting a property. Basically you need `e` to be a different type to the type returned to `e.Name()`. That's why it's better if you have to start off with `e.Add()`, as I mentioned - but you said you wanted that *exact* syntax to work. – Jon Skeet May 06 '12 at 05:53
  • @Xoltar: I'm not sure how the "checking" could work - how is `e.Name(...).Height(); e.Name(...).Height();` going to be handled differently to `e.Name(..).Height(..).Name(..).Height(..)`? You'd potentially need to check whether the property had already been set on that element, which is *really* foul. Yuk, yuk, yuk. – Jon Skeet May 06 '12 at 05:54
  • @ChuckNorris: You say I "didn't took advantage of the lambda" - I made it work *exactly* as per your original syntax (second form). I didn't introduce any *nested* lambdas, that's true... partly because this is unreadable enough already, without making it worse... I still don't see why you're so desperate to know how to emulate such a nasty API, to be honest. – Jon Skeet May 06 '12 at 05:56
  • @JonSkeet sorry not what I meant - I meant you could define Height on ElementBuilder as well, and have it return another ElementBuilder, just like ElementBuilder.Name does. Both methods would have to check to see if the element field had been initialized yet, and if not, create it and add to the collection. Of course, that would mean you could call the same thing more than once, e.g. do e.Name(...).Height(...).Name(...), but that's probably not a concern for an API of this sort, no? – Bryn Keller May 06 '12 at 06:16
  • @Xoltar: I see, yes. Actually, I think I'll edit this answer to do that as an alternative... – Jon Skeet May 06 '12 at 06:27
  • @Xoltar: I've added it as a separate possibility. See if that's what you were thinking of. (Apologies if I've misread you.) – Jon Skeet May 06 '12 at 06:33
3

I'd rather go with following fluent interface:

Config config = new Config("Foo")
                        .WithElement("element1", 23)
                        .WithElement("element2");

I think it's more readable and compact. Implementation:

public class Config
{
    private string name;
    private IList<Element> elements = new List<Element>();

    public Config(string name)
    {
        this.name = name;
    }

    public Config WithElement(string name, int height = 0)
    {
        elements.Add(new Element() { Name = name, Height = height });
        return this;
    }

    class Element
    {
        public string Name { get; set; }
        public int Height { get; set; }
    }
}

If name is optional, then add Config constructor without parameters. Also consider optional parameters for WithElemnt method, if you don't need both height and name.

UPDATE: I changed height to be optional parameter to show how you can add elements with only name specified.

UPDATE (if you want to allow only one group of elements)

Config config = new List<Element>()
                    .AddElement(new Element {Name = "element1", Height = 23 })
                    .AddElement(new Element {Name = "element2" })
                    .WrapToConfig()
                    .Name("config1");

Implementation:

public static class ConfigurationHelper
{
    public static IList<Element> AddElement(this IList<Element> elements, Element element)
    {
        elements.Add(element);
        return elements;
    }

    public static Config WrapToConfig(this IList<Element> elements)
    {
        return Config(elements);
    }
}

But this is not very obvious for users, so I'd go with first simple fluent interface.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • 1
    Agree. In the end the purpose of a fluent interface is to be.. fluent. Look on what I achieved, is not fluent anymore. – Adrian Iftode Apr 27 '12 at 09:48
  • this actually was my first solution, the difference with this approach is that the elements will not be defined all together, the user can do something like this: config.WithElement("a").Name("x").WithElement("xxsa"), also you can't use a subfluent api like in @AdrianIftode 's answer – Omu Apr 27 '12 at 10:05
  • @ChuckNorris WithElement("a") already creates an element with the name "a". The user doesn't need to use Name("x") – Adrian Iftode Apr 27 '12 at 10:07
  • @AdrianIftode Name is for the Config object not for the the Element – Omu Apr 27 '12 at 10:15
  • @ChuckNorris why not to pass Name as constructor parameter for Config? And what is wrong with Name between WithElement - both are part of Config object setup. It's up to programmer in which order he will place those parts of setup. Why would somebody set name between elements? – Sergey Berezovskiy Apr 27 '12 at 10:21
  • @ChuckNorris btw I can use your Elements method several times with several Name methods between. Its just question of good coding style. – Sergey Berezovskiy Apr 27 '12 at 10:24
  • @lazyberesovsky I posted a simple example, the real thing will have much more properties, and I don't want to use constructors with tens of parameters – Omu Apr 27 '12 at 10:28
  • @ChuckNorris again, these fluent steps are independent. So, you will be able to do `config.Elements(() => Config.Element.Name("element1").Height(23)).Name("config1").Elements(() => Config.Element.Name("element2"))` what you get is only API which is hard to read and understand. – Sergey Berezovskiy Apr 27 '12 at 10:32
  • no it isn't, look at the beginning of my questions, the elements are defined all together in a group Elements, which is much more consistent than being able to put an element at the first line, after define some other properties and after again some element – Omu Apr 27 '12 at 12:48
  • @ChuckNorris see my last example - anyone can call `Elements` method many times, and anyone can call other methods like `Name` between calls to `Elements`. – Sergey Berezovskiy Apr 27 '12 at 12:55
  • I see your point, even if you group them, you still can call the group thing multiple times, but this is what want to know how to do, the group thing – Omu Apr 27 '12 at 13:05
  • @ChuckNorris exactly. I'd use `WithElement` and didn't bother with possible `Name` call between. Because there is nothing wrong with it – Sergey Berezovskiy Apr 27 '12 at 13:18
  • it's not about choosing what to use, I just want to know how this is done – Omu Apr 27 '12 at 13:21
  • @ChuckNorris this cannot be done with fluent interface - you return same object, which you used when invoked method. Thus you will be at the same point again. Only way is to change type which you are return. Hold on, I'll write an example. – Sergey Berezovskiy Apr 27 '12 at 13:29
2

Any reason you don't want to use object and collection initializers?

public class Config
{
   public string Name { get; set; }
   public int Foo { get; set; }
   public IList<Element> Elements { get; private set; }

   public Config()
   {
       Elements = new List<Element>();
   }
}

// I'm assuming an element *always* needs a name and a height
class Element
{
   public string Name { get; private set; }
   public int Height { get; private set; }

   public Element(string name, int height)
   {
       this.Name = name;
       this.Height = height;
   }
}

Then:

var config = new Config
{
    Name = "Foo",
    Elements = { 
        new Element("element1", 23),
        new Element("element2", 31)
    },
    Foo = 23
};

If you don't want to expose the list of elements directly, you could always turn that into a builder, and copy it into a more private data structure on Build:

var config = new Config.Builder
{
    Name = "Foo",
    Elements = { 
        new Element("element1", 23),
        new Element("element2", 31)
    },
    Foo = 23
}.Build();

This has the additional advantage that you can make Config itself immutable.

If you always need Name to be present, just take that as a constructor parameter instead.

While there are times where it's good to have a fluent interface with mutating (or copy-and-change) method calls, in this case I think collection/object initializers are more idiomatic C#.

Note that if you're using C# 4 and you want to make your Element constructor calls, you can always use named arguments:

new Element(name: "element2", height: 31)
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

Use data builder pattern. Nice thing about that is that it separates the fluent build api from the data objects. Of course you can omit "with" in your convention.

Usage:

var aConfig = new ConfigBuilder();

// create config fluently with lambdas
Config config = aConfig.WithName("Foo")
        .WithElement(e => e.WithName("element1").WithHeight(23))
        .WithElement(e => e.WithName("element2").WithHeight(31))
    .WithFoo(3232)
    .Build();

// create elements in one go
config = aConfig.WithName("Foo")
         .WithElements(
             e => e.WithName("element1").WithHeight(23), 
             e => e.WithName("element2").WithHeight(31))
     .WithFoo(3232)
     .Build();


var anElement = new ElementBuilder();

// or with builders 
config = aConfig.WithName("Foo")
        .WithElement(anElement.WithName("element1").WithHeight(23))
        .WithElement(anElement.WithName("element2").WithHeight(31))
    .WithFoo(3232)
    .Build();

// use builders to reuse configuration code
anElement.WithHeigh(100);

config = aConfig.WithName("Bar")
        .WithElement(anElement.WithName("sameheight1"))
        .WithElement(anElement.WithName("sameheight2"))
    .WithFoo(5544)
    .Build();

Implementation:

public class ConfigBuilder
{
    private string name;
    private int foo;
    private List<Element> elements = new List<Element>();

    public ConfigBuilder WithName(string name)
    {
         this.name = name;
         return this;
    }

    public ConfigBuilder WithFoo(int foo)
    {
        this.foo = foo;
        return this;
    }

    public ConfigBuilder WithElement(Element element)
    {
        elements.Add(element);
        return this;
    }

    public ConfigBuilder WithElement(ElementBuilder element)
    {
        return WithElement(element.Build());
    }

    public ConfigBuilder WithElement(Action<ElementBuilder> builderConfig)
    {
         var elementBuilder = new ElementBuilder();
         builderConfig(elementBuilder);
         return this.WithElement(elementBuilder);
    }

    public ConfigBuilder WithElements(params Action<ElementBuilder>[] builderConfigs)
    {
         foreach(var config in builderConfigs)
         {
              this.WithElement(config);
         }

         return this;
    }

    public Config Build()
    {
         return new Config() 
         { 
             Name = this.name,
             Foo = this.foo,
             Elements = this.elements
         };
    }
}

public class ElementBuilder
{
    private string name;
    private int height;

    public ElementBuilder WithName(string name)
    {
        this.name = name;
        return this;
    }

    public ElementBuilder WithHeight(int height)
    {
        this.height = height;
        return this;
    }

    public Element Build()
    {
        return new Element() 
        { 
            Name = this.name,
            Height = this.height
        };
    }
}

public class Config
{
    public string Name { get; set; }
    public int Foo { get; set; }
    public IList<Element> Elements { get; set; }
}

public class Element
{
    public string Name { get; set; }
    public int Height { get; set; }
}
Peter Zajic
  • 899
  • 8
  • 17
1

Here's method 1 to place inside Config -- "one at a time":

public Config Element(Action<Element> a) {
    Element e = new Element();
    a(e);
    this.elements.Add(e);
    return this;
}

And here's how to use it:

config.Name("Foo")
    .Element(e => e.Name("element1").Height(23))
    .Element(e => e.Name("element2").Height(31))
    .Foo(3232);

Here's method 2 -- "the list":

public Config Elements(Func<List<Element>> a) {
    List<Element> elements = a();
    foreach (Element e in elements) {
        this.elements.Add(e);
    }
    return this;
}

And here's how to use it:

config.Name("Foo")
    .Elements(() => new List<Element>() {
            new Element().Name("element1").Height(23),
            new Element().Name("element2").Height(31)
        })
    .Foo(3232);

Note that it presumes Element is not nested inside Config (or you'd need new Config.Element() in example 2).

Note that in your "the list" example, you've passed in a single Element object, but you're trying to set it twice. The second line will alter the Element, not create a new one.:

.Elements(e => {
                 e.Name("element1").Height(23); // <-- You set it
                 e.Name("element2").Height(31); // <-- You change it
              })
.Foo(3232);

Thus this syntax can't work.

How it works:

Func<T,U,...> is an anonymous function delegate that takes in all the parameters but one, and returns the last one. Action<T,U,...> is an anonymous function delegate that takes in all parameters. For example:

Func<int,string> f = i => i.ToString(); says "take in an int, return a string".

Action<int> f = i => string c = i.ToString(); says "take in an int, return nothing".

robrich
  • 13,017
  • 7
  • 36
  • 63
  • "you've passed in a single Element object, but you're trying to set it twice" - not necessarily, that's why in the first example I'm using () => , but in second e could mean some builder thing – Omu May 03 '12 at 22:44
  • If it was some builder, you'd need something like e.New().Name(..).Height(..) to denote the context change from "builder" to "instance" or else you'd need .Name(..) to have the magic side-effect of creating a new one. – robrich May 03 '12 at 22:49