Monday, April 2, 2007

Close()? Dispose()? A .NET programmer needs not these things!

Hogwash.

In reading Tess' MSDN blog this morning, it got me thinking about some things with object disposal and garbage collection. Question 12 on her little pop quiz in particular:

12. Why is it important to close database connections and dispose of objects? Doesn't the GC take care of that for me?


Of course, the first poster already answers it:

12/ It does, in the finalizer thread, which takes a _long_ time to process things. Basically this shove stuff like connection pooling out of the window, not to mention that basically resources are held for much longer.


I've witnessed several occasions where the Close() and Dispose() methods are completely ignored by programmers. Streams are left hanging, web services are left dangling... it's a mess!

Usually Close() isn't so much of a problem. Developers are typically pretty aware when they're opening a file or database connection. Still, you need to be keenly aware of what you're doing when you write something such as this:


public class SqlConnectionMgr
{
public SqlConnection GetDBConnection(string connectionString)
{
return new SqlConnection(connectionString);
}
}


This is overly simplified, but I've seen similar code dozens of times. Now you've just create a connection to the database, but you've thrown it out into the wild to hope that whomever calls your code remembers to close it. If you can trust that person, fine. If not, you may find it better do something like this:


public class SqlConnectionMgr : IDisposable
{
private SqlConnection connection;

public SqlConnection GetDBConnection(string connectionString)
{
if (connection != null && connection.State == ConnectionState.Open)
connection.Close();
connection = new SqlConnection(connectionString);
return connection;
}

public void Dispose()
{
if (connection != null && connection.State == ConnectionState.Open)
connection.Close();
GC.SuppressFinalize(this);
}

~SqlConnectionMgr()
{
Dispose();
}
}


Now you can retain management of the connection in your own class. You have to implement IDisposable now, but that's a more implicit direction to the consumer that something has to be done, and you can take part of the onus of managing resources off the consumer in case they forget.

Some things to remember when implementing IDisposable:
  • Make sure to also implement a finalizer which calls your Dispose() method in case the programmer forgets to dispose of the object.
  • In the Dispose() method, you need to call the garbage collector's SuppressFinalize(object) method. This will ensure that if the object was already disposed, the finalizer is not called needlessly.


