//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner Microsoft // @backupOwner Microsoft //--------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Data.Common; using System.Data.Query.PlanCompiler; using System.Globalization; using System.Text; using System.Diagnostics; using System.IO; using System.Xml; using md = System.Data.Metadata.Edm; // // This module serves as a dump routine for an IQT // The output is a weird form of Sql - closer to Quel (and perhaps, C# // comprehensions) // namespace System.Data.Query.InternalTrees { /// /// A dump module for the Iqt /// internal class Dump : BasicOpVisitor, IDisposable { #region private state private XmlWriter _writer; #endregion #region constructors private Dump(Stream stream) : this(stream, Dump.DefaultEncoding, true) { } private Dump(Stream stream, Encoding encoding, bool indent) : base() { XmlWriterSettings settings = new XmlWriterSettings(); settings.CheckCharacters = false; settings.Indent = true; settings.Encoding = encoding; _writer = XmlWriter.Create(stream, settings); _writer.WriteStartDocument(true); } #endregion #region "public" surface internal static readonly Encoding DefaultEncoding = Encoding.UTF8; /// /// Driver method to dump the entire tree /// /// /// static internal string ToXml(Command itree) { return ToXml(itree, itree.Root); } /// /// Driver method to dump the a subtree of a tree /// /// /// /// static internal string ToXml(Command itree, Node subtree) { MemoryStream stream = new MemoryStream(); using (Dump dumper = new Dump(stream)) { // Just in case the node we're provided doesn't dump as XML, we'll always stick // an XML wrapper around it -- this happens when we're dumping scalarOps, for // example, and it's unfortunate if you can't debug them using a dump... using (new AutoXml(dumper, "nodes")) { dumper.VisitNode(subtree); } } return DefaultEncoding.GetString(stream.ToArray()); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Data.Query.InternalTrees.Dump.ToXml")] static internal string ToXml(ColumnMap columnMap) { MemoryStream stream = new MemoryStream(); using (Dump dumper = new Dump(stream)) { // Just in case the node we're provided doesn't dump as XML, we'll always stick // an XML wrapper around it -- this happens when we're dumping scalarOps, for // example, and it's unfortunate if you can't debug them using a dump... using (new AutoXml(dumper, "columnMap")) { columnMap.Accept(ColumnMapDumper.Instance, dumper); } } return DefaultEncoding.GetString(stream.ToArray()); } #endregion #region Begin/End management void IDisposable.Dispose() { // Technically, calling GC.SuppressFinalize is not required because the class does not // have a finalizer, but it does no harm, protects against the case where a finalizer is added // in the future, and prevents an FxCop warning. GC.SuppressFinalize(this); try { _writer.WriteEndDocument(); _writer.Flush(); _writer.Close(); } catch (Exception e) { if (!EntityUtil.IsCatchableExceptionType(e)) { throw; } // eat this exception; we don't care if the dumper is failing... } } internal void Begin(string name, Dictionary attrs) { _writer.WriteStartElement(name); if (attrs != null) { foreach (KeyValuePair attr in attrs) { _writer.WriteAttributeString(attr.Key, attr.Value.ToString()); } } } internal void BeginExpression() { WriteString("("); } internal void EndExpression() { WriteString(")"); } internal void End(string name) { _writer.WriteEndElement(); } internal void WriteString(string value) { _writer.WriteString(value); } #endregion #region VisitorMethods protected override void VisitDefault(Node n) { using (new AutoXml(this, n.Op)) { base.VisitDefault(n); } } protected override void VisitScalarOpDefault(ScalarOp op, Node n) { using (new AutoString(this, op)) { string separator = string.Empty; foreach (Node chi in n.Children) { WriteString(separator); VisitNode(chi); separator = ","; } } } protected override void VisitJoinOp(JoinBaseOp op, Node n) { using (new AutoXml(this, op)) { if (n.Children.Count > 2) { using (new AutoXml(this, "condition")) { VisitNode(n.Child2); } } using (new AutoXml(this, "input")) { VisitNode(n.Child0); } using (new AutoXml(this, "input")) { VisitNode(n.Child1); } } } public override void Visit(CaseOp op, Node n) { using (new AutoXml(this, op)) { int i = 0; while (i < n.Children.Count) { if ((i + 1) < n.Children.Count) { using (new AutoXml(this, "when")) { VisitNode(n.Children[i++]); } using (new AutoXml(this, "then")) { VisitNode(n.Children[i++]); } } else { using (new AutoXml(this, "else")) { VisitNode(n.Children[i++]); } } } } } public override void Visit(CollectOp op, Node n) { using (new AutoXml(this, op)) { VisitChildren(n); } } protected override void VisitConstantOp(ConstantBaseOp op, Node n) { using (new AutoString(this, op)) { if (null == op.Value) { WriteString("null"); } else { WriteString("("); WriteString(op.Type.EdmType.FullName); WriteString(")"); WriteString(String.Format(CultureInfo.InvariantCulture,"{0}",op.Value)); } VisitChildren(n); } } public override void Visit(DistinctOp op, Node n) { Dictionary attrs = new Dictionary(); StringBuilder sb = new StringBuilder(); string separator = string.Empty; foreach (Var v in op.Keys) { sb.Append(separator); sb.Append(v.Id); separator = ","; } if (0 != sb.Length) { attrs.Add("Keys", sb.ToString()); } using (new AutoXml(this, op, attrs)) { VisitChildren(n); } } protected override void VisitGroupByOp(GroupByBaseOp op, Node n) { Dictionary attrs = new Dictionary(); StringBuilder sb = new StringBuilder(); string separator = string.Empty; foreach (Var v in op.Keys) { sb.Append(separator); sb.Append(v.Id); separator = ","; } if (0 != sb.Length) { attrs.Add("Keys", sb.ToString()); } using (new AutoXml(this, op, attrs)) { using (new AutoXml(this, "outputs")) { foreach (Var v in op.Outputs) { DumpVar(v); } } VisitChildren(n); } } public override void Visit(IsOfOp op, Node n) { using (new AutoXml(this, ( op.IsOfOnly ? "IsOfOnly" : "IsOf" ))) { string separator = string.Empty; foreach (Node chi in n.Children) { WriteString(separator); VisitNode(chi); separator = ","; } } } protected override void VisitNestOp(NestBaseOp op, Node n) { Dictionary attrs = new Dictionary(); SingleStreamNestOp ssnOp = op as SingleStreamNestOp; if (null != ssnOp) { attrs.Add("Discriminator", (ssnOp.Discriminator == null) ? "" : ssnOp.Discriminator.ToString()); } StringBuilder sb = new StringBuilder(); string separator; if (null != ssnOp) { sb.Length = 0; separator = string.Empty; foreach (Var v in ssnOp.Keys) { sb.Append(separator); sb.Append(v.Id); separator = ","; } if (0 != sb.Length) { attrs.Add("Keys", sb.ToString()); } } using (new AutoXml(this, op, attrs)) { using (new AutoXml(this, "outputs")) { foreach (Var v in op.Outputs) { DumpVar(v); } } foreach (CollectionInfo ci in op.CollectionInfo) { Dictionary attrs2 = new Dictionary(); attrs2.Add("CollectionVar", ci.CollectionVar); if (null != ci.DiscriminatorValue) { attrs2.Add("DiscriminatorValue", ci.DiscriminatorValue); } if (0 != ci.FlattenedElementVars.Count) { attrs2.Add("FlattenedElementVars", FormatVarList(sb, ci.FlattenedElementVars)); } if (0 != ci.Keys.Count) { attrs2.Add("Keys", ci.Keys); } if (0 != ci.SortKeys.Count) { attrs2.Add("SortKeys", FormatVarList(sb, ci.SortKeys)); } using (new AutoXml(this, "collection", attrs2)) { ci.ColumnMap.Accept(ColumnMapDumper.Instance, this); } } VisitChildren(n); } } private static string FormatVarList(StringBuilder sb, VarList varList) { string separator; sb.Length = 0; separator = string.Empty; foreach (Var v in varList) { sb.Append(separator); sb.Append(v.Id); separator = ","; } return sb.ToString(); } private static string FormatVarList(StringBuilder sb, List varList) { string separator; sb.Length = 0; separator = string.Empty; foreach (SortKey v in varList) { sb.Append(separator); sb.Append(v.Var.Id); separator = ","; } return sb.ToString(); } private void VisitNewOp(Op op, Node n) { using (new AutoXml(this, op)) { foreach (Node chi in n.Children) { using (new AutoXml(this, "argument", null)) { VisitNode(chi); } } } } public override void Visit(NewEntityOp op, Node n) { VisitNewOp(op, n); } public override void Visit(NewInstanceOp op, Node n) { VisitNewOp(op, n); } public override void Visit(DiscriminatedNewEntityOp op, Node n) { VisitNewOp(op, n); } public override void Visit(NewMultisetOp op, Node n) { VisitNewOp(op, n); } public override void Visit(NewRecordOp op, Node n) { VisitNewOp(op, n); } public override void Visit(PhysicalProjectOp op, Node n) { using (new AutoXml(this, op)) { using (new AutoXml(this, "outputs")) { foreach (Var v in op.Outputs) { DumpVar(v); } } using (new AutoXml(this, "columnMap")) { op.ColumnMap.Accept(ColumnMapDumper.Instance, this); } using (new AutoXml(this, "input")) { VisitChildren(n); } } } public override void Visit(ProjectOp op, Node n) { using (new AutoXml(this, op)) { using (new AutoXml(this, "outputs")) { foreach (Var v in op.Outputs) { DumpVar(v); } } VisitChildren(n); } } public override void Visit(PropertyOp op, Node n) { using (new AutoString(this, op)) { VisitChildren(n); WriteString("."); WriteString(op.PropertyInfo.Name); } } public override void Visit(RelPropertyOp op, Node n) { using (new AutoString(this, op)) { VisitChildren(n); WriteString(".NAVIGATE("); WriteString(op.PropertyInfo.Relationship.Name); WriteString(","); WriteString(op.PropertyInfo.FromEnd.Name); WriteString(","); WriteString(op.PropertyInfo.ToEnd.Name); WriteString(")"); } } public override void Visit(ScanTableOp op, Node n) { using (new AutoXml(this, op)) { DumpTable(op.Table); VisitChildren(n); } } public override void Visit(ScanViewOp op, Node n) { using (new AutoXml(this, op)) { DumpTable(op.Table); VisitChildren(n); } } protected override void VisitSetOp(SetOp op, Node n) { Dictionary attrs = new Dictionary(); if (OpType.UnionAll == op.OpType) { UnionAllOp uallOp = (UnionAllOp)op; if (null != uallOp.BranchDiscriminator) { attrs.Add("branchDiscriminator", uallOp.BranchDiscriminator); } } using (new AutoXml(this, op, attrs)) { using (new AutoXml(this, "outputs")) { foreach (Var v in op.Outputs) { DumpVar(v); } } int i = 0; foreach (Node chi in n.Children) { Dictionary attrs2 = new Dictionary(); attrs2.Add("VarMap", op.VarMap[i++].ToString()); using (new AutoXml(this, "input", attrs2)) { VisitNode(chi); } } } } public override void Visit(SortOp op, Node n) { using (new AutoXml(this, op)) { base.Visit(op, n); } } public override void Visit(ConstrainedSortOp op, Node n) { Dictionary attrs = new Dictionary(); attrs.Add("WithTies", op.WithTies); using (new AutoXml(this, op, attrs)) { base.Visit(op, n); } } protected override void VisitSortOp(SortBaseOp op, Node n) { using (new AutoXml(this, "keys")) { foreach (InternalTrees.SortKey sortKey in op.Keys) { Dictionary attrs = new Dictionary(); attrs.Add("Var", sortKey.Var); attrs.Add("Ascending", sortKey.AscendingSort); attrs.Add("Collation", sortKey.Collation); using (new AutoXml(this, "sortKey", attrs)) { } } } VisitChildren(n); } public override void Visit(UnnestOp op, Node n) { Dictionary attrs = new Dictionary(); if (null != op.Var) { attrs.Add("Var", op.Var.Id); } using (new AutoXml(this, op, attrs)) { DumpTable(op.Table); VisitChildren(n); } } public override void Visit(VarDefOp op, Node n) { Dictionary attrs = new Dictionary(); attrs.Add("Var", op.Var.Id); using (new AutoXml(this, op, attrs)) { VisitChildren(n); } } public override void Visit(VarRefOp op, Node n) { using (new AutoString(this, op)) { VisitChildren(n); if (null != op.Type) { WriteString("Type="); WriteString(TypeHelpers.GetFullName(op.Type)); WriteString(", "); } WriteString("Var="); WriteString(op.Var.Id.ToString(CultureInfo.InvariantCulture)); } } #endregion #region dumper helpers private void DumpVar(Var v) { Dictionary attrs = new Dictionary(); attrs.Add("Var", v.Id); ColumnVar cv = v as ColumnVar; if (null != cv) { attrs.Add("Name", cv.ColumnMetadata.Name); attrs.Add("Type", TypeHelpers.GetFullName(cv.ColumnMetadata.Type)); } using (new AutoXml(this, v.GetType().Name, attrs)) { } } private void DumpVars(List vars) { foreach (Var v in vars) { DumpVar(v); } } private void DumpTable(Table table) { Dictionary attrs = new Dictionary(); attrs.Add("Table", table.TableId); if (null != table.TableMetadata.Extent) { attrs.Add("Extent", table.TableMetadata.Extent.Name); } using (new AutoXml(this, "Table", attrs)) { DumpVars(table.Columns); } } #region ColumnMap dumper internal class ColumnMapDumper : ColumnMapVisitor { static internal ColumnMapDumper Instance = new ColumnMapDumper(); /// /// Private constructor /// private ColumnMapDumper() { } #region Helpers /// /// Common CollectionColumnMap code /// private void DumpCollection(CollectionColumnMap columnMap, Dump dumper) { if (columnMap.ForeignKeys.Length > 0) { using (new AutoXml(dumper, "foreignKeys")) { VisitList(columnMap.ForeignKeys, dumper); } } if (columnMap.Keys.Length > 0) { using (new AutoXml(dumper, "keys")) { VisitList(columnMap.Keys, dumper); } } using (new AutoXml(dumper, "element")) { columnMap.Element.Accept(this, dumper); } } /// /// Common code to produce an the attributes for the dumper's XML node /// /// /// private static Dictionary GetAttributes(ColumnMap columnMap) { Dictionary attrs = new Dictionary(); attrs.Add("Type", columnMap.Type.ToString()); return attrs; } #endregion /// /// ComplexTypeColumnMap /// /// /// /// internal override void Visit(ComplexTypeColumnMap columnMap, Dump dumper) { using (new AutoXml(dumper, "ComplexType", GetAttributes(columnMap))) { if (columnMap.NullSentinel != null) { using (new AutoXml(dumper, "nullSentinel")) { columnMap.NullSentinel.Accept(this, dumper); } } VisitList(columnMap.Properties, dumper); } } /// /// DiscriminatedCollectionColumnMap /// /// /// /// internal override void Visit(DiscriminatedCollectionColumnMap columnMap, Dump dumper) { using (new AutoXml(dumper, "DiscriminatedCollection", GetAttributes(columnMap))) { Dictionary attrs = new Dictionary(); attrs.Add("Value", columnMap.DiscriminatorValue); using (new AutoXml(dumper, "discriminator", attrs)) { columnMap.Discriminator.Accept(this, dumper); } DumpCollection(columnMap, dumper); } } /// /// EntityColumnMap /// /// /// /// internal override void Visit(EntityColumnMap columnMap, Dump dumper) { using (new AutoXml(dumper, "Entity", GetAttributes(columnMap))) { using (new AutoXml(dumper, "entityIdentity")) { VisitEntityIdentity(columnMap.EntityIdentity, dumper); } VisitList(columnMap.Properties, dumper); } } /// /// PolymorphicColumnMap /// /// /// /// internal override void Visit(SimplePolymorphicColumnMap columnMap, Dump dumper) { using (new AutoXml(dumper, "SimplePolymorphic", GetAttributes(columnMap))) { using (new AutoXml(dumper, "typeDiscriminator")) { columnMap.TypeDiscriminator.Accept(this, dumper); } Dictionary attrs = new Dictionary(); foreach (KeyValuePair tc in columnMap.TypeChoices) { attrs.Clear(); attrs.Add("DiscriminatorValue", tc.Key); using (new AutoXml(dumper, "choice", attrs)) { tc.Value.Accept(this, dumper); } } using (new AutoXml(dumper, "default")) { VisitList(columnMap.Properties, dumper); } } } /// /// MultipleDiscriminatorPolymorphicColumnMap /// internal override void Visit(MultipleDiscriminatorPolymorphicColumnMap columnMap, Dump dumper) { using (new AutoXml(dumper, "MultipleDiscriminatorPolymorphic", GetAttributes(columnMap))) { using (new AutoXml(dumper, "typeDiscriminators")) { VisitList(columnMap.TypeDiscriminators, dumper); } Dictionary attrs = new Dictionary(); foreach (var tc in columnMap.TypeChoices) { attrs.Clear(); attrs.Add("EntityType", tc.Key); using (new AutoXml(dumper, "choice", attrs)) { tc.Value.Accept(this, dumper); } } using (new AutoXml(dumper, "default")) { VisitList(columnMap.Properties, dumper); } } } /// /// RecordColumnMap /// /// /// /// internal override void Visit(RecordColumnMap columnMap, Dump dumper) { using (new AutoXml(dumper, "Record", GetAttributes(columnMap))) { if (columnMap.NullSentinel != null) { using (new AutoXml(dumper, "nullSentinel")) { columnMap.NullSentinel.Accept(this, dumper); } } VisitList(columnMap.Properties, dumper); } } /// /// RefColumnMap /// /// /// /// internal override void Visit(RefColumnMap columnMap, Dump dumper) { using (new AutoXml(dumper, "Ref", GetAttributes(columnMap))) { using (new AutoXml(dumper, "entityIdentity")) { VisitEntityIdentity(columnMap.EntityIdentity, dumper); } } } /// /// SimpleCollectionColumnMap /// /// /// /// internal override void Visit(SimpleCollectionColumnMap columnMap, Dump dumper) { using (new AutoXml(dumper, "SimpleCollection", GetAttributes(columnMap))) { DumpCollection(columnMap, dumper); } } /// /// SimpleColumnMap /// /// /// /// internal override void Visit(ScalarColumnMap columnMap, Dump dumper) { Dictionary attrs = GetAttributes(columnMap); attrs.Add("CommandId", columnMap.CommandId); attrs.Add("ColumnPos", columnMap.ColumnPos); using (new AutoXml(dumper, "AssignedSimple", attrs)) { } } /// /// SimpleColumnMap /// /// /// /// internal override void Visit(VarRefColumnMap columnMap, Dump dumper) { Dictionary attrs = GetAttributes(columnMap); attrs.Add("Var", ((VarRefColumnMap)columnMap).Var.Id); using (new AutoXml(dumper, "VarRef", attrs)) { } } /// /// DiscriminatedEntityIdentity /// /// /// /// protected override void VisitEntityIdentity(DiscriminatedEntityIdentity entityIdentity, Dump dumper) { using (new AutoXml(dumper, "DiscriminatedEntityIdentity")) { using (new AutoXml(dumper, "entitySetId")) { entityIdentity.EntitySetColumnMap.Accept(this, dumper); } if (entityIdentity.Keys.Length > 0) { using (new AutoXml(dumper, "keys")) { VisitList(entityIdentity.Keys, dumper); } } } } /// /// SimpleEntityIdentity /// /// /// /// protected override void VisitEntityIdentity(SimpleEntityIdentity entityIdentity, Dump dumper) { using (new AutoXml(dumper, "SimpleEntityIdentity")) { if (entityIdentity.Keys.Length > 0) { using (new AutoXml(dumper, "keys")) { VisitList(entityIdentity.Keys, dumper); } } } } } #endregion #endregion internal struct AutoString : IDisposable { private Dump _dumper; internal AutoString(Dump dumper, Op op) { _dumper = dumper; _dumper.WriteString(AutoString.ToString(op.OpType)); _dumper.BeginExpression(); } public void Dispose() { try { _dumper.EndExpression(); } catch (Exception e) { if (!EntityUtil.IsCatchableExceptionType(e)) { throw; } // eat this exception; we don't care if the dumper is failing... } } internal static string ToString(OpType op) { // perf: Enum.ToString() actually is very perf intensive in time & memory switch (op) { case OpType.Aggregate: return "Aggregate"; case OpType.And: return "And"; case OpType.Case: return "Case"; case OpType.Cast: return "Cast"; case OpType.Collect: return "Collect"; case OpType.Constant: return "Constant"; case OpType.ConstantPredicate: return "ConstantPredicate"; case OpType.CrossApply: return "CrossApply"; case OpType.CrossJoin: return "CrossJoin"; case OpType.Deref: return "Deref"; case OpType.Distinct: return "Distinct"; case OpType.Divide: return "Divide"; case OpType.Element: return "Element"; case OpType.EQ: return "EQ"; case OpType.Except: return "Except"; case OpType.Exists: return "Exists"; case OpType.Filter: return "Filter"; case OpType.FullOuterJoin: return "FullOuterJoin"; case OpType.Function: return "Function"; case OpType.GE: return "GE"; case OpType.GetEntityRef: return "GetEntityRef"; case OpType.GetRefKey: return "GetRefKey"; case OpType.GroupBy: return "GroupBy"; case OpType.GroupByInto: return "GroupByInto"; case OpType.GT: return "GT"; case OpType.InnerJoin: return "InnerJoin"; case OpType.InternalConstant: return "InternalConstant"; case OpType.Intersect: return "Intersect"; case OpType.IsNull: return "IsNull"; case OpType.IsOf: return "IsOf"; case OpType.LE: return "LE"; case OpType.Leaf: return "Leaf"; case OpType.LeftOuterJoin: return "LeftOuterJoin"; case OpType.Like: return "Like"; case OpType.LT: return "LT"; case OpType.Minus: return "Minus"; case OpType.Modulo: return "Modulo"; case OpType.Multiply: return "Multiply"; case OpType.MultiStreamNest: return "MultiStreamNest"; case OpType.Navigate: return "Navigate"; case OpType.NE: return "NE"; case OpType.NewEntity: return "NewEntity"; case OpType.NewInstance: return "NewInstance"; case OpType.DiscriminatedNewEntity: return "DiscriminatedNewEntity"; case OpType.NewMultiset: return "NewMultiset"; case OpType.NewRecord: return "NewRecord"; case OpType.Not: return "Not"; case OpType.Null: return "Null"; case OpType.NullSentinel: return "NullSentinel"; case OpType.Or: return "Or"; case OpType.OuterApply: return "OuterApply"; case OpType.PhysicalProject: return "PhysicalProject"; case OpType.Plus: return "Plus"; case OpType.Project: return "Project"; case OpType.Property: return "Property"; case OpType.Ref: return "Ref"; case OpType.RelProperty: return "RelProperty"; case OpType.ScanTable: return "ScanTable"; case OpType.ScanView: return "ScanView"; case OpType.SingleRow: return "SingleRow"; case OpType.SingleRowTable: return "SingleRowTable"; case OpType.SingleStreamNest: return "SingleStreamNest"; case OpType.SoftCast: return "SoftCast"; case OpType.Sort: return "Sort"; case OpType.Treat: return "Treat"; case OpType.UnaryMinus: return "UnaryMinus"; case OpType.UnionAll: return "UnionAll"; case OpType.Unnest: return "Unnest"; case OpType.VarDef: return "VarDef"; case OpType.VarDefList: return "VarDefList"; case OpType.VarRef: return "VarRef"; case OpType.ConstrainedSort: return "ConstrainedSort"; default: Debug.Assert(false, "need to special case enum->string: " + op.ToString()); return op.ToString(); } } } internal struct AutoXml : IDisposable { private string _nodeName; private Dump _dumper; internal AutoXml(Dump dumper, Op op) { _dumper = dumper; _nodeName = AutoString.ToString(op.OpType); Dictionary attrs = new Dictionary(); if (null != op.Type) { attrs.Add("Type", TypeHelpers.GetFullName(op.Type)); } _dumper.Begin(_nodeName, attrs); } internal AutoXml(Dump dumper, Op op, Dictionary attrs) { _dumper = dumper; _nodeName = AutoString.ToString(op.OpType); Dictionary attrs2 = new Dictionary(); if (null != op.Type) { attrs2.Add("Type", TypeHelpers.GetFullName(op.Type)); } foreach (KeyValuePair kv in attrs) { attrs2.Add(kv.Key, kv.Value); } _dumper.Begin(_nodeName, attrs2); } internal AutoXml(Dump dumper, string nodeName) : this(dumper, nodeName, null) { } internal AutoXml(Dump dumper, string nodeName, Dictionary attrs) { _dumper = dumper; _nodeName = nodeName; _dumper.Begin(_nodeName, attrs); } public void Dispose() { _dumper.End(_nodeName); } } } }