Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulating method cascades in C#

The Dart programming language has support for method cascades. Method cascades would allow the following Silverlight/WPF C# code:

var listBox = new ListBox();

listBox.Width = 200;
listBox.MouseEnter += (s, e) => Console.WriteLine("MouseEnter");

var button1 = new Button() { Content = "abc" };
button1.Click += (s, e) => Console.WriteLine("button1.Click");

listBox.Items.Add(button1);

var button2 = new Button() { Content = "def" };
button2.Click += (s, e) => Console.WriteLine("button2.Click");

listBox.Items.Add(button2);

ContentPanel.Children.Add(listBox);

to be written instead as:

ContentPanel.Children.Add(
    new ListBox()
        ..Width = 200
        ..MouseEnter += ((s, e) => Console.WriteLine("MouseEnter"))
        ..Items.Add(
            new Button()
                ..Content = "abc";
                ..Click += ((s, e) => Console.WriteLine("button 1 Click")))
        ..Items.Add(
            new Button()
                ..Content = "def";
                ..Click += (s, e) => (Console.WriteLine("button 2 Click"))));

My question is, is there a way to simulate or closely approximate method cascades in C#?

Here's one approach I came up with. Given this extension method:

public static T Call<T>(this T obj, Action<T> proc)
{
    proc(obj);

    return obj;
}

the above example can be written as follows:

ContentPanel.Children.Add(
    new ListBox().Call(o => {
            o.Width = 200;
            o.MouseEnter += (s, e) => Console.WriteLine("MouseEnter");
            o.Items.Add(
                new Button().Call(b => {
                        b.Content = "abc";
                        b.Click += (s, e) => Console.WriteLine("button 1 Click"); }));
            o.Items.Add(
                new Button().Call(b => {
                    b.Content = "def";
                    b.Click += (s, e) => Console.WriteLine("button 2 Click"); })); }));

I wouldn't argue that that's pretty. :-) But it does essentially enable a fluent style to be applied.

like image 818
dharmatech Avatar asked Jan 09 '13 04:01

dharmatech


1 Answers

I think you can reach close to what you want to achieve by using fluent interface. It will allow you to chain methods to create and initialize objects in one statement.

You can get something like that:

Fluent fluent = new Fluent();
var panel = fluent.CreateControlPanel().Children()
                .AddListBox().SetWidth(200).AddMouseEnterEvent((s, e) => { }).Create()
                .AddTextBox().SetText("Foo").Create()
                .GetControlPanel();

The idea is that a method returns an object allowing to initialize another object. A chain of initializer can call at any item a "finalizer" method (above Create) that returns the original object (above Children) to continue to add other objects or configure the initial one.

So for example in AddListBox returns an object of type ListBoxSetup which has a bunch of methods like SetWidth or AddMouseEnterEvent. In this case Children will also be a special object (like of type ChildSetup) which has a bunch of methods such as AddListBox or AddTextBox. Each of the method has in charge to create an object of type ListBox or TextBox or setup properties of the underlying object to be created. Fluent will have a method that returns your whole object structure correctly setup.

Take a look to this link: http://blog.raffaeu.com/archive/2010/06/26/how-to-write-fluent-interface-with-c-and-lambda.aspx

Here's an example of the underlying code create to end up with the above. Of course the code could be greatly improved in its architecture but it's here just for the sake of the example.

public class Fluent
{
    public ControlPanelCreator CreateControlPanel()
    {
        return new ControlPanelCreator(new StackPanel(), this);
    }
}

public class ControlPanelCreator
{
    #region Fields
    private Fluent fluent;
    private Panel panel;
    #endregion

    #region Constructors
    internal ControlPanelCreator(Panel panel, Fluent fluent)
    {
        this.fluent = fluent;
        this.panel = panel;
    }
    #endregion

    #region Methods
    public ControlPanelChildrenCreator Children()
    {
        return new ControlPanelChildrenCreator(this.panel, this);
    }
    #endregion
}

public class ControlPanelChildrenCreator
{
    #region Fields
    private ControlPanelCreator panelCreator;
    private Panel panel;
    #endregion

    #region Constructors
    internal ControlPanelChildrenCreator(Panel panel, ControlPanelCreator panelCreator)
    {
        this.panel = panel;
        this.panelCreator = panelCreator;
    }
    #endregion

    #region Methods
    public ListBoxCreator AddListBox()
    {
        ListBox listBox = new ListBox();
        this.panel.Children.Add(listBox);
        return new ListBoxCreator(listBox, this);
    }

    public TextBoxCreator AddTextBox()
    {
        TextBox textBox = new TextBox();
        this.panel.Children.Add(textBox);
        return new TextBoxCreator(textBox, this);
    }

    public Panel GetControlPanel()
    {
        return this.panel;
    }
    #endregion
}

public class ListBoxCreator
{
    #region Fields
    private ListBox listbox;
    private ControlPanelChildrenCreator parentCreator;
    #endregion

    #region Constructors
    internal ListBoxCreator(ListBox listBox, ControlPanelChildrenCreator parentCreator)
    {
        this.listbox = listBox;
        this.parentCreator = parentCreator;
    }
    #endregion

    #region Methods
    public ListBoxCreator SetWidth(int width)
    {
        this.listbox.Width = width;
        return this;
    }

    public ListBoxCreator AddMouseEnterEvent(Action<object, MouseEventArgs> action)
    {
        this.listbox.MouseEnter += new MouseEventHandler(action);
        return this;
    }

    public ControlPanelChildrenCreator Create()
    {
        return this.parentCreator;
    }
    #endregion
}

public class TextBoxCreator
{
    #region Fields
    private TextBox textBox;
    private ControlPanelChildrenCreator parentCreator;
    #endregion

    #region Constructors
    internal TextBoxCreator(TextBox textBox, ControlPanelChildrenCreator parentCreator)
    {
        this.textBox = textBox;
        this.parentCreator = parentCreator;
    }
    #endregion

    #region Methods
    public TextBoxCreator SetText(string defaultText)
    {
        this.textBox.Text = defaultText;
        return this;
    }

    public ControlPanelChildrenCreator Create()
    {
        return this.parentCreator;
    }
    #endregion
}
like image 168
Guillaume Avatar answered Oct 13 '22 02:10

Guillaume