(And yes, I know that in changing my example above it no longer is able to pass out multiple connections. I'll leave that as a challenge for the reader.)

IDisposable is an excellent way of managing resource, because, well, that's why it was added to the framework. You need to be keenly aware of whether or not the objects you're consuming implement this interface. (The easiest way to find out if you're in the middle of coding is to look through the object's IntelliSense list for the Dispose() method.)

C# provides an excellent mechanism for working with disposable objects known as the using statement:


using (MyDisposableObject obj = new MyDisposableObject())
{
// do something ...
}


When this code executes, the object is created and neatly disposed of within the scope of the using statement. This basically is just a clean, shorthand way of writing:


MyDisposableObject obj = new MyDisposableObject();
// do something ...
obj.Dispose();


There are a lot of objects in the .NET framework that use resources that need to be disposed, including unmanaged resources that can wreak havoc if left open waiting for the garbage collector to come along. One in particular that I have run into is the Bitmap class. If you don't dispose of a Bitmap when you're done with it, depending on the size of the image you could end up with a huge chunk of the heap sitting around doing nothing, waiting to be removed from memory at some indeterminate point in the future.

While .NET provides a lovely managed platform, be sure that you are still keenly aware of resources that you're using. While it's not as easy to lose pointers and form memory leaks in a .NET application as it is in an unmanaged C++ application, you can still waste plenty of system resources waiting around for the garbage collector to do your cleanup for you.

Thursday, March 29, 2007

How to remove empty elements from an XML document

Since this is kind of universally useful, I recently was able to perfect a little snippet of XSL to remove empty elements from an XML document. This only removes elements that have no inner content or attributes. (In case you're wondering, I did this to handle special legacy XML documents for serialization.)


<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="no" indent="no"/>
<xsl:strip-space elements="*" />
<xsl:template match="*[not(node()) and not(./@*)]"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>


And in case you're not familiar with how to apply XSL in .NET:

C#:

XmlDocument xsl = new XmlDocument();
xsl.LoadXml(XSL_REMOVE_EMPTY_NODES); // constant contains the XSL above

StringWriter writer = new StringWriter();
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(xsl);
transform.Transform(new XmlNodeReader(xml.doc), null, writer);
writer.Flush();

string transformedXml = writer.ToString();


VB.NET:

Dim xsl As XmlDocument = New XmlDocument()
xsl.LoadXml(XSL_REMOVE_EMPTY_NODES) ' constant contains the XSL above

Dim writer As StringWriter = New StringWriter()
Dim transform As XslCompiledTransform = New XslCompiledTransform()
transform.Load(xsl)
transform.Transform(New XmlNodeReader(xml.doc), Nothing, writer);
writer.Flush()

Dim transformedXml As String = writer.ToString()


You can also use a Stream or XmlWriter in place of a TextWriter like I have above. I just used a StringWriter since I needed a String for the final result.

Friday, March 23, 2007

"The project you are trying to open is a Web project. You need to open it by specifying its URL path."

I hate it when I get this error even when I am opening the project from a URL. Luckily, there's a simple solution.

In the same folder as the project file (.csproj or .vbproj) of the web project you are trying to open, make a new file with the same name as the project file with .webinfo tacked on the end.

Thus, if you have a project called MyWebProject.csproj, you create a new file called MyWebProject.csproj.webinfo. In that file, it needs a short XML snippet:


<VisualStudioUNCWeb>
<Web URLPath="http://localhost/MyWebProject/MyWebProject.csproj" />
</VisualStudioUNCWeb>


Obviously, you need to replace the URLPath value with the actual value for your web project file. Once you have this hint in place, Visual Studio should be able to load your web project without a hitch.

This seems to be Visual Studio .NET 2003 problem. I've not seen it manifest itself in Visual Studio .NET 2005 yet.

Sunday, February 4, 2007

Trust in ADO.NET

In reviewing some C# code for a project at work, I'm constantly running across the following construct in the code (and this is from different developers, not just one):


DataSet ds = SqlHelper.ExecuteDataSet(sConn, CommandType.StoredProc, sProc, aParams);
// ... snip ...
DataRow row = ds.Tables[0].Rows[0];
int someInt = Int32.Parse(row["intColumn"].ToString());


Now, while this code works like you would expect, there's still something wrong with it. You're basically wasting cycles on mindless conversions.

Whenever you use ADO.NET DataRow objects, it is true that the return from the indexer is a generic Object, so something needs to happen to get the result into a strongly-typed value. But, if the database column is a SQL int, ADO.NET already returned you an Int32!

What's written above is basically the same as this:


int a = 1;
int b = Int32.Parse(a.ToString());


You're wasting cycles first converting the Int32 to a String and then using Int32.Parse to get it back to the Int32 that it was in the first place. Trust in ADO.NET!


DataSet ds = SqlHelper.ExecuteDataSet(sConn, CommandType.StoredProc, sProc, aParams);
// ... snip ...
DataRow row = ds.Tables[0].Rows[0];
int someInt = (int)row["intColumn"];


There we go, much better. If you want something that feels a little "safer" than a direct cast, feel free to use Convert.ToInt32; it at least knows not to waste time doing needless conversions if it's already passed an Int32.

As a final thought, I'll leave you this completely failed code to think about:


DataSet ds = SqlHelper.ExecuteDataSet(sConn, CommandType.StoredProc, sProc, aParams);
// ... snip ...
DataRow row = ds.Tables[0].Rows[0];
bool someBool = (row["bitColumn"].ToString() == "1");


(Hint: The reason this code didn't work like the developer expected is because the DataRow already returned a Boolean.)

Merge Modules

In my previous post, I mentioned a method for automating the addition of features (specifically properties) to a Windows Installer package generated by Visual Studio. After doing some more research, I encountered the wonderful land of merge modules.

Merge modules are just what they sound like; they allow you to merge features into a Windows Installer package that you are creating. Among other things, you can set default properties by including a Property table in the merge module.

Creating a merge module is not difficult but requires a bit of knowledge. Once again, you need the lovely Orca tool from the Windows Platform SDK.

Orca, from what I found, doesn't seem to be able to create a blank merge module template. So I had to do it by hand. Starting with a blank new file, you will need to add the following schema:

TableColumnPKTypeNullable
ComponentComponentXString (72)N
ComponentId String (38)Y
Directory String (72)N
Attributes Short IntN
Condition String (255)Y
Key Path String (72)Y
DirectoryDirectoryXString (72)N
Directory_Parent String (72)Y
Default_Dir Local String (255)N
FeatureComponentsFeature_XString (38)N
Component_ String (72)N
ModuleComponentsComponentXString (72)N
ModuleIDXString (72)N
LanguageXShort IntN
ModuleSignatureModuleIDXString (72)N
LanguageXShort IntN
Version String (32)N


Some of these tables are predefined in Orca, so you won't need to enter all of them by hand. Most of the tables don't actually have to have data, they just need to exist for the file to be considered a valid merge module.

The only table that requires data is the ModuleSignature table. It must contain at least one row identifying the merge module. The format for the ModuleID is a GUID separated by underscores (_). The Language is 1033 for US English; you will need to look this code up if you are using other locales. Version is the typical Windows Installer version format: n.nn.nnnn.

Once you have that basic structure in place, you can enter your additions into the merge module. The merge module can have pretty much anything a full install package can have. Once you're done, save the file with a .msm extension. You can then go in to Visual Studio and add the merge module to your deployment project.

Technically, there is one more thing you really should have in your merge module: a _Validation table. While not strictly necessary for use, this table is used by Orca to run validation against the merge module.

One caveat I did find with using a merge module to preset properties in deployment project builds: you can't use a merge module to override existing entries in the Property table. You can only create new properties. I was able to get the majority of the setup I wanted done automatically, I still have to go in a manually edit my final .msi file to accommodate all of my requirements.