Even more ReSharper Annotations hackery: marking 3rd party code as obsolete

As part of transitioning to a new API, I wanted a way to mark the old API as obsolete, so that Visual Studio (or ReSharper) would flag using of the old API as an error.

Normally, the ObsoleteAttribute does exactly that – marks certain methods or types as obsolete, and has a constructor overload to specify whether the usage will be treated as an error or not. Unfortunately, in my case there was no source code for the old API (it was used as a 3rd-party DLL), and there’s no way to apply the Obsolete attribute to external DLLs without modifying the assembly using IL-rewriting tools.

It had occurred to me that perhaps this would be possible to achieve using one of ReSharper’s External Annotations, but there are no built-in annotations for this kind of task. The annotations mechanism is used to decorate various .NET types and methods with special attributes that provide ReSharper with additional information. Turns out, it is possible to use the same mechanism to apply the Obsolete attribute as well! This way, ReSharper could mark the usage as an error, even if it’s not really a compilation error (which is good enough for me!).

I previously wrote about how to add external annotations using XML files. According to ReSharper’s documentation, external annotation XMLs can be placed in one of the following locations:

  • [ReSharper install directory]\Bin\ExternalAnnotations\[Assembly name].xml
  • [ReSharper install directory]\Bin\ExternalAnnotations\[Assembly name]\[Any name].xml, where [Assembly name] is the assembly name without the version
  • Alongside the assembly, named [Assembly name].ExternalAnnotations.xml

Since the old assembly was under my control, I chose the 3rd option, to place the annotations XML next to the assembly, and in the source control. This way, every team member that uses ReSharper will have this annotation enabled by simply updating to the latest source.

The external annotations XMLs are the same format as XML documentation files, generated by Visual Studio when you build your assembly. Supposing our assembly name is Acme.OldServiceBusImpl.dll, and the type we want to make obsolete is called EventBus, we need to create a file called Acme.OldServiceBusImpl.ExternalAnnotations.xml, and place it beside the DLL. The file has the following content:

<assembly name="Acme.OldEventBusImpl">
  <member name="T:Acme.OldEventBusImpl.EventBus">
    <attribute ctor="M:System.ObsoleteAttribute.#ctor(System.String,System.Boolean)">
      <argument>Use Acme.NewServiceBusImpl.ServiceBus instead.</argument>
      <argument>true</argument>
    </attribute>
  </member>
</assembly>

This specifies that for an assembly named Acme.OldServiceBusImpl, having a type Acme.OldEventBusImpl.EventBus, add an attribute instance of ObsoleteAttribute with the constructor overload ObsoleteAttribute(string, bool), the boolean indicating the usage is considered an error.

After adding the XML file and reloading your project, ReSharper will now mark the usage of this type as an error! Your code will still compile, but you’ll get red on you!

Happy hacking!

  • http://devtalk.net Dmitri

    I suppose a natural evolution of this is an idea of automated refactorings to implement changes. For example, in the above we could find usages of EventBus and replace them with ServiceBus. Of course, it’s not as easy as it sounds – you have to perform mapping at a class level, kind of like an equivalence table. Still, very doable.

    • http://www.hmemcpy.com/blog/ Igal Tabachnik

      Yup. @orangy suggested that this is also doable with SSR. My immediate goal was to provide a warning to other team members who, at the time being, should not try and use the old API until it was completely replaced. I believe this served its purpose.