New information has been added to this article since publication.
Refer to the Editor's Update below.

.NET Matters

XML Comments, Late-bound COM, and More

Stephen Toub

Code download available at:NETMatters0406.exe(174 KB)

Q I'd like to use the XML documentation for a method from within that method. Is this possible? I'd like to be able to do the following:

/// <summary>Usage: myApp.exe file1.dat file2.dat ... fileN.dat</summary> public static void Main(string [] args) { if (args.Length == 0) { Console.WriteLine(XmlComments.Current.Summary.InnerText); return; } ••• }

Does any class like this exist in the Microsoft® .NET Framework?

Q I'd like to use the XML documentation for a method from within that method. Is this possible? I'd like to be able to do the following:

/// <summary>Usage: myApp.exe file1.dat file2.dat ... fileN.dat</summary> public static void Main(string [] args) { if (args.Length == 0) { Console.WriteLine(XmlComments.Current.Summary.InnerText); return; } ••• }

Does any class like this exist in the Microsoft® .NET Framework?

A The C# compiler is able to extract XML comments from C# source files. If at compile time, the /doc command-line option (or the equivalent option in the Visual Studio® .NET IDE) is used to specify a target documentation file, all of the comments are extracted from the source files and are written in an XML format to the target file. Visual Studio .NET can then use this file to display IntelliSense® when the XML file is in the same directory as the compiled assembly (the documentation file must also be named the same as the assembly, with a .xml extension). Both the Visual C++® and Visual Basic® compilers will support this feature in Visual Studio 2005.

A The C# compiler is able to extract XML comments from C# source files. If at compile time, the /doc command-line option (or the equivalent option in the Visual Studio® .NET IDE) is used to specify a target documentation file, all of the comments are extracted from the source files and are written in an XML format to the target file. Visual Studio .NET can then use this file to display IntelliSense® when the XML file is in the same directory as the compiled assembly (the documentation file must also be named the same as the assembly, with a .xml extension). Both the Visual C++® and Visual Basic® compilers will support this feature in Visual Studio 2005.

There are no classes in the .NET Framework written specifically for the purpose of mining and working with these XML comments, but functionality exposed from the System.Xml namespace can be used to make that task quite straightforward. In fact, in the code download for this column I've included a class that lets you use the syntax you've described.

In order to retrieve the XML documentation for a specific type or class member, you first need to find the XML file containing that documentation. To do that, you need to know in which assembly the type or class member is declared. Given the correct MemberInfo, you can use the MemberInfo.DeclaringType to get the Type that declares the member (in the case where you want the documentation for a Type, you don't need to obtain its DeclaringType as you already have a valid Type object). The assembly that declared this type can then be found using the Type's Assembly property, and the Assembly instance returned from that property exposes its file location on disk through the Location property. You can then substitute .xml for the extension of the assembly file and load the XML document from that location into an XmlDocument. Note that most of the assemblies in the .NET Framework are loaded from the Global Assembly Cache (GAC), but their XML documentation files are in the runtime directory. If the system fails to load the documentation from the assembly's folder, try the location returned by RuntimeEnvironment.GetRuntimeDirectory (see last month's column for more information).

Figure 1 Accessing XML Comments Programmatically

Figure 1** Accessing XML Comments Programmatically **

Once you have the XmlDocument loaded with the assembly's XML comments, XPath expressions can be used to find the comments for the specified MemberInfo. This approach is diagrammed in Figure 1 and the code that retrieves the XmlNode is shown in Figure 2, creating an XPath expression based on the documentation at Processing the XML File.

Figure 2 Using XPath to Retrieve Comments

