Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Designing With Lambdas - Part II

In my last post I went through a very simple example of applying lambdas to achieve more DRY.

In this installment I'll cheat a little and rehash a previous article I wrote before this blog existed. The article fits rather nicely in this series.

Creating XML with .Net

In .Net two of the most popular ways of creating XML are the System.Xml.XmlDocument, which implements the XML DOM, and System.Xml.XmlTextWriter. There's a new interesting way in VB9 using Xml Literals, but it is hardly popular at the time of this writing.

These APIs are obviously old-timers in .Net and were created before lambdas were available. For the sake of comparison, let's see how we would write the following XML document using these two APIs.

<?xml version="1.0" encoding="utf-8"?>
<children>
    <!--Children below...-->
    <child age="1" referenceNumber="ref-1">child &amp; content #1</child>

    <child age="2" referenceNumber="ref-2">child &amp; content #2</child>
    <child age="3" referenceNumber="ref-3">child &amp; content #3</child>
    <child age="4" referenceNumber="ref-4">child &amp; content #4</child>

    <child age="5" referenceNumber="ref-5">child &amp; content #5</child>
    <child age="6" referenceNumber="ref-6">child &amp; content #6</child>
    <child age="7" referenceNumber="ref-7">child &amp; content #7</child>

    <child age="8" referenceNumber="ref-8">child &amp; content #8</child>
    <child age="9" referenceNumber="ref-9">child &amp; content #9</child>
</children>

With the good ol' DOM, this document could be produced using something like this.

XmlDocument xml = new XmlDocument();
XmlElement root = xml.CreateElement("children");
xml.AppendChild(root);

XmlComment comment = xml.CreateComment("Children below...");
root.AppendChild(comment);

for(int i = 1; i < 10; i++)
{
	XmlElement child = xml.CreateElement("child");
	child.SetAttribute("age", i.ToString());
	child.SetAttribute("referenceNumber", "ref-" + i);
	child.InnerText = "child & content #" + i;
	root.AppendChild(child);
}

string s = xml.OuterXml;

Nothing too dramatic here. But my argument is that the only thing the DOM API has going for it is its ubiquitousness, which is not a minor feat considering how clunky the API is. Look at all those set this and append that. Can you still remember when you were first learning the DOM and never remembering how attributes were set?

Now it's the XmlTextWriter's turn. Here's the code to write the same XML document.

StringWriter sw = new StringWriter();
XmlTextWriter wr = new XmlTextWriter(sw);

wr.WriteStartDocument();
wr.WriteComment("Children below...");
wr.WriteStartElement("children");

for(int i=1; i<10; i++)
{
	wr.WriteStartElement("child");
	wr.WriteAttributeString("age", i.ToString());
	wr.WriteAttributeString("referenceNumber", "ref-" + i);
	wr.WriteString("child & content #" + i);
	wr.WriteEndElement();
}

wr.WriteEndElement();
wr.WriteEndDocument();


wr.Flush();
wr.Close();
string s = sw.ToString();

The XmlTextWriter API is rather efficient but, golly, is it a b!tch to use. No kidding, folks. Miss one of those WriteEndXXXXXX and you're toast. Good luck in your debugging session.

But enough of bashing our favorite APIs. The point here is just to show a draft of what an API like this could be designed in the era of lambdas.

XmlBuilder - let the lambdas in

What if we could somehow wrap the XmlTextWriter in a way that we could never forget to close an element? Remember how we wrapped the code in FileUtil.EachLine in the first installment of this series? We wrote that method in such a way that the file will never be left open by accident. I think we could do the same with the XmlTextWriter API.

Take a moment to inspect the following code. Put yourself in the shoes of a developer that is trying to write XML for the first time and needs to choose an XML API.

string s = XmlBuilder.Build(xml =>
{
	xml.Root(children =>
	{
		children.Comment("Children below...");

		for(int i = 1; i < 10; i++)
		{
			children.Element(child =>
			{
				child["age"] = i.ToString();
				child["referenceNumber"] = "ref-" + i;
				child.AppendText("child & content #" + i);
			});
		}
	});
});

Did you notice how the code structure maps nicely to the XML document structure? See how there's no way for you to forget one of those AppendChild calls from the DOM or WriteEndElement from the XmlTextWriter?

I particularly like the way the attributes are defined using the indexer syntax. Do you see how I chose to format the lambdas so that they look like C# control blocks? Placing the opening brace of the lambda in the next line created this indented block of code that defines some form of context. The context in this case is "inside this block I'll be building one XML element. When the block ends, the element ends."

