March 2008 - Posts
Between work and stuff going on in my personal life, there hasn't been a lot of spare time for blogging and developer community things over the last few weeks. Hopefully, that will be changing in the near future. In the meantime, I wanted to squeeze in a quick blog post about something that came up today regarding StringBuilder.
It was pointed out someone on my team was concatenating several strings to build a query in a few methods. It is arguable whether these particular queries should be embedded in the source code, but that isn't what this post is about. It was requested that the developer modify the source code to use a StringBuilder rather than concatenating the strings. Generally speaking, I would agree with this approach as the StringBuilder is more efficient when concatenating several strings, but this wasn't one of those cases.
Consider the following two methods:
public static string LiteralConcatenationExample()
{
return "SELECT T1.Field1, " +
"T1.Field2, " +
"T1.Field3, " +
"T2.Field4 " +
"FROM Table1 T1 " +
"INNER JOIN Table2 T2 ON T1.Field1 = T2.Field1";
}
public static string StringBuilderWithLiteralsExample()
{
StringBuilder sb = new StringBuilder();
sb.Append("SELECT T1.Field1, ");
sb.Append("T1.Field2, ");
sb.Append("T1.Field3, ");
sb.Append("T2.Field4 ");
sb.Append("FROM Table1 T1 ");
sb.Append("INNER JOIN Table2 T2 ON T1.Field1 = T2.Field1");
return sb.ToString();
}
Which method do you think would be more efficient?
If you chose the second method, you should take a closer look at the generated IL:
.method public hidebysig static string LiteralConcatenationExample() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldstr "SELECT T1.Field1, T1.Field2, T1.Field3, T2.Field4 "
+ "FROM Table1 T1 INNER JOIN Table2 T2 ON T1.Field1 = T2.Field1"
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method Program::LiteralConcatenationExample.method public hidebysig static string StringBuilderWithLiteralsExample() cil managed
{
// Code size 90 (0x5a)
.maxstack 2
.locals init ([0] class [mscorlib]System.Text.StringBuilder sb,
[1] string CS$1$0000)
IL_0000: nop
IL_0001: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "SELECT T1.Field1, "
IL_000d: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0012: pop
IL_0013: ldloc.0
IL_0014: ldstr "T1.Field2, "
IL_0019: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_001e: pop
IL_001f: ldloc.0
IL_0020: ldstr "T1.Field3, "
IL_0025: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_002a: pop
IL_002b: ldloc.0
IL_002c: ldstr "T2.Field4 "
IL_0031: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0036: pop
IL_0037: ldloc.0
IL_0038: ldstr "FROM Table1 T1 "
IL_003d: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0042: pop
IL_0043: ldloc.0
IL_0044: ldstr "INNER JOIN Table2 T2 ON T1.Field1 = T2.Field1"
IL_0049: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_004e: pop
IL_004f: ldloc.0
IL_0050: callvirt instance string [mscorlib]System.Object::ToString()
IL_0055: stloc.1
IL_0056: br.s IL_0058
IL_0058: ldloc.1
IL_0059: ret
} // end of method Program::StringBuilderWithLiteralsExample
As you can see, the string concatenation is considerable more efficient. This may be a bit confusing to some people as you have undoubtedly had it hammered into your head that a StringBuilder should be used when concatenating strings. Well, I would agree when you are dealing with dynamic strings that are built using parameters. However, literal strings are a different story.
Since this scenario is using literal strings, the compiler can optimize the values into one large string that can be loaded with a single load string operation (ldstr). If the code is modified to use a StringBuilder for each line of the query, it actually increases the number of operations by four for each line of the query.
In fairness, most situations will probably be dealing with dynamic strings, which would change this scenario back to favor the usage of a StringBuilder. However, you should keep in mind that a StringBuilder doesn't automagically give you a more efficient solution in every situation, especially if you are only concatenating a few strings.
The last time that I purchased a computer was 2003. I bought all the parts and built my own gargantuan desktop, which has been my faithful servant for the nearly five years. Unfortunately, all good things must come to an end. Although my old desktop would still get the job done, it was becoming noticeably outdated. During the last few years, I have also found myself becoming more and more dependent upon the need for a laptop. It is a little difficult to drag around a mammoth desktop to code camps and user groups when I'm doing a presentation. While my employer issued laptop was adequate, it certainly left a lot to be desired.
After debating for some time as to whether I wanted another desktop or a kick ass laptop, I finally decided it was time to say good bye to my desktop. It is difficult for me to come up with many valid reasons for going the desktop route again. Five years ago, I was a fairly intense MMORPG gamer, but I have finally beaten that addiction and have no desire to return to such a waste of time (albeit a fun waste of time). My gaming itch is now satisfied via XBox 360. Games that can be paused are much more attractive to me these days. Since PC gaming is removed from the picture, a laptop was the logical choice.
After researching quite a few options and waiting for the right deal to come along, I finally went with the Dell XPS M1530. It was the perfect balance between price and power. I have spent many hours over the last several nights trying to get it setup with all of my software and various tweaks. Finally, it is beginning to feel like home rather than a stranger's computer.
Here is a photo of the model that I bought as well as the specs:
- Intel Core 2 Duo Processor T7500 (2.2Ghz / 800Mhz FSB / 4MB L2 Cache)
- 4GB DDR2 667Mhz
- 200GB 7200RPM SATA (with Free Fall Sensor)
- 256MB NVIDIA GeForce 8600M GT
- 8X DVD+/-RW Slot Load Drive
- Intel 4965AGN Wireless N Mini-Card
- 85 WHr 9-cell Lithium Ion Primary Battery
- Integrated Biometric Finger Print Reader
- Integrated Web Camera
As I write this post, my old desktop is being reformatted and reinstalled with Windows Server 2003. It will now become a file server for my wife and me, and it will probably serve a few other experimental purposes as well. I would love to have loaded Windows Server 2008 on it, but the hardware isn't 64 bit. At least, my old companion will continue to live on in some other capacity rather than being sent to the big hardware dumpster in the sky.
In .NET 3.0, WCF did not have any built-in support for syndication feeds, such as RSS and ATOM. As a result, it was necessary to roll your own solution anytime you wanted to render a syndication feed from your service. While this wasn't an incredibly difficult task, it was still rather tedious. Fortunately, .NET 3.5 introduced rich support for easily enabling syndication feeds from your service with relatively little effort. Overall, it does a great job of handling the dirty work for you. Let's take a look at how to set it up.
For this example, let's say that you have a WCF service that provides product information. We want to enhance this service to provide an RSS/ATOM feed containing the product information. The first thing we need to do is setup our service contract.
[ServiceContract(Namespace = "JeffBarnes.Samples.Syndication")]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
[ServiceKnownType(typeof(Atom10FeedFormatter))]
public interface IProductService
{
[OperationContract]
[WebGet(UriTemplate = "/product/{code}")]
Product GetProduct(string code);
[OperationContract(Name = "GetAllProducts")]
[WebGet(UriTemplate = "/products?format={format}")]
SyndicationFeedFormatter GetProductSyndicationFeed(string format);
[OperationContract(Name = "GetProductsByCategory")]
[WebGet(UriTemplate = "/products/{category}?format={format}")]
SyndicationFeedFormatter GetProductSyndicationFeed(string category, string format);
}
If you are familiar with WCF services, the ServiceContract and OperationContract attributes should already be familiar to you. Depending upon whether you have written many advanced services, you may also be familiar with ServiceKnownType. If not, it is essentially used to inform the WCF runtime that it needs to be aware of a specific type for the purpose of serialization.
The WebGet attribute is a newcomer in .NET 3.5. It is part of the new REST stack. I have already written about WebGet in a previous post. You should refer to it for a more thorough overview. The abridged version is that it allows the WCF runtime to dispatch HTTP GET requests that match the specified UriTemplate to the given method. As you can probably discern from the code, the GetProductSyndicationFeed method returns a feed of products in a specified format (RSS or ATOM).
So, what does the implementation look like? I'm going to trim it down to the most interesting pieces for the sake of brevity.
// Create the syndication items from the products.
IEnumerable<SyndicationItem> items =
from product in products
orderby product.Code
select new SyndicationItem(...);
// Create the syndication feed.
SyndicationFeed feed = new SyndicationFeed(
"Jeff's Product Catalog",
feedDesc,
WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri,
items);
// Determine the format to use.
SyndicationFeedFormatter formatter = null;
if (String.Compare(format, "rss", true) == 0)
{
formatter = new Rss20FeedFormatter(feed);
}
else
{
formatter = new Atom10FeedFormatter(feed);
}
return formatter;
First, a LINQ query is used to construct an enumerable collection of SyndicationItems based upon the product information. SyndicationItem offers a lot of flexibility and extensibility. It provides seamless support for formatting your content as html, xml, or custom formats. Next, the SyndicationItems are supplied to a SyndicationFeed that decorates the items with some other information. Ultimately, the SyndicationFeed is wrapped by a feed formatter. Depending upon the requested format, either RSS or ATOM will be used.
There is one last gotcha to keep in mind in regards to the hosting of the service. In order to enable the REST stack, there is a specific binding and behavior that must be used: webHttpBinding and webHttpBehavior. You can manually set these up via configuration. However, there is a new ServiceHost subclass available that offers a configuration free alternative. Check out the following snippet:
using (WebServiceHost host = new WebServiceHost(typeof(ProductService), new Uri("http://localhost:8000/ProductService")))
{
host.Open();
Console.WriteLine("Service Ready");
Console.Read();
}
WebServiceHost will automatically create an endpoint with the necessary binding and behavior to enable the REST stack. I don't even have a configuration file in this demo. The only configuration code is what is shown above. If you are using IIS to host your service, you can still use WebServiceHost, but it requires referencing System.ServiceModel.Activation.WebServiceHostFactory from your SVC file.
Once you get everything wired together, it's as easy as typing a URL into your web browser of choice and viewing the results. Most browsers are aware of syndication feeds and apply special formatting to them. For example, here is a screen shot of IE when you hit http://localhost:8000/productservice/products/Beverage for the demo.
I have barely scratched the surface of the capabilities of the syndication infrastructure in WCF, but this example effectively demonstrates how little effort is required to get started. I'll probably do some additional posts on some of the more advanced syndication abilities at some point in the future.
You can download the complete sample here.
Disclaimer:The opinions and views expressed within this blog are solely my own and do not represent those of my employer or anyone else.