Writing my first DSL in C # and hanging on func <T> & Action
I am trying to write my first DSL for a simple tool at work. I use the builder pattern to set a complex parent object, but I run into brick walls to create child collections of the parent object. Here's a sample:
Using:
var myMorningCoffee = Coffee.Make.WithCream().WithOuncesToServe(16); Closing example (I think they are called):
var myMorningCoffee = Coffee.Make.WithCream().PourIn( x => { x.ShotOfExpresso.AtTemperature(100); x.ShotOfExpresso.AtTemperature(100).OfPremiumType(); } ).WithOuncesToServe(16); Class example (without the child PourIn () method, as that is what I'm trying to figure out.)
public class Coffee { private bool _cream; public Coffee Make { get new Coffee(); } public Coffee WithCream() { _cream = true; return this; } public Coffee WithOuncesToServe(int ounces) { _ounces = ounces; return this; } } So, in my application for working, my complex construction of objects is just fine, but I canβt understand for life how to get a lambda encoded for a subcategory on the parent object. (in this example, these are shots (children's collection) expresso).
Perhaps I misled the concepts here, and I do not mind being straightforward; Nevertheless, I really like how it reads and would like to find out how to do it.
Thanks Sam
Ok, so I figured out how to write my DSL using an optional expression constructor. This is how I wanted my DSL to read:
var myPreferredCoffeeFromStarbucks = Coffee.Make.WithCream().PourIn( x => { x.ShotOfExpresso().AtTemperature(100); x.ShotOfExpresso().AtTemperature(100).OfPremiumType(); } ).ACupSizeInOunces(16); Here is my passing test:
[TestFixture] public class CoffeeTests { [Test] public void Can_Create_A_Caramel_Macchiato() { var myPreferredCoffeeFromStarbucks = Coffee.Make.WithCream().PourIn( x => { x.ShotOfExpresso().AtTemperature(100); x.ShotOfExpresso().AtTemperature(100).OfPremiumType(); } ).ACupSizeInOunces(16); Assert.IsTrue(myPreferredCoffeeFromStarbucks.expressoExpressions[0].ExpressoShots.Count == 2); Assert.IsTrue(myPreferredCoffeeFromStarbucks.expressoExpressions[0].ExpressoShots.Dequeue().IsOfPremiumType == true); Assert.IsTrue(myPreferredCoffeeFromStarbucks.expressoExpressions[0].ExpressoShots.Dequeue().IsOfPremiumType == false); Assert.IsTrue(myPreferredCoffeeFromStarbucks.CupSizeInOunces.Equals(16)); } } And here is my DSL class for CoffeeExpressionBuilder:
public class Coffee { public List<ExpressoExpressionBuilder> expressoExpressions { get; private set; } public bool HasCream { get; private set; } public int CupSizeInOunces { get; private set; } public static Coffee Make { get { var coffee = new Coffee { expressoExpressions = new List<ExpressoExpressionBuilder>() }; return coffee; } } public Coffee WithCream() { HasCream = true; return this; } public Coffee ACupSizeInOunces(int ounces) { CupSizeInOunces = ounces; return this; } public Coffee PourIn(Action<ExpressoExpressionBuilder> action) { var expression = new ExpressoExpressionBuilder(); action.Invoke(expression); expressoExpressions.Add(expression); return this; } } public class ExpressoExpressionBuilder { public readonly Queue<ExpressoExpression> ExpressoShots = new Queue<ExpressoExpression>(); public ExpressoExpressionBuilder ShotOfExpresso() { var shot = new ExpressoExpression(); ExpressoShots.Enqueue(shot); return this; } public ExpressoExpressionBuilder AtTemperature(int temp) { var recentlyAddedShot = ExpressoShots.Peek(); recentlyAddedShot.Temperature = temp; return this; } public ExpressoExpressionBuilder OfPremiumType() { var recentlyAddedShot = ExpressoShots.Peek(); recentlyAddedShot.IsOfPremiumType = true; return this; } } public class ExpressoExpression { public int Temperature { get; set; } public bool IsOfPremiumType { get; set; } public ExpressoExpression() { Temperature = 0; IsOfPremiumType = false; } } Any suggestions are welcome.
What if .IncludeApps accepted an array of AppRegistrations
IncludeApps(params IAppRegistration[] apps) then
public static class App { public static IAppRegistration IncludeAppFor(AppType type) { return new AppRegistration(type); } } public class AppRegistration { private AppType _type; private bool _cost; public AppRegistration(AppType type) { _type = type; } public AppRegistration AtNoCost() { _cost = 0; return this; } } so in the end it will look like this ...
.IncludeApps ( App.IncludeAppFor(AppType.Any), App.IncludeAppFor(AppType.Any).AtNoCost() ) Inside the IncludeApps method, you must check the registration and create objects as needed.
To go to the delegate route, maybe something like this will work?
var aPhone = MyPhone.Create; MyPhone.Create.IncludeApps ( x => { x.IncludeAppFor(new object()); } ); class MyPhone { public MyPhone IncludeApps(Action<MyPhone> includeCommand) { includeCommand.Invoke(this); return this; } } If you are not configured on the delegate route, maybe the options will work?
var anotherPhone = MyPhone.Create.IncludeApps( new IncludeAppClass(AppType.Math), new IncludeAppClass(AppType.Entertainment).AtNoCost()); class MyPhone { internal MyPhone IncludeApps(params IncludeAppClass[] includeThese) { if (includeThese == null) { return this; } foreach (var item in includeThese) { this.Apps.Add(Item); } return this; } }