Dotneteers.net
All for .net, .net for all!

LearnVSXNow! #23 - Coping with GUIDs

Right now I am in Egypt on a family holiday, I am writing this article in a diving boat between dives. I have just returned from the depth of an amazing dive site called Small Giftun and I have met a friendly family of six great Napoleon fishes. They asked me to say hello to youJ:

 

In my last post I promised to show a few patterns demonstrating how a new Managed Package Framework could be improved by means of usability, less coding for a task, etc. In this article I show you a few patterns to be used to improve the perception of using GUIDs.

What is difficult with GUIDs?

GUIDs (Globally Unique Identifiers) are great due to the fact their name suggests. When creating and assigning a GUID to an object of any type we can be sure that our object has a unique identifier no other object has in the universe. However when we have to write down GUIDs, we sometimes do not feel the value held by their uniqueness. GUIDs are creatures with names not easy to remember.

The Visual Studio and so VS SDK use GUIDs for many purposes. One kind of usage comes from the COM nature: GUIDs are used to identify COM types and interfaces. Using GUIDS for command IDs and context IDs are great from the architect’s point of view, but might have issues for developers.

Mistyping GUIDs

When we create a VSPackage with commands in C#, we have to write down the GUID of the package at least twice, once in the package definition file and once in the .vsct file:

MyPackage.cs:

 

[Guid("BA95C5F5-ED40-43ef-8F0E-23A72AEF63BA")]

public sealed class MyPackage : Package

{

  // ...

}

 

MyPackage.vsct:

 

<?xml version="1.0" encoding="utf-8"?>

<CommandTable xmlns="...">

  <Extern href="stdidcmd.h" mce_href="stdidcmd.h"/>

  <Extern href="vsshlids.h" mce_href="vsshlids.h"/>

  <Extern href="msobtnid.h" mce_href="msobtnid.h"/>

  <Commands package="guidMyPackage">

    <!-- Command definitions -->

  </Commands>

 

  <Symbols>

    <GuidSymbol name="guidMyPackage"

      value="{BA95C5F5-ED40-43ef-8F0E-23A72AEF63BA}" />

  </Symbols>

 

</CommandTable>

When we have to type the same GUID twice, we have the likelihood to mistype them. In the situation above we do not get even a warning message during the build process about mistyped GUIDs, we can find this issue only when recognized that our commands do not work with the package.

We can encapsulate the package GUID string into a static class as a constant and even then System.Guid value represented by the string:

public static class GuidList

{

  public const string MyPackageGuidString =

    "BA95C5F5-ED40-43ef-8F0E-23A72AEF63BA";

  public static readonly Guid MyPackageGuid = new Guid(MyPackageGuidString);

}

This approach would help a lot when we have to put down the same GUID in the C# source, however here we have two representations of the same identifier. When we use the GUID in an attribute, we must use the string representation, because attributes can use only constant values in their parameters known at compile time. In other cases we may use the System.Guid representation.

However, even this static class would not solve the problem of mistyped GUIDs in the .VSCT file.

Using predefined GUIDs

Many Visual Studio service methods require using GUIDs defined by somewhere in VS. For example at certain points we must know the GUID that represents a VS command, a UI context ID, a logical view ID, etc. The VSConstants class in the Microsoft.VisualStudio namespace defines a few of them in order to reference them by a member name. It is good we have them in defined in this class, however, we VSConstants defines less than the one percent of all the GUIDs used in Visual Studio and less than ten percent of frequently used ones.

Actually, we do not have a complete list of GUIDs used somewhere in Visual Studio, often we must look for some workarounds to obtain them.

When we have to reference these GUID values from attributes, we cannot even use the values defined in VSConstants. Attributes expect GUIDs in string form while VSConstants members are System.Guid values.

Referencing to wrong GUIDs

When an attribute or a method parameter expects a GUID value we can use any GUID value without getting any compilation error. It could happen that we use command GUID when a logical view GUID has been expected. Generally we recognize this situation only by the fact that our code does not work. Sometimes it takes hours to find the cause of the issue.

Summarizing GUID issues in VS SDK

—  Issue #1: The same GUID string value has to be typed in the code more than once. Introducing constants solves this issue only partially (no solution for the .vsct file).

