Another year has come and gone, and with it a few more pounds than I care to admit. Rather than just make yet another hollow New Year's Resolution to "get in shape", I decided to actually take action. I wanted to motivate myself to follow through, so I signed up with Team In Training to ride 100 miles around Lake Tahoe on 6/3/07. Yes, that will be quite a sight to behold, watching me propel all that fat around mountain passes and hairpin switchbacks!
Team In Training (TNT) is a program sponsored by the Leukemia and Lymphoma Society. They have made a lot of strides over the year in researching leukemia and blood cancer diseases. For example, they have increased survival rates of some types of leukemia in children from 4% in 1960 to 80% today.
I need your help to get this done. By donating your money, you get a tax deductible donation, and also help a great cause. I've committed to raise a minimum of $4,200 for the Leukemia Society to help this year's beneficiary, 6 year old Gabrielle Van Dyke. I'm also riding in memory of Lewis Challoner, the father of my friend, Steve Challoner. Lewis took us on the Rock 100 (and then the Rock 64) when we were in high school, and continued to take long-distance bike rides into his late 60s.
The easiest way to donate is to donate online by going to my fund-raising web page at http://www.active.com/donate/tntwi/tntwiDMiser. If you want/need to send a check, or have any other questions, please feel free to email me.
Thanks for your time, and I look forward to your donation to help this important cause. If you know of anyone else that might like to help, please pass this on. Every little bit helps!
I just went through the very lengthy process of moving from MSSQLCE beta to MSSQLCE release. The good news is that after I was done, all of my code still worked, so at least I have that going for me.
The biggest time sink for me was uninstalling applications. Uninstalling the patch from VS2005 Pro was not all that straight-forward. First, I had to re-download and burn the ISO image since I couldn't find my original DVD. I then noticed that I had another entry in my Add/Remove Programs called "Microsoft Visual Studio 2005 Permier Partner Edition - ENU" that had the VS.NET SP1 Beta applied to it. I had no idea how this program got installed, and that took quite a bit of spelunking to figure out. I came across a blog entry from Heath Stewart that explained how to look into the patch database to figure out which MS patches were applied to which products. After installing that, it needed a Windows Installer Patch (msp) file, but I couldn't find that. I ended up searching my system, and got the VS.NET msp file and opened it in Heath's utility. From there, I got the GUID of the Premier Partner Edition, and searched my registry. In the end, it turned out to be MSSQL 2005 Developer Edition that installed that program. I finally located that DVD, pointed the uninstaller to the right msi file, and I was able to uninstall the patch. However, after completing all of this, I couldn't install the MSSQL Server 2005 Compact Edition Tools for VS 2005 SP1. I received the same error as I did last time (see the above link for details), but I couldn't solve it by doing a Repair on VS.NET, and I couldn't dig anything up in Filemon or Regmon to find out why it was failing so I ended up completely uninstalling and reinstalling VS.NET. Not exactly very user-friendly.
Installing the release of MSSQLCE is very similar to the post I made earlier on how to install MSSQLCE. The difference is the links to the new files, which can be found here:
In Delphi, if you want an array of integers, indexed by an enumeration, you simply do this:
type
TSuit = (Clubs, Spades, Hearts, Diamonds);
var
CardCount: array[TSuit] of integer;
...
CardCount[Clubs] := 13;
In .NET, however, you can not specify an enumerated type as the indexer into an array, so a line by line conversion to C# will not work. Instead, you need to create an entirely new helper class to make the calling code more readable (i.e. without putting casting code in all of the uses of the indexer). Furthermore, you can't use the code below in a Compact Framework (CF) application, because Enum.GetValues() and Enum.GetNames() aren't implemented in the CF libraries.
class Program { static void Main(string[] args) { CardCounter cc = new CardCounter(); cc[Suit.Clubs] = 13; } } public enum Suit {Clubs, Spades, Hearts, Diamonds}; public class CardCounter { private static int enumCount = Enum.GetValues(typeof(Suit)).Length; private int[] counts = new int[enumCount]; public int this[Suit suit] { get { return counts[(int)suit]; } set { counts[(int)suit] = value; } } }
XML serialization is handy, but it does lack some power. For example, if you have class inheritance, and you want a property of the child class to have a different name than the one given to it by the parent, you will get an error at run-time when trying to serialize the object: "Member 'Derived.Name' hides inherited member 'Base.Name', but has different custom attributes.".
Assume you have a class structure like this:
public abstract class Base { private int id; private string name; [XmlElement("id")] public int Id { get { return id; } set { id = value; } } [XmlElement("name")] public virtual string Name { get { return name; } set { name = value; } } } public class Derived : Base { [XmlElement("newName")] public override string Name { get { return base.Name; } set { base.Name = value; } } }
To see the error, call it like this:
static void Main(string[] args)
{ Derived d = new Derived(); d.Id = 23; d.Name = "Dan"; string xml = GetXMLFromObject(d); Console.WriteLine(xml); }
Download the sample application here to play with it.
Some times, it is better to have a minimal format for your XML file. When using XmlSerializer, it likes to add the standard XML namespaces to the generated XML file. If you want those gone, use code like this: public static string GetXMLFromObject(object obj) { XmlSerializer xs = new XmlSerializer(obj.GetType()); // Set up chain of writers StringBuilder sb = new StringBuilder(); using (XmlTextWriter writer = new XmlTextWriter(new StringWriter(sb))) { writer.Formatting = Formatting.Indented; // Remove standard namespace attributes XmlSerializerNamespaces xsn = new XmlSerializerNamespaces(); xsn.Add(string.Empty, string.Empty); xs.Serialize(writer, obj, xsn); } return sb.ToString(); }
The key is to create an XmlSerializerNameSpaces object, populate it with empty strings, and then pass it to the XmlSerizliazer. The result is a more compact XML file. Note that this works, despite the comment in the documentation explicitly saying that it isn't supported.
You can also use this class to add your own custom namespaces. This could be useful to prefix certain elements with a certain namespace.
While the XmlIgnore/Specified pattern to control serialization is very useful, it could be overkill for simpler serialization scenarios.
When you create an object with a bool field, that field is initialized and defaulted to the value of false. Under normal conditions, this value will get serialized every time. However, what if we only care about capturing that value when the value is true? It turns out that we can use DefaultValueAttribute. The first thing that may stand out to you is that this attribute lives in the System.ComponentModel namespace, and not in an XML namespace. The online help also mentions that this is used for visual designers and code generators. XmlSerializer also uses this attribute to decide whether or not to stream the value during serialization, since it's really a form of a code generator (more on that in a later post). If the value of the field or property matches the value that you specify in the DefaultValueAttribute, then that property will not be streamed.
Using an XmlAttributeAttribute, you can specify that a certain property will be expressed as an XML attribute instead of an XML element. To demonstrate both of these concepts, see the following code:
public class Url { [XmlAttribute("address")] public string Address;
[XmlAttribute("primary"), DefaultValue(false)] public bool Primary;
public Url() { } }
static void Main(string[] args) { Url u = new Url(); u.Address = "http://testing/"; string xml = GetXMLFromObject(u); Console.WriteLine(xml); }
This produces roughly the following output (XML namespace information is stripped here. I will show how to do this in code with a future post). Notice that the Primary attribute is not generated because it matches the DefaultValue.
<?xml version="1.0" encoding="utf-16"?> <Url address="http://testing/" />
David Hervieux posted a link to some Serialization FAQs in a comment to my last post. This material is good, and actually forced me to go back through what I wanted to cover, since I didn't want to just add posts that duplicate what can be found easily on the web. I'll still move forward with a few items in XML serialization that I think are unique.
The XMLIgnore attribute is used to tell the XmlSerializer that this field is transient, and should not be (de)serialized. The most typical use for this would be to mark calculated fields with this attribute. This is an easy customization that helps your serialization process, but it leaves a bit to be desired. It's an "all or nothing" approach, where the property is either streamed or not.
This MSDN article hints at what to do to get more fine-grained control of this aspect of serialization. Basically, you create a public boolean field or property named propertyNameSpecified for the property you want to control. The XmlSerializer uses this convention to determine whether or not to include the associated property for serialization. Since this new Specified field will be eligible for serialization, be sure to mark it with the XmlIgnore attribute to prevent it from being serialized.
A very simple code listing showing how this all fits together:
public class Player { private int number; private string name;
[XmlElement("jerseyNumber")] public int Number { get { return number; }
// You could add more logic here to only set NumberSpecified based on some criteria set { number = value; NumberSpecified = true; } }
[XmlElement("playerName")] public string Name { get { return name; } set { name = value; } }
[XmlIgnore] public bool NumberSpecified; }
The downsides to using this techniques are:
It requires another field, and some custom logic to make this work.
It requires that the Specified field be marked as public, exposing it to consumers of the library. There is no real reason why the XmlSerializer couldn't use reflection to get at a non-public member.
If you use a property, it must be a full read-write property. A read-only property could very well suffice here, and make your code easier to maintain.
Download the very simple sample showing this behavior here.
Serializing between .NET objects and XML files, and vice versa, is extremely easy, yet flexible and powerful. I'll be blogging a few entries about this topic in the days to come. This entry will set the stage, showing the basics of XML serialization.
Assume you have a (stripped down) class definition like this:
public class Player
{
[XmlElement("jerseyNumber")]
public int Number { get; set; }
[XmlElement("playerName")]
public string Name { get; set; }
}
And you wanted to read/write an XML file that looked like this:
<Player>
<jerseyNumber>23</jerseyNumber>
<playerName>Dan</playerName>
</Player>
The key to making this happen is the XmlSerializer class. This is the class that will convert between public properties and XML elements. You can also decorate your class with simple attributes to coerce your class into generating custom XML (i.e. XML attributes, hide or rename elements, etc.).
Here are a couple of helper methods, and a sample that uses them to show you how all of this comes together.
static string GetXMLFromObject(object obj)
{
XmlSerializer ser = new XmlSerializer(obj.GetType());
StringBuilder sb = new StringBuilder();
using (XmlTextWriter writer = new XmlTextWriter(new StringWriter(sb)))
{
writer.Formatting = Formatting.Indented;
ser.Serialize(writer, obj);
return sb.ToString();
}
}
static object CreateObjectFromXML(string xml, Type xmlType)
{
XmlSerializer ser = new XmlSerializer(xmlType);
object obj = null;
using (StringReader reader = new StringReader(xml))
{
obj = ser.Deserialize(reader);
}
return obj;
}
static void Main(string[] args)
{
Player p = new Player();
p.Number = 23;
p.Name = "Dan";
string xml = GetXMLFromObject(p);
Console.WriteLine(xml);
Console.WriteLine();
Player q = (Player)CreateObjectFromXML(xml, typeof(Player));
Console.WriteLine(q.Number + " --> " + q.Name);
Console.ReadLine();
}
Other references: XML Serialization in the .NET Framework
|