Inherit documentation for positional C# record parameters from an interface
This issue has been moved from a ticket on Developer Community.
Hello!
This is about documentation and what Visual Studio tool tips show for positional record parameters if there's no specific xml-style documentation there for each parameter, but the record implements an interface that has such documentation.
Consider you have this C# code in a Visual Studio (e.g. 16.9.4) code editor window:
public interface IMyInterfaceWithDoc
{
/// <summary>Some words about A</summary>
int A { get; }
/// <summary>Some more words about B</summary>
int B { get; }
}
public record MyRecord(int A, int B) : IMyInterfaceWithDoc
{
public int CalcProduct()
{
return this. A * this. B;
}
}
void Main()
{
var r = new MyRecord(A: 17, B: 3);
int sum = r.A + r.B;
}
Now please observe that Visual Studio will display the documentation inherited from the underlying interface, whenever you hover with the mouse over the record property named "A" or "B", but - sadly - except for the constructor:
For example, here it works just fine (hovering over the "B"):

But here, it doesn't:

What I would like to see:
Like in the other cases, in the absence of a dedicated <param ..> documentation snippet, please show inherited documentation inferred from the interface if the target property (which is auto-defined by a positional parameter) machtes a property in the interface that has docs.
This would be very helpful, in order to have more information (with less typing) when using the constructor somewhere in the code.
Thanks!
Original Comments
Feedback Bot on 5/9/2021, 11:14 PM:
Thank you for taking the time to provide your suggestion. We will do some preliminary checks to make sure we can proceed further. We'll provide an update once the issue has been triaged by the product team.
I think this is worth looking at. Either an automatic approach would do like suggested, and/or an option like this
/// <param name="A" inheritdoc="IMyInterfaceWithDoc.A"/>
which may be useful in many other situations, too.
This is a request to automatically have a parameter for a primary constructor inherit documentation from its implicitly-created associated property. In the meantime, this is possible:
/// <param name="B"><inheritdoc cref="IMyInterfaceWithDoc.B" path="/summary"/></param>
@dotnet/roslyn-compiler Is there an easy way to tell if a primary constructor parameter is associated with an implicitly created property of the same name?
@jcouv Another request for an 'associated' api here for ide purposes for these symbols.
Added labels per https://github.com/dotnet/roslyn/blob/main/docs/contributing/API%20Review%20Process.md
@jcouv for this to be an API proposal, we need to have an actual API shape proposed. @CyrusNajmabadi, I know you have an open PR with a draft: please make an issue following the API request template so we can triage it and talk about it in an upcoming design review.
@sharwell the workaround you specified here does not work in VS 2022 17.1.0 (and in fact, neither does specifying a literal string in the <param> element, so it seems like a regression here). Even if it did, it's an unnecessarily and horribly clunky way to achieve something that should have Just Worked out of the box with zero changes required.
New features are great, unfinished new features not so much.
@IanKemp everyone has a different definition of "finished".
@IanKemp everyone has a different definition of "finished".
A better word would be complete. As in, the design of the record language feature wasn't complete because it didn't consider documentation comments, ergo the implementation wasn't complete either.
It would be nice to support this feature.
However, I don't think that this is a record only problem.
The current behaviour is consistent with class.
Side Note:
<inheritdoc/> works if the signature of the base ctor is the same.
Sample Code
internal static class Intellisencer
{
public static void CheckIntellisence()
{
// Intellisense:
// - ClassA -> "A ctor."
// - propA -> "A prop."
_ = new ClassA(propA: 1);
// Intellisense:
// - ClassB -> "A ctor."
// - propA -> "A prop."
_ = new ClassB(propA: 1);
// Intellisense:
// - ClassB -> ""
// - propA -> ""
// - propB -> ""
_ = new ClassB(propA: 1, propB: 2);
// Intellisense:
// - RecordA -> "A ctor."
// - propA -> "A prop."
_ = new RecordA(propA: 1);
// Intellisense:
// - RecordA -> "A ctor."
// - propA -> "A prop."
_ = new RecordB(propA: 1);
// Intellisense:
// - RecordB -> ""
// - propA -> ""
// - propB -> ""
_ = new RecordB(propA: 1, propB: 2);
}
}
/// <summary>A class.</summary>
internal class ClassA
{
/// <summary>A ctor.</summary>
/// <param name="propA">A prop.</param>
public ClassA(int propA) { this.PropA = propA; }
/// <summary>A property.</summary>
public int PropA { get; }
}
/// <summary>B Class.</summary>
internal class ClassB : ClassA
{
/// <inheritdoc/>
public ClassB(int propA) : this(propA: propA, propB: 0) { }
/// <inheritdoc/>
public ClassB(int propA, int propB) : base(propA: propA) { this.PropB = propB; }
/// <summary>B property.</summary>
public int PropB { get; }
}
/// <summary>A class.</summary>
internal record RecordA
{
/// <summary>A ctor.</summary>
/// <param name="propA">A prop.</param>
public RecordA(int propA) { this.PropA = propA; }
/// <summary>A property.</summary>
public int PropA { get; }
}
/// <summary>B Record.</summary>
internal record RecordB : RecordA
{
/// <inheritdoc/>
public RecordB(int propA) : this(propA: propA, propB: 0) { }
/// <inheritdoc/>
public RecordB(int propA, int propB) : base(propA: propA) { this.PropB = propB; }
/// <summary>B property.</summary>
public int PropB { get; }
}
This is a request to automatically have a parameter for a primary constructor inherit documentation from its implicitly-created associated property. In the meantime, this is possible:
/// <param name="B"><inheritdoc cref="IMyInterfaceWithDoc.B" path="/summary"/></param>
This would be a workable solution if it actually worked. Instead, you get:
// B: <inheritdoc cref="P:IMyInterfaceWithDoc.B" path="/summary"></inheritdoc>
var r = new MyRecord(A: 1, B: 2);
// B: <inheritdoc cref="P:IMyInterfaceWithDoc.B" path="/summary"></inheritdoc>
_ = r.B;