—  Issue #2: Attributes decorating types and members can use only the string forms of GUIDs due to the nature of .NET attributes.

—  Issue #3: Finding GUIDs defined and used in Visual Studio is hard, since we do not have a complete list of them. Only a few of them is defined in VSConstants class with good naming conventions in order to find and use them.

—  Issue #4: GUIDs are not distinguished by the semantics of objects or concepts behind them. For example, we can use a command ID where a UI context ID was expected without any compilation time warning or error.

Resolving GUID issues

Almost all issues summarized above can be resolved with a simple trick .NET uses: let’s represent GUIDs with types! For COM interoperability reasons each .NET type has a GUID that can be queried by the Type.GUID property. This GUID is implicitly set by the compiler to a generated GUID or can be set explicitly with the Guid attribute.

What are the opportunities of this approach?

—  Everywhere a GUID is used it can be changed to a System.Type instance. System.Type instances created with the typeof() operator can be used in attributes, because typeof() is evaluated in compile time.

—  A type can have its own metadata that can hold some information beside the simple GUID it represents. This metadata can be used to assign some kind of semantics to the GUID.

—  A type can be used as type parameter in generic types. This behavior also adds some salt to the GUID.

A sample implementation

To show you what power is in this simple trick, let us see a sample implementation that encapsulates Visual Studio UI context identifiers into types. The essence of the solution is here:

public interface IUIContextGuidType { }

 

/// <summary>

/// This class is a name provider for types representing UIContext GUIDs used

/// </summary>

public static class UIContext

{

  [Guid("ADFC4E60-0397-11D1-9F4E-00A0C911004F")]

  public sealed class SolutionBuilding: IUIContextGuidType { }

 

  [Guid("ADFC4E61-0397-11D1-9F4E-00A0C911004F")]

  public sealed class Debugging: IUIContextGuidType { }

 

  [Guid("ADFC4E62-0397-11D1-9F4E-00A0C911004F")]

  public sealed class FullScreenMode: IUIContextGuidType { }

 

  [Guid("ADFC4E63-0397-11D1-9F4E-00A0C911004F")]

  public sealed class DesignMode: IUIContextGuidType { }

 

  [Guid("ADFC4E64-0397-11D1-9F4E-00A0C911004F")]

  public sealed class NoSolution: IUIContextGuidType { }

 

  [Guid("F1536EF8-92EC-443C-9ED7-FDADF150DA82")]

  public sealed class SolutionExists: IUIContextGuidType { }

 

  [Guid("ADFC4E65-0397-11D1-9F4E-00A0C911004F")]

  public sealed class EmptySolution: IUIContextGuidType { }

 

  [Guid("ADFC4E66-0397-11D1-9F4E-00A0C911004F")]

  public sealed class SolutionHasSingleProject: IUIContextGuidType { }

}

The IUIContextGuidType is a markup interface to declare that a type is used as a wrapper type for a Visual Studio UI context GUID. In order to represent the predefined UI contexts, we use the static UIContext class as a container for the GUID equivalent types.

Each nested type represents the GUID with the corresponding attribute and each implements the IUIContextGuidType interface. Because it is a markup interface it has no member to implement.

The intended usage of these types can be demonstrated by the ProvideAutoLoad attribute. This attribute is used by the regpkg.exe utility to register the package automatically load with a specified UI context. The attribute constructor accepts a single GUID parameter representing the UI context. The source code of ProvideAutoLoad attribute is available in the VS SDK folder under VisualStudioIntegration\Common\Source\CSharp\Shell90. Here is actually the full source code of the attribute without comments and other non-relevant elements tuned for C# 3.0:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]

public sealed class ProvideAutoLoadAttribute : RegistrationAttribute

{

  public ProvideAutoLoadAttribute(string cmdUiContextGuid)

  {

    LoadGuid = new Guid(cmdUiContextGuid);

  }

 

  public Guid LoadGuid { get; private set; }

 

  private string RegKeyName

  {

    get

    {

      return string.Format(CultureInfo.InvariantCulture,

        "AutoLoadPackages\\{0}", LoadGuid.ToString("B"));

    }

  }

 

  public override void Register(RegistrationContext context)

