The strange case of VSLangProj80.ProjectProperties3.AbsoluteProjectDirectory

When you have a System.Type that is a component, the best way to get its public properties is to use System.ComponentModel.TypeDescriptor.GetProperties(type), rather than System.Type.GetProperties(). This is so because a component type can have a designer which is able to add new properties to the type, and to remove or change existing properties. For example, controls have a Locked property that is added by the designer of controls (ControlDesigner class), it is not a property that belongs to the System.Windows.Forms.Control type.

If the type is not a component, I guess that System.ComponentModel.TypeDescriptor.GetProperties returns the same public properties than System.Type.GetProperties. System.ComponentModel.TypeDescriptor.GetProperties returns a collection of PropertyDescriptor, which has an IsBrowsable property that tells you if a property is browsable or not.

So, the other day I got an strange issue: I was calling System.ComponentModel.TypeDescriptor.GetProperties on the VSLangProj80.ProjectProperties3 type (which is not a component type but my code was called other times with types that are components), to get the properties that are likely to exist in the EnvDTE.Project.Properties collection. And I got in the results the “AbsoluteProjectDirectory” property, that I noticed that didn’t appear in the Object Browser of Visual Studio, despite its IsBrowsable property returning True. How come?

I was aware that VSLangProj80 stuff is not pure .NET but an imported typelib or something like that, but it was not until I used .NET Reflector that I discovered that the get accessor method of the AbsoluteProjectDirectory property has the System.Runtime.InteropServices.TypeLibFuncFlags attribute with the &H40 value:

<DispId(&H2732)> _
ReadOnly Property AbsoluteProjectDirectory As <MarshalAs(UnmanagedType.BStr)> String
   <MethodImpl(MethodImplOptions.InternalCall, MethodCodeType:=MethodCodeType.Runtime), DispId(&H2732), TypeLibFunc(CShort(&H40))> _
   Get
      ...
   End Get
End Property

which matches the FHidden value:

<Serializable, Flags, ComVisible(True)> _
Public Enum TypeLibFuncFlags
   ' Fields
   FBindable = 4
   FDefaultBind = &H20
   FDefaultCollelem = &H100
   FDisplayBind = &H10
   FHidden = &H40
   FImmediateBind = &H1000
   FNonBrowsable = &H400
   FReplaceable = &H800
   FRequestEdit = 8
   FRestricted = 1
   FSource = 2
   FUiDefault = &H200
   FUsesGetLastError = &H80
End Enum

That explained the issue and I was able to tweak my code to deal with that case. BTW, notice that there is also a TypeLibFuncFlags.FNonBrowsable flag.