private XmlNode GetComments(MemberInfo mi) { Type declType = (mi is Type) ? ((Type)mi) : mi.DeclaringType; XmlDocument doc = LoadAssemblyComments(declType.Assembly); if (doc == null) return null; string xpath; // Handle nested classes string typeName = declType.FullName.Replace("+", "."); // Based on the member type, get the correct xpath query switch(mi.MemberType) { case MemberTypes.NestedType: case MemberTypes.TypeInfo: xpath = "//member[@name='T:" + typeName + "']"; break; case MemberTypes.Constructor: xpath = "//member[@name='M:" + typeName + "." + "#ctor" + CreateParamsDescription( ((ConstructorInfo)mi).GetParameters()) + "']"; break; case MemberTypes.Method: xpath = "//member[@name='M:" + typeName + "." + mi.Name + CreateParamsDescription( ((MethodInfo)mi).GetParameters()); if (mi.Name == "op_Implicit" || mi.Name == "op_Explicit") { xpath += "~{" + ((MethodInfo)mi).ReturnType.FullName + "}"; } xpath += "']"; break; case MemberTypes.Property: xpath = "//member[@name='P:" + typeName + "." + mi.Name + CreateParamsDescription( ((PropertyInfo)mi).GetIndexParameters()) + "']"; break; case MemberTypes.Field: xpath = "//member[@name='F:" + typeName + "." + mi.Name + "']"; break; case MemberTypes.Event: xpath = "//member[@name='E:" + typeName + "." + mi.Name + "']"; break; // Unknown member type, nothing to do default: return null; } // Get the node from the document return doc.SelectSingleNode(xpath); }

I've created the XmlComments class so that the constructor takes the MemberInfo for which you want the comments. The constructor retrieves the XmlNode for the member's comments if one is available and uses XPath queries to parse the results looking for nodes and node lists that represent the summary, method parameters, exceptions that can be thrown, and so on. These nodes and node lists are then made available through public properties on XmlComments such as Summary and Exceptions. In order to support the syntax in your question, I've added a static Current property that uses the StackTrace class to retrieve the frame for the calling method and then uses the associated MethodInfo in order to construct an XmlComments instance:

public static XmlComments Current { get { return new XmlComments( new StackTrace().GetFrame(1).GetMethod()); } }

There are a few corner cases I needed to be aware of when writing this, the most noticeable being accessor methods for properties and events. Accessors don't have their own XML comments, so given a MethodInfo for an accessor I need to be able to access the parent MemberInfo. One approach would be to use simple string manipulation to parse the name of the property or event from the accessor's name (for example, trimming off the "get_" or "set_" from the beginning of the name). I opted for a slightly more robust approach. For a given type, I store a Hashtable that maps accessor MethodInfo objects to their parent MemberInfo (which I create by looping over all properties and events in a Type, adding to the table all accessors found). When I need to check if a method is an accessor, I can query this table; if the MethodInfo exists as a key in the table, then not only is it an accessor but I also have immediate access to the MemberInfo for the parent which I can pass to the GetComments method shown in Figure 2 instead of the original MethodInfo for the accessor.

Of course, this whole solution only works if you build the XML comments for your application. And it's important to remember that the XML comments are not compiled into the assembly but rather into a separate .xml file, which means any solution that uses this technique should be tested with and without the .xml file present, in the correct location, and correctly named.

Q I'm trying to access a COM component at run time, but I don't have an interop assembly for it. Is it still possible for me to use it? I've considered dynamically generating an assembly when I need it, but that seems like overkill. The problem is that I'm writing a generic utility class in .NET and I won't know the ProgID of the COM component at compile time. It does implement IDispatch.

Q I'm trying to access a COM component at run time, but I don't have an interop assembly for it. Is it still possible for me to use it? I've considered dynamically generating an assembly when I need it, but that seems like overkill. The problem is that I'm writing a generic utility class in .NET and I won't know the ProgID of the COM component at compile time. It does implement IDispatch.

A The Type class exposes a method, GetTypeFromProgID, that you can use for this very scenario (you can use Type.GetTypeFromCLSID if you know the CLSID rather than the ProgID). You supply it with the ProgID of your component and it'll return to you a Type that describes it. You can then use the Activator class to create an instance of the component and various methods on Type, such as InvokeMember, SetProperty, and GetProperty, to access it.

A The Type class exposes a method, GetTypeFromProgID, that you can use for this very scenario (you can use Type.GetTypeFromCLSID if you know the CLSID rather than the ProgID). You supply it with the ProgID of your component and it'll return to you a Type that describes it. You can then use the Activator class to create an instance of the component and various methods on Type, such as InvokeMember, SetProperty, and GetProperty, to access it.

To make it easier to work with, I've created the helper class shown in Figure 3 that makes performing the most common operations easier. As an example, the following code creates an instance of the SharePoint.StssyncHandler COM component and queries its GetStssyncAppName method to determine which application on your machine handles the stssync protocol:

using(LateBoundComHelper lb = new LateBoundComHelper("SharePoint.StssyncHandler")) { string stsSyncHandler = (string)lb.Invoke("GetStssyncAppName"); }

With Microsoft Outlook® 2003 installed, stsSyncHandler will most likely be set to "Outlook".

Figure 3 Accessing Late-bound COM Objects

public class LateBoundComHelper : IDisposable { private Type _type; private object _obj; public LateBoundComHelper(string progId) : this(Type.GetTypeFromProgID(progId, true)) { } public LateBoundComHelper(Type t) { if (t == null) throw new ArgumentNullException("t"); _type = t; _obj = Activator.CreateInstance(t); } public object Invoke(string methodName, params object [] args) { ThrowIfDisposed(); return _type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, _obj, args); } public object this[string propertyName] { get { ThrowIfDisposed(); return _type.InvokeMember(propertyName, BindingFlags.GetProperty, null, _obj, null); } set { ThrowIfDisposed(); _type.InvokeMember(propertyName, BindingFlags.SetProperty, null, _obj, new object[]{value}); } } private void ThrowIfDisposed() { if (_obj == null) throw new ObjectDisposedException(GetType().Name); } public void Dispose() { if (_obj != null) { if (_type.IsCOMObject) { while (Marshal.ReleaseComObject(_obj) > 0); } else if (_obj is IDisposable) ((IDisposable)_obj).Dispose(); _obj = null; } } }

