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.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With