InheritDoc
Automatically replace <inheritdoc /> tags in .NET XML comments with inherited documentation at build time
Install / Use
/learn @saucecontrol/InheritDocREADME
InheritDoc
This MSBuild Task automatically replaces <inheritdoc /> tags in your .NET XML documentation with the actual inherited docs.
How to Use It
-
Add some
<inheritdoc />tags to your XML documentation comments.This tool’s handling of
<inheritdoc />tags is based on the design document used for Roslyn's support in Visual Studio, which is in turn based on the<inheritdoc />support in Sandcastle Help File Builder (SHFB). -
Add the SauceControl.InheritDoc NuGet package reference to your project.
This is a development-only dependency; it will not be deployed with or referenced by your compiled app/library.
-
Build your project as you normally would.
The XML docs will be post-processed automatically with each non-debug build, whether you use Visual Studio, dotnet CLI, or anything else that hosts the MSBuild engine.
Additional Features
-
Updates the contents of inherited docs to replace
paramandtypeparamnames that changed in the inheriting type or member. -
Supports trimming your published XML doc files of any types or members not publicly visible in your API.
-
Validates your usage of
<inheritdoc />and warns you if no documentation exists or if yourcrefs orpaths are incorrect.
How it Works
The InheritDoc task inserts itself between the Compile and CopyFilesToOutputDirectory steps in the MSBuild process. It uses the arguments passed to the compiler to find your assembly, the XML doc file, and all referenced assemblies, and processes it to replace <inheritdoc /> tags. The output of InheritDoc is then written to your output (bin) directory and is used for the remainder of your build process. If you have further steps, such as building a NuGet package, the updated XML file will used in place of the original, meaning <inheritdoc /> Just Works™.
This enhances the new support for <inheritdoc /> in Roslyn (available starting in VS 16.4), making it available to all downstream consumers of your documentation. When using tools such as DocFX, you will no longer be subject to limitations around <inheritdoc /> tag usage because the documentation will already have those tags replaced with the upstream docs.
Requirements
InheritDoc requires MSBuild 16.0 or greater, which is included with the .NET SDK or with Visual Studio 2019 or later.
Some Examples
Click to expand samples:
<details> <summary>Basic <code><inheritdoc /></code> Usage (Automatic Inheritance)</summary>Consider the following C#
/// <summary>Interface IX</summary>
public interface IX
{
/// <summary>Method X</summary>
void X();
}
/// <inheritdoc />
public interface IY : IX
{
/// <summary>Method Y</summary>
void Y();
}
/// <summary>Class A</summary>
public class A : IY
{
void IX.X() { }
/// <inheritdoc />
public virtual void Y() { }
/// <summary>Method M</summary>
/// <typeparam name="T">TypeParam T</typeparam>
/// <param name="t">Param t</param>
/// <returns>
/// Returns value <paramref name="t" />
/// of type <typeparamref name="T" />
/// </returns>
public virtual T M<T>(T t) => t;
/// <summary>Method P</summary>
private void P() { }
/// <summary>Overloaded Method O</summary>
/// <param name="s">Param s</param>
/// <param name="t">Param t</param>
/// <param name="u">Param u</param>
public static void O(string[] s, string t, string u) { }
/// <inheritdoc cref="O(string[], string, string)" />
public static void O(string[] s) { }
}
/// <inheritdoc />
public class B : A
{
/// <inheritdoc />
public override void Y() { }
/// <inheritdoc />
public override TValue M<TValue>(TValue value) => value;
}
Once processed, the output XML documentation will look like this (results abbreviated and comments added manually to highlight features)
<member name="T:IX">
<summary>Interface IX</summary>
</member>
<member name="M:IX.X">
<summary>Method X</summary>
</member>
<member name="T:IY">
<summary>Interface IX</summary> <!-- inherited from IX -->
</member>
<member name="M:IY.Y">
<summary>Method Y</summary>
</member>
<member name="T:A">
<summary>Class A</summary>
</member>
<member name="M:A.Y">
<summary>Method Y</summary> <!-- inherited from IY -->
</member>
<member name="M:A.M``1(``0)">
<summary>Method M</summary>
<typeparam name="T">TypeParam T</typeparam>
<param name="t">Param t</param>
<returns>
Return value <paramref name="t" />
of type <typeparamref name="T" />
</returns>
</member>
<!-- private method A.P doc removed -->
<member name="M:A.O(System.String[],System.String,System.String)">
<summary>Overloaded Method O</summary>
<param name="s">Param s</param>
<param name="t">Param t</param>
<param name="u">Param u</param>
</member>
<member name="M:A.O(System.String[])">
<summary>Overloaded Method O</summary> <!-- inherited (by cref) from overload -->
<param name="s">Param s</param>
<!-- unused parameters automatically removed -->
</member>
<member name="T:B">
<summary>Class A</summary> <!-- inherited from A -->
</member>
<member name="M:B.Y">
<summary>Method Y</summary> <!-- inherited from IY (recursively through A) -->
</member>
<member name="M:B.M``1(``0)">
<summary>Method M</summary> <!-- inherited from A -->
<typeparam name="TValue">TypeParam T</typeparam> <!-- typeparam updated to match override's name -->
<param name="value">Param t</param> <!-- param updated to match override's name -->
<returns>
Returns value <paramref name="value" /> <!-- paramref and typeparamref updated as well -->
of type <typeparamref name="TValue" />
</returns>
</member>
<member name="M:A.IX#X"> <!-- explicit interface implementation doc added automatically -->
<summary>Method X</summary>
</member>
</details>
<details>
<summary>Explicit Inheritance</summary>
InheritDoc also supports the path attribute defined in the Roslyn draft design doc, which is analogous to the select attribute in SHFB.
In this example, we define a custom Exception class that for some reason doesn't inherit from System.Exception and yet we want to use its documentation anyway.
public class ExceptionForSomeReasonNotInheritedFromSystemException
{
/// <inheritdoc cref="Exception(string)" />
/// <param name="theErrorMessage"><inheritdoc cref="Exception(string)" path="/param[@name='message']/node()" /></param>
ExceptionForSomeReasonNotInheritedFromSystemException(string theErrorMessage) { }
}
Outputs:
<member name="M:ExceptionForSomeReasonNotInheritedFromSystemException.#ctor(System.String)">
<summary>Initializes a new instance of the <see cref="T:System.Exception"></see> class with a specified error message.</summary>
<param name="theErrorMessage">The message that describes the error.</param>
</member>
Notice the param element for message was excluded automatically because there was no matching parameter on the target constructor, however with a nested <inheritdoc /> and a custom selector, we were able to extract the contents from that param element into a new one with the correct name.
Although the .NET compilers don't allow adding namespace documentation comments, some tools (including SHFB) have a convention for declaring them in code. InheritDoc follows this convention.
Note that both the [CompilerGenerated] attribute and the class name NamespaceDoc are required by InheritDoc.
namespace InheritDocTest
{
/// <summary>Namespace InheritDocTest</summary>
[CompilerGenerated] internal class NamespaceDoc { }
}
Will output:
<member name="N:InheritDocTest">
<summary>Namespace InheritDocTest</summary>
</member>
</details>
Configuration
Enabling/Disabling InheritDoc
InheritDoc is enabled by default for all non-debug builds. It can be enabled or disabled explicitly by setting the InheritDocEnabled MSBuild property in your project.
<PropertyGroup>
<InheritDocEnabled>false</InheritDocEnabled>
</PropertyGroup>
Alternatively, you can conditionally include the NuGet package only for specific configurations.
<ItemGroup Condition="'$(Configuration)'=='Dist'">
<PackageReference Include="SauceControl.InheritDoc" Version="2.*" PrivateAssets="all" />
</ItemGroup>
*NuGet tools may include a more verbose version of the PackageReference tag when you add the package to your project. The above example is all that's actually necessary.
