I spent a fair bit of time today cleaning up some existing code in the Help Builder Add-In that deals with importing and exporting XML comments. There were a few minor bugs that occur if the code is formatted certain ways.
What’s really eating at me with this stuff is how inconsistent the VB and C# code models are. The C# model is pretty straight forward. It works pretty consistently but VB does all sorts of crazy shit in the editor. For example, the active document can return you the currently selected CodeElement and it returns to you the location in the document so you can place the cursor there. Help Builder uses this to read and insert XML comments for example (yes, I read them manually even though the are properties for this but VB doesn’t natively support comments hence you can’t retrieve them <g>).
So in CSharp if you’re sitting on a class or in the middle of code inside of a class that isn’t inside a method or member returns a reference to the class. VB returns null. Ok, not too much of a problem. But when you sit on a class that has an inherits clause the VB codemodel returns the line that contains the Inherits line of code rather than the actual line that contains class definition. What the…? <g>
Along a similar vein C# returns attributes as part of a member definition and considers them part of the member. VB on the other hand considers it only if it’s on the same line as the member. So this:
<Xml.Serialization.XmlIgnore()> Public Whatever As String
behaves completely different than this:
<Xml.Serialization.XmlIgnore()> _
Public Whatever As String
even though this is supposedly the same command. This is especially inconsistent when you keep in mind the way the class is handled where it reads something that’s not even part of the same line (ie. the inherits clause).
This differentiation is a drag. There are a number of other things that are bit more subtle. The end result is that there’s a ton of code that needs to be written for each individual object model when the functionality is ALMOST the same but not quite. To give you an idea here’s a simple method that is responsible for inserting the XML Comments in Help Builder’s Add-In:
/// <summary>
/// Inserts an XML comment based on a CodeElement string. If no codeElement
/// is passed the currently active position is translated to a code element.
/// </summary>
/// <param name="XmlComment"></param>
/// <param name="t"></param>
/// <returns></returns>
public bool InsertXmlComment(string XmlComment,CodeElement element)
{
try
{
TextDocument Doc = this.DTE.ActiveDocument.Object("") as TextDocument;
string Delimiter = "/// ";
string AttributePrefix = "[";
bool IsVb = false;
if ( element.Language == CodeModelLanguageConstants.vsCMLanguageVB )
{
Delimiter = "''' ";
AttributePrefix = "<";
IsVb = true;
}
// *** Get the startPoint of the element
TextPoint t = element.GetStartPoint(vsCMPart.vsCMPartWhole).CreateEditPoint();
if (t == null)
return false;
// *** Move to the line of the definition and move to the beginning of the line
Doc.Selection.MoveToPoint(t,false);
Doc.Selection.StartOfLine(vsStartOfLineOptions.vsStartOfLineOptionsFirstColumn,false);
// *** Our EndPoint will be the beginning of the member definition or Attribute list
// *** so we can insert. We'll read backwards up to skip over attributes, then
// *** collect the comments if any to replace.
EditPoint startPoint = Doc.Selection.TopPoint.CreateEditPoint();
EditPoint endPoint = Doc.Selection.TopPoint.CreateEditPoint();
string selText;
// *** Vb workaaround for class header / move up to classheader
if (IsVb && element is CodeClass)
{
selText = startPoint.GetText(50).Trim(' ','\t');
// *** Move up a line because VB places pointer on the inherits line
while (selText.ToLower().IndexOf("class ") < 0 )
{
startPoint.LineUp(1);
startPoint.StartOfLine();
selText = startPoint.GetText(50).Trim(' ','\t');
}
endPoint.MoveToPoint(startPoint);
}
// **** don't move up when we're at the top of the document.
bool TopOfDocument = startPoint.Line == 1;
// *** Move up over Attributes - vb handles this completely differently than C#
if (IsVb && startPoint.Line > 1)
{
while(true)
{
startPoint.LineUp(1);
startPoint.StartOfLine();
selText = startPoint.GetText(endPoint).Trim(' ','\t');
// *** Check for Attribute Prefix
if ( !selText.StartsWith(AttributePrefix) )
{
startPoint.LineDown(1);
startPoint.StartOfLine();
break;
}
// *** Move up our Comment Paste point
endPoint.MoveToPoint(startPoint);
}
}
selText = startPoint.GetText(80).Trim(' ','\t');
// *** Move up over all of the comments
while(startPoint.Line > 1)
{
// *** Move up one line and check for comments
startPoint.LineUp(1);
startPoint.StartOfLine();
selText = startPoint.GetText(endPoint).Trim(' ','\t');
if ( !selText.StartsWith(Delimiter) )
{
startPoint.LineDown(1); // Undo last line up
break;
}
}
startPoint.ReplaceText(endPoint,XmlComment + "\r\n",
(int) vsEPReplaceTextOptions.vsEPReplaceTextAutoformat);
selText = startPoint.GetText(endPoint).Trim(' ','\t');
}
catch
{
return false;
}
return true;
}
A good chunk of this code deals with these two simple differences I mentioned. There are a number of these little differences that are really maddening and conditional code like this is littered through some otherwise pretty generic routines. The really frustrating part is that often times I test only in C# cause that’s what I use in my own code, then switch to VB to test this stuff out and it appears to work Ok – only to find that the differences are very subtle… The issue of attributes slipped by me in VB completely until somebody thankfully pointed it out.
Oh well, can’t have it all. But I really wish the teams at Microsoft would spend a bit more time working TOGETHER. This is certainly not the most important area – data binding in WinForms and Web Forms tops my ‘What the hell where you thinking’ list… <g>
As an aside while fixing these various issues I also built a reverse updater that can take an open source code document and update any help topics that match topics in the Help Builder topic. This is the first step in the related discussion I had last week with Issam at DataDynamics. With this baseline in place it’s now fairly easy to update the source docs from Help Builder. More work is required to allow updates for a whole project, but most of that is just busy work <g>.