As an aside, a similar helper class exists buried in the depths of System.Web. Using your favorite disassembler or decompiler, find the System.Web.Mail.SmtpMail class. SmtpMail declares a nested class named LateBoundAccessHelper that it uses to provide late-bound access to CDONT.NewMail on Windows NT® and also to CDO.Message on Windows® 2000 and higher.

If for some reason you still want to dynamically generate an assembly from a COM type library, you can use the ConvertTypeLibToAssembly method on the System.Runtime.InteropServices.TypeLibConverter class to generate the assembly for you. In fact, the tlbimp.exe and tlbexp.exe utilities included with the .NET Framework SDK are wrappers around this class (tlbimp.exe uses ConvertTypeLibToAssembly, while tlbexp.exe uses ConvertAssemblyToTypeLib). An example of its usage is available at TypeLibConverter.ConvertTypeLibToAssembly Method.

Q My team uses Debug.Assert throughout our Windows Forms app. When an assertion fails, an assertion dialog is displayed. I'd like to be able to control when this dialog is displayed based on certain environmental conditions, but I don't want to have to change every call to Assert to account for this. Is this possible?

Q My team uses Debug.Assert throughout our Windows Forms app. When an assertion fails, an assertion dialog is displayed. I'd like to be able to control when this dialog is displayed based on certain environmental conditions, but I don't want to have to change every call to Assert to account for this. Is this possible?

A As with any programming challenge, there are most likely multiple ways to solve this problem. One solution which works well is to write your own trace listener which derives from DefaultTraceListener. Override its Fail method and in your implementation only call to the base method when you want to allow the assertion to be displayed. Remember that the DefaultTraceListener implementation already only displays the dialog if the calling code has the UIPermissionWindow.SafeSubWindows permission and if the AssertUIEnabled setting is true in the application's diagnostics configuration. Once you have your custom trace listener, you can remove the DefaultTraceListener and substitute yours in its place. You can do it programmatically, like so:

System.Diagnostics.Debug.Listeners.Clear(); System.Diagnostics.Debug.Listeners.Add(new MyCustomTraceListener());

Or you can use a configuration file, as shown here:

<system.diagnostics> <trace autoflush="true" indentsize="0"> <listeners> <clear/> <add name="MyTraceListener" type="NetMatters.MyCustomTraceListener, MyAssembly" /> </listeners> </trace> </system.diagnostics>