You can download the code and play with it. It's only a proof of concept and there's a lot of missing functionality that I hope to implement one day, probably when I decide to use it in some real project.

Explanation of the code

Below you can see an excerpt from the code, showing how the Element() method was implemented. Let's discuss it.

public virtual void Element(Action<XmlElementBuilder> build)
{
	string name = build.Method.GetParameters()[0].Name;
	Element(name, new Dictionary<string, string>(), build);
}

public virtual void Element(string localName, 
			Action<XmlElementBuilder> build)
{
	Element(localName, new Dictionary<string, string>(), build);
}

public virtual void Element(string localName, 
			IDictionary<string, string> attributes, 
			Action<XmlElementBuilder> build)
{
	XmlElementBuilder child = new XmlElementBuilder(localName, Writer);
	
	Writer.WriteStartElement(localName);
	child._tagStarted = true;

	foreach(var att in attributes)
		child[att.Key] = att.Value;

	build(child);// <-- element content is generated here
	Writer.WriteEndElement();
	_contentAdded = true;
}

Looking at the various overload of this method we can see how the lambda comes into play and also at least one more trick. The very first method reflects into the given delegate (the lambda) to determine the name that was used for the single parameter of Action<XmlElementBuilder>. That's how we did not need to specify the children and child node names. Of course this is not always desirable or possible because the naming rules or XML elements is different than C# identifiers, so the other overloads let us specify the node name.

In the last overload of Element() is where the real code is. Line #19 Writer.WriteStartElement(localName); opens the element, line #25 build(child); invokes the lambda passing a builder instance for what goes inside the element. Line #26 Writer.WriteEndElement(); makes sure we keep synch with the element we started in line #19, by ending it before the method exits.

For easier reference I'm including the code for XmlElementBuilder and its base class.

public class XmlElementBuilder : XmlBuilderBase
{
	internal XmlElementBuilder(string localName, XmlTextWriter writer)
		: base(writer)
	{
		Name = localName;
	}

	public string Name { get; protected set; }

	public void AppendText(string text)
	{
		Writer.WriteString(text);
	}
}
public abstract class XmlBuilderBase
{
	protected XmlBuilderBase(XmlTextWriter writer)
	{
		Writer = writer;
	}

	internal XmlTextWriter Writer { get; set; }
	private bool _contentAdded = false;
	private bool _tagStarted = false;

	public virtual void Comment(string comment)
	{
		Writer.WriteComment(comment);
		_contentAdded = true;
	}

	public virtual void Element(Action<XmlElementBuilder> build)
	{
		string name = build.Method.GetParameters()[0].Name;
		Element(name, new Dictionary<string, string>(), build);
	}

	public virtual void Element(string localName, Action<XmlElementBuilder> build)
	{
		Element(localName, new Dictionary<string, string>(), build);
	}

	public virtual void Element(string localName, IDictionary<string, string> attributes, Action<XmlElementBuilder> build)
	{
		XmlElementBuilder child = new XmlElementBuilder(localName, Writer);
		
		Writer.WriteStartElement(localName);
		child._tagStarted = true;

		foreach(var att in attributes)
			child[att.Key] = att.Value;

		build(child);// <-- element content is generated here
		Writer.WriteEndElement();
		_contentAdded = true;
	}

	Dictionary<string, string> _attributes = new Dictionary<string, string>();
	
	public string this[string attributeName] 
	{
		get
		{
			if(_attributes.ContainsKey(attributeName))
				return _attributes[attributeName];
			return null;
		}
		set
		{
			if(_contentAdded)
				throw new InvalidOperationException(
					"Cannot add attributes after" + 
					" content has been added to the element.");

			_attributes[attributeName] = value;

			if(_tagStarted)
				Writer.WriteAttributeString(attributeName, value);
		}
	}
}

I realize the code I'm providing here is not a complete solution for all XML creation needs, but that's also not the point of this series. The idea here is to explore ways to incorporate lambdas in the API. When you think about it, this design has been possible all along via delegates since .Net 1.0. Anonymous delegates made this a much, much better. But only with the expressive lambda syntax we are seeing an explosion of this type of delegate usage.


Posted 04-14-2008 8:47 PM by sergiopereira

[Advertisement]

Comments

Zdeslav Vojkovic wrote re: Designing With Lambdas - Part II
on 04-15-2008 3:24 AM

I believe that when it comes to building XML, you can't beat templating engines (e.g. NVelocity or something similar). Additionally, you can let non-developers (but not completely technically-challenged :) edit templates, while I would be very afraid to let them touch code (especially with lambdas)

