Wednesday, April 18, 2007 1:57 AM bart

Answers to C# Quiz - Fun with System.Xml

Answers to the C# Quiz - Fun with System.Xml. Here's a quick refresh of the problem:

Copy Code
1 using System; 2 using System.Xml; 3 4 class Program 5 { 6 static void Main() 7 { 8 new Bar<int>().CreateBar<double>("Bart").CreateBar<long>("DeSmet"); 9 } 10 } 11 12 class Bar<T> 13 { 14 static XmlDocument doc = new XmlDocument(); 15 16 private XmlElement root; 17 18 public Bar() 19 { 20 root = doc.CreateElement("Root"); 21 } 22 23 private Bar(XmlElement root) { this.root = root; } 24 25 public Bar<S> CreateBar<S>(string foo) 26 { 27 XmlElement f = doc.CreateElement(foo); 28 root.AppendChild(f); 29 return new Bar<S>(root); 30 } 31 }

The comments on the original post are mostly correct. The problem with this fragment is that for every Bar<T> with a distinct T, another instance of the static XmlDocument "doc" will be created. This is due to the way generics in .NET work. What happens is this:

  • new Bar<int>() creates a <Root> tag inside the document "doc" which is owned by Bar<int>; it's on the first use of Bar<int> that the "doc" instance is created;
  • the CreateBar<double>("Bart") call on the Bar<int> instance will work fine, since it references the same document "doc"; it does return a Bar<double> instance however, pointing to the "root" element of the current instance (ctor line 23);
  • next, the CreateBar<long>("DeSmet") call happens on the type Bar<double> which has its own "doc" element (the constructor call of line 14 has been called for Bar<double> during line 29's constructor call - or before - because that's the first use of Bar<double>); here things fail due to the way DOM works concerning document contexts:
  • Unhandled Exception: System.ArgumentException: The node to be inserted is from adifferent document context.
    at System.Xml.XmlNode.AppendChild(XmlNode newChild)
    at Bar`1.CreateBar[S](String foo)
    at Program.Main()

A solution? One solution is to use a singleton:

Copy Code
1 internal static class SingletonDocument 2 { 3 static XmlDocument doc; 4 5 public static XmlDocument Instance 6 { 7 get 8 { 9 if (doc == null) 10 doc = new XmlDocument(); 11 return doc; 12 } 13 } 14 }

thus replacing line 14 of the original fragment with

Copy Code
static XmlDocument doc = SingletonDocument.Instance;

Note: The SingletonDocument shouldn't be declared as a child class of the Bar class, since you'd end up with distinct statics again in such a case (Bar<int>.SingletonDocument is different from Bar<double>.SingletonDocument for example).

An alternative is to change the CreateBar<S>(string foo) method like this:

Copy Code
1 public Bar<S> CreateBar<S>(string foo) 2 { 3 XmlElement f = doc.CreateElement(foo); 4 root.AppendChild(f); 5 if (typeof(S) != typeof(T)) 6 Bar<S>.doc = doc; 7 return new Bar<S>(root); 8 }

You can omit the type-check in line 5 if you'd like. The basic idea here is to "transfer" the existing instance every time a new Bar<S> is created. However, this trick only helps "from the inside"; it's easy to forget to "forward" the existing instance and it doesn't work "from the outside", that is: different distinct types created outside CreateBar<S> will have different doc instances. Therefore, the singleton seems like the best general-purpose solution (i.e. without changing the original code structure, just solving the problem of the different statics) to this problem. An additional question raised by the solution mentioned above is whether or not there's still value in using a static variable anyway:

Copy Code
1 class Bar<T> 2 { 3 private XmlDocument doc = new XmlDocument(); 4 5 private XmlElement root; 6 7 public Bar() 8 { 9 root = doc.CreateElement("Root"); 10 } 11 12 private Bar(XmlElement root) { this.root = root; } 13 14 public Bar<S> CreateBar<S>(string foo) 15 { 16 XmlElement f = doc.CreateElement(foo); 17 root.AppendChild(f); 18 Bar<S> res = new Bar<S>(root); 19 res.doc = doc; 20 return res; 21 } 22 }

The source of this quiz problem? LINQ's IQueryable<T> has a method called CreateQuery<TElement>(...) which results in an equivalent circumstance as the one illustrated in the quiz problem... In a larger context than a simple quiz such a bug might be a bit more subtle to spot. | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:


No Comments