A As with any programming challenge, there are most likely multiple ways to solve this problem. One solution which works well is to write your own trace listener which derives from DefaultTraceListener. Override its Fail method and in your implementation only call to the base method when you want to allow the assertion to be displayed. Remember that the DefaultTraceListener implementation already only displays the dialog if the calling code has the UIPermissionWindow.SafeSubWindows permission and if the AssertUIEnabled setting is true in the application's diagnostics configuration. Once you have your custom trace listener, you can remove the DefaultTraceListener and substitute yours in its place. You can do it programmatically, like so:

System.Diagnostics.Debug.Listeners.Clear(); System.Diagnostics.Debug.Listeners.Add(new MyCustomTraceListener());

Or you can use a configuration file, as shown here:

<system.diagnostics> <trace autoflush="true" indentsize="0"> <listeners> <clear/> <add name="MyTraceListener" type="NetMatters.MyCustomTraceListener, MyAssembly" /> </listeners> </trace> </system.diagnostics>

If you decide to take another approach and want to be able to display the assertion dialog at will without relying on the Debug or TraceListeners collection, an easy way is to again use the functionality built into the DefaultTraceListener:

new DefaultTraceListener().Fail();

This will display the assertion regardless of which listeners are currently configured in the listeners collections. Remember, too, that referring to the Debug and Trace listeners collections as separate entities is a bit of a misnomer. Internally, both Debug.Listeners and Trace.Listeners wrap TraceInternal.Listeners, and thus a listener added to one of them is actually added to both. In fact, the real difference between the Trace and Debug classes is the value passed as a parameter to the ConditionalAttribute attached to each method on the classes. This attribute controls the circumstances under which the call sites are compiled into the target Microsoft intermediate language (MSIL).

Q For my application, I need a buffer that only keeps the last n objects added to it. I checked the System.Collections namespace but I couldn't find anything appropriate. Does such a class exist? Is there any way to reuse existing functionality in the .NET Framework to make my implementation easier?

Q For my application, I need a buffer that only keeps the last n objects added to it. I checked the System.Collections namespace but I couldn't find anything appropriate. Does such a class exist? Is there any way to reuse existing functionality in the .NET Framework to make my implementation easier?

A As far as I know, System.Collections.Queue is the closest class in the Framework to what you want. In fact, it's so close that with only a few added lines of code you can create your own class that behaves the way you require.

A As far as I know, System.Collections.Queue is the closest class in the Framework to what you want. In fact, it's so close that with only a few added lines of code you can create your own class that behaves the way you require.

The Queue class implements the classic First In First Out (FIFO) data structure of the same name. Internally, it maintains an array of objects and two integers that mark the head and tail positions within that array. When the Enqueue method is used to add an item to the queue, the internal capacity of the queue is checked to determine whether the array has enough room to store the new item. If it doesn't, the size of the array is increased. After it has confirmed there's enough room, the new item is added at the position specified in the tail integer. The tail integer is then incremented, possibly causing it to wrap around to the beginning of the array; this can happen if items have been dequeued, incrementing the head pointer and leaving a gap at the beginning.

In order to implement the circular buffer, all you need to do is ensure that there's a maximum capacity for the queue and that you never exceed that limit. One solution is to override the Enqueue method and first check to determine whether you've reached the limit. If you have, simply remove an item using the Dequeue method, then proceed by delegating to the base class. The code in Figure 4 shows the basics of the implementation. Note that in order to make this truly robust, you should override and implement other methods like Queue.Clone, which returns a Queue instance, but I'll leave that as an exercise for you.

Figure 4 Implementing a Circular Buffer

public sealed class CircularBuffer : Queue { private int _capacity; public CircularBuffer(int capacity) : base(capacity) { if (capacity <= 0) throw new ArgumentOutOfRangeException("capacity"); _capacity = capacity; } public override void Enqueue(object obj) { if (base.Count == _capacity) base.Dequeue(); base.Enqueue(obj); } public override void TrimToSize() { throw new NotSupportedException(); } }

