Add triple-slash comments + IDE plugin analysis documentation
Co-authored-by: omegaleo <5655201+omegaleo@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,260 @@
|
|||||||
|
# Do We Need IDE Plugins for DocumentationAttribute?
|
||||||
|
|
||||||
|
## The Question
|
||||||
|
|
||||||
|
Should we create plugins for Visual Studio and Rider to read `DocumentationAttribute` directly and display it in IDE tooltips?
|
||||||
|
|
||||||
|
## Quick Answer
|
||||||
|
|
||||||
|
**NO - Plugins are NOT necessary or recommended.** The standard .NET approach with triple-slash comments is better.
|
||||||
|
|
||||||
|
## Why Plugins Are Not The Right Solution
|
||||||
|
|
||||||
|
### 1. Complexity vs. Benefit
|
||||||
|
|
||||||
|
**Plugin Development Effort:**
|
||||||
|
- ✅ Visual Studio plugin: Complex, requires VSIX development
|
||||||
|
- ✅ Rider plugin: Requires Kotlin/Java, ReSharper SDK knowledge
|
||||||
|
- ✅ VS Code plugin: TypeScript/JavaScript, OmniSharp integration
|
||||||
|
- ❌ Maintenance burden: Updates for each IDE version
|
||||||
|
- ❌ Testing: Must test across multiple IDE versions
|
||||||
|
- ❌ Distribution: Users must install plugins manually
|
||||||
|
|
||||||
|
**Standard Approach:**
|
||||||
|
- ✅ Triple-slash comments: Built into language
|
||||||
|
- ✅ XML documentation: MSBuild native support
|
||||||
|
- ✅ Works everywhere: All IDEs, all tools
|
||||||
|
- ✅ Zero installation: Just works
|
||||||
|
- ✅ Zero maintenance: Standard .NET
|
||||||
|
|
||||||
|
### 2. Limited Adoption
|
||||||
|
|
||||||
|
**Plugin Reality:**
|
||||||
|
- Users must discover the plugin exists
|
||||||
|
- Users must manually install it
|
||||||
|
- Users must trust third-party plugin
|
||||||
|
- Users must update it regularly
|
||||||
|
- Many users won't bother
|
||||||
|
|
||||||
|
**Standard Approach:**
|
||||||
|
- Works for everyone immediately
|
||||||
|
- No installation needed
|
||||||
|
- No trust barrier
|
||||||
|
- Standard .NET practice
|
||||||
|
|
||||||
|
### 3. IDE Extension Points Limitations
|
||||||
|
|
||||||
|
#### Visual Studio
|
||||||
|
|
||||||
|
**Documentation Provider API:**
|
||||||
|
```csharp
|
||||||
|
// VS can extend Quick Info via IAsyncQuickInfoSourceProvider
|
||||||
|
// But this is complex and has limitations
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- Only works in VS (not VS Code, not Rider)
|
||||||
|
- Requires COM interop or MEF
|
||||||
|
- Performance concerns
|
||||||
|
- Must handle all edge cases
|
||||||
|
- Complex debugging
|
||||||
|
|
||||||
|
#### JetBrains Rider
|
||||||
|
|
||||||
|
**PSI (Program Structure Interface):**
|
||||||
|
```kotlin
|
||||||
|
// Rider can extend documentation via QuickDoc providers
|
||||||
|
// Requires ReSharper SDK
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- Kotlin/Java development
|
||||||
|
- Different from VS approach
|
||||||
|
- Must understand Rider's PSI
|
||||||
|
- Complex plugin architecture
|
||||||
|
|
||||||
|
#### VS Code
|
||||||
|
|
||||||
|
**Language Server Protocol:**
|
||||||
|
```typescript
|
||||||
|
// Must extend OmniSharp or create custom language server
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- Requires LSP knowledge
|
||||||
|
- Different architecture again
|
||||||
|
- Must integrate with C# extension
|
||||||
|
|
||||||
|
### 4. The Standard Solution Works Better
|
||||||
|
|
||||||
|
**What We Have:**
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value...", null, "code example")]
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the value from the NeoDictionary for the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key to search for</param>
|
||||||
|
/// <param name="value">The value associated with the key, if found</param>
|
||||||
|
/// <returns>True if the key was found, false otherwise</returns>
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
```
|
||||||
|
|
||||||
|
**How It Works:**
|
||||||
|
|
||||||
|
1. **In IDE (Developer View):**
|
||||||
|
- Rider/VS reads `/// <summary>` from source
|
||||||
|
- Shows immediately in tooltips
|
||||||
|
- No plugin needed
|
||||||
|
|
||||||
|
2. **In XML (Consumer View):**
|
||||||
|
- XmlDocGenerator reads DocumentationAttribute
|
||||||
|
- Augments XML with examples, extended info
|
||||||
|
- Consumers get rich documentation
|
||||||
|
|
||||||
|
3. **Result:**
|
||||||
|
- ✅ Best of both worlds
|
||||||
|
- ✅ Zero plugins needed
|
||||||
|
- ✅ Standard .NET practice
|
||||||
|
|
||||||
|
## What DocumentationAttribute Provides
|
||||||
|
|
||||||
|
The attribute is still valuable for:
|
||||||
|
|
||||||
|
### 1. Code Examples
|
||||||
|
```csharp
|
||||||
|
[Documentation(..., codeExample: @"```csharp
|
||||||
|
var dict = new NeoDictionary<string, int>();
|
||||||
|
dict.Add(""key"", 42);
|
||||||
|
```")]
|
||||||
|
```
|
||||||
|
|
||||||
|
This goes into `<example>` in XML, which IDEs show in extended documentation.
|
||||||
|
|
||||||
|
### 2. Structured Metadata
|
||||||
|
```csharp
|
||||||
|
[Documentation(..., args: new[] {
|
||||||
|
"key: The unique identifier",
|
||||||
|
"value: The output value"
|
||||||
|
})]
|
||||||
|
```
|
||||||
|
|
||||||
|
XmlDocGenerator converts this to proper `<param>` tags.
|
||||||
|
|
||||||
|
### 3. Build-Time Generation
|
||||||
|
- Processes entire assembly
|
||||||
|
- Generates consistent documentation
|
||||||
|
- Can enforce patterns
|
||||||
|
- Single source of truth for metadata
|
||||||
|
|
||||||
|
## Alternative: Roslyn Analyzer
|
||||||
|
|
||||||
|
If we want IDE integration, a **Roslyn Analyzer** is better than a plugin:
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
✅ **Works in all IDEs** that support Roslyn (VS, Rider, VS Code)
|
||||||
|
✅ **No installation** - ships with NuGet package
|
||||||
|
✅ **Compile-time warnings** - enforces documentation standards
|
||||||
|
✅ **Code fixes** - can generate `///` from attributes
|
||||||
|
✅ **Standard approach** - many libraries do this
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Analyzer detects missing /// when DocumentationAttribute exists
|
||||||
|
[Documentation("MyMethod", "Does something")]
|
||||||
|
public void MyMethod() { } // Warning: Add /// summary from Documentation attribute
|
||||||
|
|
||||||
|
// Code fix can generate:
|
||||||
|
[Documentation("MyMethod", "Does something")]
|
||||||
|
/// <summary>
|
||||||
|
/// Does something
|
||||||
|
/// </summary>
|
||||||
|
public void MyMethod() { }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class DocumentationAttributeAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
// Detect: Has DocumentationAttribute but no /// summary
|
||||||
|
// Suggest: Add /// summary matching the attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExportCodeFixProvider]
|
||||||
|
public class DocumentationAttributeCodeFix : CodeFixProvider
|
||||||
|
{
|
||||||
|
// Generate /// <summary> from DocumentationAttribute
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Distribution:**
|
||||||
|
- Package with OmegaLeo.HelperLib.Documentation
|
||||||
|
- Automatically available to developers
|
||||||
|
- No separate installation
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
### For Immediate Solution: Add Triple-Slash Comments ✅
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value...")]
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the value from the NeoDictionary for the given key.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Works immediately
|
||||||
|
- Standard .NET practice
|
||||||
|
- Zero maintenance
|
||||||
|
- Universal compatibility
|
||||||
|
|
||||||
|
### For Future Enhancement: Roslyn Analyzer
|
||||||
|
|
||||||
|
Create an analyzer that:
|
||||||
|
1. Warns when `[Documentation]` exists without `///`
|
||||||
|
2. Provides code fix to generate `///` from attribute
|
||||||
|
3. Ships with NuGet package
|
||||||
|
4. Works in all IDEs
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Better than IDE plugins
|
||||||
|
- Works everywhere Roslyn works
|
||||||
|
- Standard extensibility model
|
||||||
|
- Low maintenance
|
||||||
|
|
||||||
|
### Do NOT Create IDE Plugins ❌
|
||||||
|
|
||||||
|
**Reasons:**
|
||||||
|
- Too complex for the benefit
|
||||||
|
- Limited adoption
|
||||||
|
- Maintenance burden
|
||||||
|
- Non-standard approach
|
||||||
|
- Triple-slash comments solve the problem
|
||||||
|
|
||||||
|
## Comparison Matrix
|
||||||
|
|
||||||
|
| Approach | IDE Support | Installation | Maintenance | Adoption | Recommendation |
|
||||||
|
|----------|-------------|--------------|-------------|----------|----------------|
|
||||||
|
| **Triple-slash `///`** | All IDEs | None | None | ✅ Universal | ✅ **USE THIS** |
|
||||||
|
| **XML Files** | All IDEs | None | Low | ✅ Standard | ✅ Already done |
|
||||||
|
| **Roslyn Analyzer** | VS, Rider, VS Code | Auto (NuGet) | Low | ✅ High | ⚠️ Future enhancement |
|
||||||
|
| **VS Plugin** | VS only | Manual | High | ❌ Low | ❌ **DON'T DO** |
|
||||||
|
| **Rider Plugin** | Rider only | Manual | High | ❌ Low | ❌ **DON'T DO** |
|
||||||
|
| **VS Code Plugin** | VS Code only | Manual | Medium | ❌ Low | ❌ **DON'T DO** |
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
**No plugins needed.** The standard .NET approach works better:
|
||||||
|
|
||||||
|
1. ✅ Add `/// <summary>` comments to source code
|
||||||
|
2. ✅ Keep `[Documentation]` attributes for extended metadata
|
||||||
|
3. ✅ XmlDocGenerator augments XML with attribute content
|
||||||
|
4. ✅ Everyone sees documentation in their IDE
|
||||||
|
5. ✅ No installation or plugins required
|
||||||
|
|
||||||
|
If we want to enhance the developer experience, create a **Roslyn Analyzer** (not IDE plugins) that helps keep `///` and `[Documentation]` in sync.
|
||||||
@@ -12,6 +12,9 @@ BenchmarkUtility.Stop(""MyBenchmark"");
|
|||||||
var results = BenchmarkUtility.GetResults(""MyBenchmark"");
|
var results = BenchmarkUtility.GetResults(""MyBenchmark"");
|
||||||
```")]
|
```")]
|
||||||
[Changelog("1.2.0", "Fixed root namespace to OmegaLeo.HelperLib.Helpers.", "January 28, 2026")]
|
[Changelog("1.2.0", "Fixed root namespace to OmegaLeo.HelperLib.Helpers.", "January 28, 2026")]
|
||||||
|
/// <summary>
|
||||||
|
/// Utility class for benchmarking code execution time.
|
||||||
|
/// </summary>
|
||||||
public class BenchmarkUtility
|
public class BenchmarkUtility
|
||||||
{
|
{
|
||||||
private static Dictionary<string, List<long>> _benchmarks = new Dictionary<string, List<long>>();
|
private static Dictionary<string, List<long>> _benchmarks = new Dictionary<string, List<long>>();
|
||||||
@@ -83,12 +86,20 @@ Console.WriteLine($""Average time: {averageTime} ms"");
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Documentation("Start", "Starts or restarts the stopwatch for the given key.", null, null)]
|
[Documentation("Start", "Starts or restarts the stopwatch for the given key.", null, null)]
|
||||||
|
/// <summary>
|
||||||
|
/// Starts or restarts the stopwatch for the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The benchmark identifier</param>
|
||||||
public static void Start(string key)
|
public static void Start(string key)
|
||||||
{
|
{
|
||||||
GetStopwatch(key).Restart();
|
GetStopwatch(key).Restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Documentation("Stop", "Stops the stopwatch for the given key and records the elapsed time.", null, null)]
|
[Documentation("Stop", "Stops the stopwatch for the given key and records the elapsed time.", null, null)]
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the stopwatch for the given key and records the elapsed time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The benchmark identifier</param>
|
||||||
public static void Stop(string key)
|
public static void Stop(string key)
|
||||||
{
|
{
|
||||||
if (!_benchmarks.ContainsKey(key))
|
if (!_benchmarks.ContainsKey(key))
|
||||||
@@ -101,6 +112,11 @@ Console.WriteLine($""Average time: {averageTime} ms"");
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Documentation("GetResults", "Retrieves the list of recorded times for the given key.", null, null)]
|
[Documentation("GetResults", "Retrieves the list of recorded times for the given key.", null, null)]
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the list of recorded times for the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The benchmark identifier</param>
|
||||||
|
/// <returns>List of recorded times in milliseconds</returns>
|
||||||
public static List<long> GetResults(string key)
|
public static List<long> GetResults(string key)
|
||||||
{
|
{
|
||||||
return _benchmarks.ContainsKey(key) ? _benchmarks[key] : new List<long>();
|
return _benchmarks.ContainsKey(key) ? _benchmarks[key] : new List<long>();
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ namespace OmegaLeo.HelperLib.Models
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
[Documentation("NeoDictionary", "Dictionary like class created to make it easier to display dictionaries in game engines like Unity")]
|
[Documentation("NeoDictionary", "Dictionary like class created to make it easier to display dictionaries in game engines like Unity")]
|
||||||
[Changelog("1.2.0", "Fixed root namespace to OmegaLeo.HelperLib.Models.", "January 28, 2026")]
|
[Changelog("1.2.0", "Fixed root namespace to OmegaLeo.HelperLib.Models.", "January 28, 2026")]
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary like class created to make it easier to display dictionaries in game engines like Unity.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">The type of keys in the dictionary</typeparam>
|
||||||
|
/// <typeparam name="TValue">The type of values in the dictionary</typeparam>
|
||||||
public class NeoDictionary<TKey,TValue>
|
public class NeoDictionary<TKey,TValue>
|
||||||
{
|
{
|
||||||
public List<NeoDictionaryItem<TKey, TValue>> Items = new List<NeoDictionaryItem<TKey, TValue>>();
|
public List<NeoDictionaryItem<TKey, TValue>> Items = new List<NeoDictionaryItem<TKey, TValue>>();
|
||||||
@@ -23,6 +28,12 @@ namespace OmegaLeo.HelperLib.Models
|
|||||||
: new Dictionary<TKey, TValue>();
|
: new Dictionary<TKey, TValue>();
|
||||||
|
|
||||||
[Documentation("TryGetValue", "Tries to get the value from the NeoDictionary for the given key.")]
|
[Documentation("TryGetValue", "Tries to get the value from the NeoDictionary for the given key.")]
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the value from the NeoDictionary for the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key to search for</param>
|
||||||
|
/// <param name="value">The value associated with the key, if found</param>
|
||||||
|
/// <returns>True if the key was found, false otherwise</returns>
|
||||||
public bool TryGetValue(TKey key, out TValue value)
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
{
|
{
|
||||||
var item = Items.FirstOrDefault(x => x.Key.Equals(key));
|
var item = Items.FirstOrDefault(x => x.Key.Equals(key));
|
||||||
@@ -39,6 +50,12 @@ namespace OmegaLeo.HelperLib.Models
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Documentation("TryGetValueFromIndex", "Tries to get the value from the NeoDictionary at the given index.")]
|
[Documentation("TryGetValueFromIndex", "Tries to get the value from the NeoDictionary at the given index.")]
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the value from the NeoDictionary at the given index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The zero-based index of the element</param>
|
||||||
|
/// <param name="value">The value at the specified index, if found</param>
|
||||||
|
/// <returns>True if the index is valid, false otherwise</returns>
|
||||||
public bool TryGetValueFromIndex(int index, out TValue value)
|
public bool TryGetValueFromIndex(int index, out TValue value)
|
||||||
{
|
{
|
||||||
if (index < Items.Count)
|
if (index < Items.Count)
|
||||||
@@ -58,6 +75,11 @@ namespace OmegaLeo.HelperLib.Models
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Documentation("Add", "Adds a new NeoDictionaryItem to the NeoDictionary.")]
|
[Documentation("Add", "Adds a new NeoDictionaryItem to the NeoDictionary.")]
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new NeoDictionaryItem to the NeoDictionary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key of the element to add</param>
|
||||||
|
/// <param name="value">The value of the element to add</param>
|
||||||
public void Add(TKey key, TValue value)
|
public void Add(TKey key, TValue value)
|
||||||
{
|
{
|
||||||
Items.Add(new NeoDictionaryItem<TKey, TValue>(key, value));
|
Items.Add(new NeoDictionaryItem<TKey, TValue>(key, value));
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# Why Rider Shows Documentation Attribute Instead of XML Documentation
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
|
||||||
|
When working in the HelperLib repository, hovering over methods in Rider shows:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value...", null, "")]
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of rich IntelliSense documentation.
|
||||||
|
|
||||||
|
## Root Cause: Source Code Takes Priority
|
||||||
|
|
||||||
|
### How IDEs Resolve Documentation
|
||||||
|
|
||||||
|
IDEs like Rider, Visual Studio, and VS Code follow this priority order:
|
||||||
|
|
||||||
|
1. **Triple-slash comments (`///`) in source code** ← HIGHEST PRIORITY
|
||||||
|
2. XML documentation files (`.xml`)
|
||||||
|
3. Decompiled/reflected information
|
||||||
|
4. Raw signature
|
||||||
|
|
||||||
|
### What's Happening in HelperLib
|
||||||
|
|
||||||
|
Current state:
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value...")]
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
{
|
||||||
|
// No /// comments here!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**The test project uses `<ProjectReference>`:**
|
||||||
|
- Rider can access the source code directly
|
||||||
|
- No `///` comments exist in source
|
||||||
|
- Rider shows the raw source code (including the attribute)
|
||||||
|
- XML file is ignored because source is available
|
||||||
|
|
||||||
|
## The Solution: Add Triple-Slash Comments
|
||||||
|
|
||||||
|
### Option 1: Minimal Comments (Recommended)
|
||||||
|
|
||||||
|
Add simple `/// <summary>` that references the documentation:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value from the NeoDictionary for the given key.")]
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the value from the NeoDictionary for the given key.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Rider shows documentation immediately
|
||||||
|
- ✅ Works for developers with source access
|
||||||
|
- ✅ XML generator can still augment with additional info
|
||||||
|
- ✅ Standard .NET practice
|
||||||
|
|
||||||
|
### Option 2: Generated Comments (Advanced)
|
||||||
|
|
||||||
|
Create a Source Generator that reads `DocumentationAttribute` and generates `/// <summary>` comments automatically.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Single source of truth (the attribute)
|
||||||
|
- ✅ No duplication
|
||||||
|
|
||||||
|
**Drawbacks:**
|
||||||
|
- ❌ More complex
|
||||||
|
- ❌ Requires Source Generator infrastructure
|
||||||
|
- ❌ Comments generated at compile-time (not visible in source)
|
||||||
|
|
||||||
|
### Option 3: Accept Current Behavior
|
||||||
|
|
||||||
|
Keep as-is and document that developers see attributes, consumers see XML.
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ No code changes needed
|
||||||
|
- ✅ XML documentation works perfectly for NuGet consumers
|
||||||
|
|
||||||
|
**Drawbacks:**
|
||||||
|
- ❌ Poor developer experience in IDE
|
||||||
|
- ❌ Makes development harder
|
||||||
|
|
||||||
|
## Why This Matters
|
||||||
|
|
||||||
|
### For Library Developers (Us)
|
||||||
|
|
||||||
|
Working on HelperLib with ProjectReference:
|
||||||
|
- ❌ **Without `///`:** See raw attributes, no IntelliSense
|
||||||
|
- ✅ **With `///`:** See rich documentation in IDE
|
||||||
|
|
||||||
|
### For Library Consumers
|
||||||
|
|
||||||
|
Installing HelperLib NuGet package:
|
||||||
|
- ✅ Only have DLL + XML
|
||||||
|
- ✅ Always see rich documentation
|
||||||
|
- ✅ Not affected by source code
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
**Add `/// <summary>` comments to all public APIs.**
|
||||||
|
|
||||||
|
This is standard practice in .NET libraries:
|
||||||
|
- Microsoft does this (see .NET source code)
|
||||||
|
- Popular libraries do this (Newtonsoft.Json, etc.)
|
||||||
|
- Provides best developer experience
|
||||||
|
|
||||||
|
The DocumentationAttribute can still provide additional metadata:
|
||||||
|
- Code examples
|
||||||
|
- Extended remarks
|
||||||
|
- Structured argument descriptions
|
||||||
|
|
||||||
|
The XmlDocGenerator augments the XML with this additional content.
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
1. Add `/// <summary>` to all public types and members
|
||||||
|
2. Keep DocumentationAttribute for extended metadata
|
||||||
|
3. XmlDocGenerator augments XML with attribute data
|
||||||
|
4. Best of both worlds:
|
||||||
|
- Developers see documentation in IDE
|
||||||
|
- XML files have rich content from attributes
|
||||||
|
- Consumers get full documentation
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```csharp
|
||||||
|
[Documentation("BenchmarkUtility", "Utility class for benchmarking code execution time.")]
|
||||||
|
public class BenchmarkUtility
|
||||||
|
{
|
||||||
|
[Documentation("Start", "Starts or restarts the stopwatch for the given key.")]
|
||||||
|
public static void Start(string key)
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```csharp
|
||||||
|
[Documentation("BenchmarkUtility", "Utility class for benchmarking code execution time.", null, @"```csharp
|
||||||
|
BenchmarkUtility.Start(""MyBenchmark"");
|
||||||
|
// Code to benchmark
|
||||||
|
BenchmarkUtility.Stop(""MyBenchmark"");
|
||||||
|
```")]
|
||||||
|
/// <summary>
|
||||||
|
/// Utility class for benchmarking code execution time.
|
||||||
|
/// </summary>
|
||||||
|
public class BenchmarkUtility
|
||||||
|
{
|
||||||
|
[Documentation("Start", "Starts or restarts the stopwatch for the given key.")]
|
||||||
|
/// <summary>
|
||||||
|
/// Starts or restarts the stopwatch for the given key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The benchmark identifier</param>
|
||||||
|
public static void Start(string key)
|
||||||
|
```
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- ✅ Rider shows documentation immediately
|
||||||
|
- ✅ XML file has base documentation
|
||||||
|
- ✅ XmlDocGenerator adds code examples from attribute
|
||||||
|
- ✅ Perfect IDE experience for everyone
|
||||||
|
|
||||||
|
## Automated Solution
|
||||||
|
|
||||||
|
Could create a tool or analyzer that:
|
||||||
|
1. Reads DocumentationAttribute
|
||||||
|
2. Warns if no `/// <summary>` exists
|
||||||
|
3. Suggests adding the summary from the attribute
|
||||||
|
|
||||||
|
This enforces the pattern and maintains single source of truth.
|
||||||
Reference in New Issue
Block a user