  {

    context.Log.WriteLine(string.Format(Resources.Culture,

      Resources.Reg_NotifyAutoLoad, LoadGuid.ToString("B")));

    using (Key childKey = context.CreateKey(RegKeyName))

    {

      childKey.SetValue(context.ComponentType.GUID.ToString("B"), 0);

    }

  }

 

  public override void Unregister(RegistrationContext context)

  {

    context.RemoveValue(RegKeyName, context.ComponentType.GUID.ToString("B"));

  }

}

Because the attribute derives from the RegistrationAttribute class, it is recognized and used by the regpkg.exe utility.

The original ProvideAutoLoad attribute cannot accept a System.Type parameter to represent the UI context GUID, because it does not have the appropriate constructor. Well, let us create our own ProvideAutoLoad version in a new namespace to avoid type name collision. Let’s add the new constructor to the class:

  public ProvideAutoLoadAttribute(Type type)

  {

    if (!typeof(IUIContextGuidType).IsAssignableFrom(type))

    {

      throw new ArgumentException(

        "Only types implementing IUIContextGuidType are accepted in " +

        "ProvideAutoLoad.");

    }

    LoadGuid = type.GUID;

  }

As you see, our constructor not simply assigns the GUID behind the type to the attribute but also checks its semantics. We accept only those types which implement the IUIContextGuidType markup interface; in other cases we raise an exception.

No we can write our package definition just like in the following example:

[ProvideAutoLoad(typeof(UIContext.NoSolution))]

// --- Other registration attributes omitted

public sealed class MyPackage : Package

{

  // ...

}

Do not forget, this time ProvideAutoLoad is the one we defined in our own namespace and not the one in VS SDK. This definition works and our package is properly registered for automatic loading. Now, let’s assume we would use the following definition:

[ProvideAutoLoad(typeof(System.Int32))]

// --- Other registration attributes omitted

public sealed class MyPackage : Package

{

  // ...

}

This code is syntactically correct but semantically is not: System.Int32 (to be precise, the GUID behind System.Int32) does not represent any UI contexts. Our code will compile, however not built. When the build process runs regpkg.exe, we have an error as the following figure illustrates:

 

When the C# compiler creates the assembly from the source code it does not creates an instance of the ProvideAutoLoad attribute, it simply puts the metadata used to initialize the attribute. So, we do not get any compilation error. However, when repkg.exe runs, as soon as its requests the ProvideAutoLoad attribute instance, the .NET framework reads out the metadata from the assembly and instantiates the attribute. The constructor we add to the attributes checks for the semantics of the type representing the GUID. Because System.Int32 is semantically incorrect, we got the message shown in the figure above.

This time we do not have to wait while our package runs to catch this kind of semantic issue, we can recognize it during build time.

Evaluating the solution

Our sample implementation uses a pattern that copes with Issue #1, #2 and #4.

The type representing the GUID is the one location where the GUID should be literally put down, at other locations the type should be used instead of the GUID. However, this patter only works if types, methods and attributes expecting GUIDs accept System.Type parameters. Right now the current VS SDK does not have this behavior; we should change it as the sample implementation illustrated.

The pattern above does not solve Issue #3, but gives a good hint about the GUIDs of VS SDK can be represented by a good type hierarchy.

The solution above fits not for the predefined GUIDs of the VS SDK but also for user defined GUIDs as well. For example, Visual Studio can use user defined UI contexts. Developers can define their UI context IDs working seamlessly with ProvideAutoLoad attributes, as the following sample illustrates:

public static class MyUIContext

{

  [Guid("45EFA5FC-469A-44E5-AA5C-2D2196D1C936")]

  public sealed class MySolutionIsLoaded: IUIContextGuidType { }

 

  [Guid("C2349B2A-F2A8-49FD-82E2-FCC61C5564F3")]

  public sealed class MyProjectHasOpenFiles: IUIContextGuidType { }

}

Where we are?

The COM root of Visual Studio makes it understandable why GUIDs are commonly used in VS SDK. However, the GUID approach is not really close to the programming model we can use in the .NET framework. In this post I showed you a pattern that could make our life with GUIDs easier. The essence of this pattern is to represent a GUID with a .NET type. Adding some semantics to these types (by means of metadata) allows adding semantics to plain GUIDs.


Posted Jun 24 2008, 03:25 PM by inovak
Filed under:

Add a Comment

(required)  
(optional)
(required)  
Remember Me?