Q I want to write a regular expression that will detect strings that have only legal combinations of '{' and '}' characters (where every '{' is at some point followed by a matching '}'), but I can't figure out how to express it. Is this even possible? For example, the string "abcd{1234{5678}ef}g" is valid, as is "{}", but "}{" and "abcd{1234{5678{ef}g}" are invalid. Help!

Q I want to write a regular expression that will detect strings that have only legal combinations of '{' and '}' characters (where every '{' is at some point followed by a matching '}'), but I can't figure out how to express it. Is this even possible? For example, the string "abcd{1234{5678}ef}g" is valid, as is "{}", but "}{" and "abcd{1234{5678{ef}g}" are invalid. Help!

A Matching all strings with equal numbers of '{' and '}', regardless of their order, requires a grammar more powerful than regular grammars (those that can be processed by regular expressions). It can easily be done with context-free grammars (CFG), but there are no classes in the Framework for parsing those.

A Matching all strings with equal numbers of '{' and '}', regardless of their order, requires a grammar more powerful than regular grammars (those that can be processed by regular expressions). It can easily be done with context-free grammars (CFG), but there are no classes in the Framework for parsing those.

If all you want to do is find out if there are legally matching '{' and '}' characters, you can write some simple code to walk through each character in the string and enforce the rule manually. Start with a count of 0. Every time you see a '{', increment the count by 1; every time you see a '}', decrement the count by 1. If you ever get a negative count at any step along the way, your string is not a match. If at the end the count equals 0, then it is a match:

private bool IsMatch(string str) { int count=0; for(int i=0; i<str.Length; i++) { if (str[i] == '{') count++; else if (str[i] == '}') count--; if (count < 0) return false; } return count == 0; }

Even if there were Framework classes that provided CFG support, you can't get much better performance than something like what I've just shown, and what you're doing should be clear to anyone maintaining the code (especially if you add comments).

[Editor's Update - 7/20/2004: The System.Text.RegularExpressions.Regex class supports grouping constructs with lookahead and lookbehind modifiers. This allows it to support some grammars more powerful than regular grammars (for more information, see Grouping Constructs). As such, matching all strings with an equal number of properly-ordered left and right brackets is possible using a pattern like the following:

string pattern = @"^((?<openBracket>\{) | [^\{\}] |" + @"(?<closeBracket-openBracket>\}))*" + @"(?(openBracket)(?!))$"; Regex r = new Regex(pattern, RegexOptions.IgnorePatternWhitespace);

However, the IsMatch method shown previously will still perform better than this equivalent Regex.]

Q I'm writing an application that makes HTTP requests to various Web sites using the URLs provided to me by a user. I only want to make the request if the URL is on my local intranet. Is there an easy way to perform this check?

Q I'm writing an application that makes HTTP requests to various Web sites using the URLs provided to me by a user. I only want to make the request if the URL is on my local intranet. Is there an easy way to perform this check?

A The System.Security.Policy namespace provides a Zone class that is used by the common language runtime (CLR) when enforcing Code Access Security (CAS). You can explicitly use the Zone class to perform the desired check:

private static bool IsIntranet(Uri url) { System.Security.Policy.Zone zone = System.Security.Policy.Zone.CreateFromUrl(url.ToString()); return zone.SecurityZone == System.Security.SecurityZone.Intranet; }

URL membership in a zone is based on your Internet Explorer configuration. To configure zones, use the Security tab in the Internet Explorer options panel.

A The System.Security.Policy namespace provides a Zone class that is used by the common language runtime (CLR) when enforcing Code Access Security (CAS). You can explicitly use the Zone class to perform the desired check:

private static bool IsIntranet(Uri url) { System.Security.Policy.Zone zone = System.Security.Policy.Zone.CreateFromUrl(url.ToString()); return zone.SecurityZone == System.Security.SecurityZone.Intranet; }

URL membership in a zone is based on your Internet Explorer configuration. To configure zones, use the Security tab in the Internet Explorer options panel.

A safer approach might be to run the code that makes these requests in a CAS sandboxed environment that only allows requests to intranet URLs. You can read more about sandboxing at Sandboxing code dynamically.

Send your questions and comments to  netqa@microsoft.com.

Stephen Toub is the Technical Editor for MSDN Magazine.