Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Domain Specific Language using C# 4.0 - Part 3

0.00/5 (No votes)
30 Dec 2010 1  
Explains how to define internal DSL using C# language constructs

Previous Parts

In the last part of this article, we provided a domain-friendly API to create a new exam but left the part to provide a readable way for adding "n" of questions and their corresponding options.

Level 2: Method Chaining & Fluent API

After creating the exam instance, what would be the feasible way for the user to add questions. First, let us see the current API to add questions with the exam() method in ExamBuilder:

ExamBuilder.exam(".NET Fundamentals").AddQuestion(question);

How many questions we can add by this way? Is it domain friendly? "Only one" is the answer for first question. "Yes this is definitely not domain friendlier" is the answer for the second question. The immediate requirement for the above problem is that if AddQuestion() returns the current exam object, then we can call AddQuestion() again and again for every question as long as we need for this exam. Instead of modifying "AddQuestion()", I introduce a method "question()" in the Exam class as:

//Exam.cs
public Exam question(Question question)
{
	AddQuestion(question);
	return this;
}

We have just created a fluent API "question()" for this exam DSL. Fluent means "readable", by using "method chaining". Method chaining is a technique to call one or more methods in the same or different objects in a relay race fashion which produces a domain friendly definition. In the question() method, it just calls AddQuestion() method followed by returning the current instance of Exam object.

This will allow to add more than one question like:

//User Mindset: Space1 for defining questions
Question q1 = new Question("Expansion of CLR");
q1.AddOption("A", "Common Language Runtime");
q1.AddOption("B", "Common LINQ Runtime");
q1.AddOption("C", "C# Language Runtime");
q1.AddOption("D", "C Language Runtime");
q1.AddAnswers(new string[] { "A" });

Question q2 = new Question("Expansion of CTS");
q2.AddOption("A", "C# Type System");
q2.AddOption("B", "Common Type System");
q2.AddOption("C", "Compiler Test Symbols");
q2.AddOption("D", "CLR Tolerate Service");
q2.AddAnswers(new string[] { "A" });            

//User Mindset: Space2 for adding above questions into Exam object
ExamBuilder.exam(title: ".NET Fundamentals")
	.question(q1)
	.question(q2);

However, we have touched the slice of the cake. This is the time to refactor Question object instantiation and its AddOption() method. Currently, the instantiation itself creates annoyance to the user and also he will get a feel of having two different spaces one for defining questions and another one for adding them into Exam object.

Let us introduce another version of question().

Level 3: Collections

//Exam.cs
private static string[] opts = new string[] { "A", "B", "C", "D" };

public Exam question(string description, IList<string> options)
{
	Question q = new Question(description);
	int i = 0;
	
	foreach (string option in options)
	{
		q.AddOption(opts[i++], option);
	}

	return this;
}
</string>

Instead of asking Question object as parameter, this method asks question description as first argument followed by collection of string for options. Internally, I assign option keys "A", "B", "C", "D" sequentially using "opts" field. The consumption of this version would be:

ExamBuilder.exam(title: ".NET Fundamentals")
	.question("Expansion of IL", 
		new List<string> {"Indian Language", "Intermediate Language"}
	);
</string>

But, you will definitely hate to see "new List". What else we can do? Let us try "params" modifier.

public Exam question(string description, params string[] options)

//Usage
.question("Expansion of IL", "Indian Language", "Intermediate Language")

Still, this is not domain friendly. How can user know all the parameters after the first parameter are options. Syntactically, you cannot stop the user after four options.

Shall we try to use Func<> or Action<> delegates? See the below code and its corresponding consumption.

Level 4: Delegates

//Exam.cs
public Exam question(string desc, Action<question> options)
{
	Question q = new Question(desc);
	options(q);
	AddQuestion(q);
	return this;
}

//Usage
.question("Which one of the following is loaded during a new .NET process creation?", 
			q =>
			{
				q.AddOption("A", "ILAsm.exe");
				q.AddOption("B", "ILDasm.exe");
				q.AddOption("C", "MSCorEE.dll");
				q.AddOption("D", "MSVCVM.dll");
			}
		)
</question>

The above code seems quite domain friendly. Let us further refactor the Question's AddOption() method to make this as more readable by introducing a fluent version. The definition and consumption would be:

//Solution 1 - inclusive of Exam, Question ctor, methods used
in the usage section below

//Question.cs
public Question a(string option)
{
	AddOption("A", option);
	return this;
}

public Question b(string option)
{
	AddOption("B", option);
	return this;
}

public Question c(string option)
{
	AddOption("C", option);
	return this;
}

public Question d(string option)
{
	AddOption("D", option);
	return this;
}

//Usage
ExamBuilder.exam(title: ".NET Fundamentals")
	.question("Expansion of CLR", q=>
		{
			q.a("Common Language Runtime")
			 .b("Common LINQ Runtime")
			 .c("C# Language Runtime")
			 .d("C Language Runtime");
		})
	.question("Expansion of CTS", q =>
		{
			q.a("C# Type System")
			 .b("Common Type System")
			 .c("Compiler Test Symbols")
			 .d("CLR Tolerate Service");
		})
	.question("Which one of the following is loaded during a 
				new .NET process creation?", q =>
		{
			q.a("ILAsm.exe")
			 .b("ILDasm.exe")
			 .c("MSCorEE.dll")
			 .d("MSVCVM.dll");
		}
	);

Since every question should have four options, I've defined chain of "a(), b(), c(), d()" methods to add options. I've added a comment saying that "Solution 1" in the code means that this would be one of the acceptable versions of exam DSL.

Only problem with this solution is the "q=>" followed by "q.a()...." which makes little annoyance. Can we tweak this part or would any other complete alternate be more readable? Let us see.

To be continued...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here