sergiopereira wrote re: Designing With Lambdas - Part II
on 04-15-2008 8:54 AM

@Zdeslav, you make a good point. I'm not trying to force any other approach into retirement. I'm just showing what our XML API could be if we had lambdas in .Net from the very start.

I also believe that the lambda syntax can be learned without much effort and become common place. The lambda syntax can even lead you to forms of DSLs that are easier to edit than XML itself.

Steve wrote re: Designing With Lambdas - Part II
on 04-15-2008 9:05 AM

Why not just use LINQ to XML?

var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

new XElement("children",

from number in numbers

select new XElement("child",

new XAttribute("age", number),

new XAttribute("referenceNumber", "ref-" + number.ToString()),

"child & content #" + number.ToString()

)

);

Steve wrote re: Designing With Lambdas - Part II
on 04-15-2008 9:18 AM
Formatting got pretty shot on that comment, for a nicely formatted version checkout: http://pastie.caboo.se/181027
sergiopereira wrote re: Designing With Lambdas - Part II
on 04-15-2008 9:47 AM

@Steve, absolutely. That actually proves part of the point I'm trying to make. Linq to XML uses lambdas heavily though all the extension methods added to IEnumerable and IQueryable. The select statement you show above is nothing more than that. But again, LINQ is a new feature that did not exist before .Net 3.5, so it's part of a more modern API, precisely what I'm trying to create in this series.

By the way, try to create an XML doc that is a little more complex and nested than my sample and you'll see your Linq to XML get pretty hairy.

Steve wrote re: Designing With Lambdas - Part II
on 04-15-2008 10:45 AM

Sergio,

The XML API for creating XML doesn't use lambdas.  I think you are confusing the querying capabilities of LINQ to XML with the XML manipulation capabilities.  

In LINQ in Action we create some reasonable complex XML and I found LINQ to XML to be up to the task.  I guess my point is that I you are trying to create a better API for working with XML you should build it on top of LINQ to XML.  If the goal is to create an XML API to illustrate an example API that uses lamda's then that's a different story.

sergiopereira wrote re: Designing With Lambdas - Part II
on 04-15-2008 11:27 AM

@Steve

"If the goal is to create an XML API to illustrate an example API that uses lambda's then that's a different story."

That's the whole point of the series. I think I repeated that a dozen time already :) The problem is that people jump on the examples instead of the underlying subject.

Again, I'm not proposing that we discard all the existing APIs and rewrite them using lambdas for the sake of using lambdas. I hope that by making lambdas more common in the APIs we start to see more creative and expressive APIs, like LINQ.

Also, why do you say your sample doesn't use lambdas? I compiled it and looked at the produced code via Reflector and got this:

List<int> <>g__initLocal10 = new List<int>();            
<>g__initLocal10.Add(1);                                             
<>g__initLocal10.Add(2);                                             
<>g__initLocal10.Add(3);                                             
<>g__initLocal10.Add(4);                                             
<>g__initLocal10.Add(5);                                             
<>g__initLocal10.Add(6);                                             
<>g__initLocal10.Add(7);                                             
<>g__initLocal10.Add(8);                                             
<>g__initLocal10.Add(9);                                             
List<int> numbers = <>g__initLocal10;                          
if (CS$<>9__CachedAnonymousMethodDelegate12 == null)                 
{                                                                          
    CS$<>9__CachedAnonymousMethodDelegate12 = delegate (int number) {
        return new XElement("child",                                       
		new object[] {                                         
			new XAttribute("age", number),                 
			new XAttribute(                                
				"referenceNumber",                     
				"ref-" + number.ToString()),           
				"child & content #" + number.ToString()
			});                                            
    };                                                                     
}                                                                          
XElement xml = new XElement("children",                                    
	numbers.Select<int,                                         
	XElement>(CS$<>9__CachedAnonymousMethodDelegate12));  

I don't know, that delegate looks like a lambda to me ;) Disguised behind the LINQ sugar, but it's there.

rascunho » Blog Archive » links for 2008-04-15 wrote rascunho &raquo; Blog Archive &raquo; links for 2008-04-15
on 04-15-2008 4:32 PM

Pingback from  rascunho  &raquo; Blog Archive   &raquo; links for 2008-04-15

Sergio Pereira wrote ALT.NET Conference Recap
on 04-21-2008 7:29 PM

ALT.NET Conference, take two. When I signed for this conference for the second time I didn&#39;t honestly

Sergio Pereira wrote Designing With Lambdas - Part III
on 04-29-2008 2:58 PM

In the previous two installment of the series we discussed how we can use lambdas to encapsulate more

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)