Infra, Ops & WorkflowSûr100100/100
New Control API (.NET)
Instructions for adding new public APIs (properties, methods, events, delegates) to existing WinForms controls or components. Covers API issue tracking, PublicAPI file maintenance, property/event conventions, CodeDOM serialization, design-time attributes, and XML documentation.
ou envoie-le directement à ton agent.
Installer dans ton projet
$ curl -fsSL https://arboristhree.xyz/cli/install-skill.mjs -o /tmp/arboris-install-skill.mjs && node /tmp/arboris-install-skill.mjs new-control-api__dotnet-dotnet__src-winforms-github-skills-64f99c75d249.NET
dotnet/dotnet
Contenu à copier
---
name: new-control-api
description: >-
Instructions for adding new public APIs (properties, methods, events,
delegates) to existing WinForms controls or components. Covers API issue
tracking, PublicAPI file maintenance, property/event conventions, CodeDOM
serialization, design-time attributes, and XML documentation.
metadata:
origin: .NET
author: dotnet-winforms
version: "1.0"
---
# Adding New APIs to an Existing WinForms Control or Component
These rules apply when **adding new public or protected members** — properties,
methods, events, delegates, enums, or interfaces — to an existing WinForms
control or component. For creating entirely new controls, or for general coding
standards, see the `coding-standards` skill instead.
> **Golden rule:** Every new public API must be tracked, reviewed, documented,
> and serialization-safe before it ships.
---
## 1. API Issue Tracking
Every new public API surface change requires a corresponding **API proposal
issue** in the `api-suggestion` format, which will be reviewed by the .NET API
review board.
### 1.1 Check for an existing issue
Before creating anything, search the **upstream** repository
(`dotnet/winforms`) for an existing issue with the `api-suggestion` label that
covers the planned API change.
### 1.2 Environment-aware issue management
* **Copilot CLI / Copilot Web-UI (CCA):** You have permission to interact with
the GitHub API. Follow the rules below to check, create, or update issues.
* **VS Code / Visual Studio:** Do **not** attempt to create or modify issues
yourself — the necessary permissions may not be available. Instead, remind
the user to verify and manage the API issue manually.
### 1.3 Creating or updating the API issue
> ⚠️ **NEVER create a new issue in the upstream repo** (`dotnet/winforms`).
1. **If the issue exists upstream and you can modify it** (you are acting on
behalf of a WinForms team member with sufficient permissions), update it
in-place.
2. **If you cannot modify the upstream issue**, check whether a corresponding
issue exists in the **origin** fork. If yes, update it there. If no, create
it there.
3. **If no issue exists at all**, create it in the **origin** fork using the
format described below.
### 1.4 Required issue content
The API issue **must** contain all of the following sections. If any information
is missing, **stop and ask the user** before proceeding with implementation.
**Section 1 — Background and motivation:**
Why is this API needed? What scenario does it enable?
**Section 2 — API Proposal** (C# code block with full public signature, no
method bodies):
```csharp
namespace System.Windows.Forms;
public partial class ExistingControl
{
public Color NewProperty { get; set; }
public event EventHandler? NewPropertyChanged;
protected virtual void OnNewPropertyChanged(EventArgs e);
}
```
**Section 3 — API Usage** (C# code block showing consumption):
```csharp
var control = new ExistingControl();
control.NewProperty = Color.Red;
control.NewPropertyChanged += (s, e) => Console.WriteLine("Changed!");
```
**Section 4 — Alternative Designs:** Other approaches considered.
**Section 5 — Risks:** Breaking changes, perf regressions, etc.
**Section 6 — Will this feature affect UI controls?** Designer support,
accessibility impact, localization needs.
---
## 2. PublicAPI File Tracking
All new **public** and **protected** members must be recorded in the
PublicAPI text files so the Roslyn analyzer can enforce API compatibility.
### 2.1 During development — `PublicAPI.Unshipped.txt`
Add entries for every new API surface to the **Unshipped** file of the
project that contains the new code. For `System.Windows.Forms` controls this
is:
```
src\System.Windows.Forms\PublicAPI.Unshipped.txt
```
### 2.2 Before final release — `PublicAPI.Shipped.txt`
Before snapping for a release, entries are moved from `Unshipped` to
`Shipped`. During development, only touch `Unshipped`.
### 2.3 Entry format
Entries use the **Roslyn PublicAPI format** — one line per accessor, fully
qualified, sorted alphabetically. Key patterns:
```text
# Property (getter + setter on separate lines)
System.Windows.Forms.Control.DataContext.get -> object?
System.Windows.Forms.Control.DataContext.set -> void
# Virtual / override / abstract modifiers prefix the line
virtual System.Windows.Forms.Control.DataContext.get -> object?
virtual System.Windows.Forms.Control.DataContext.set -> void
# Events — handler type as return
System.Windows.Forms.Control.DataContextChanged -> System.EventHandler?
# Protected virtual On-methods
virtual System.Windows.Forms.Control.OnDataContextChanged(System.EventArgs! e) -> void
# Methods with parameters
System.Windows.Forms.Control.SomeMethod(int count, string! name) -> bool
# Constructors
System.Windows.Forms.MyComponent.MyComponent() -> void
# Enum members
System.Windows.Forms.MyEnum.Value1 = 0 -> System.Windows.Forms.MyEnum
System.Windows.Forms.MyEnum.Value2 = 1 -> System.Windows.Forms.MyEnum
```
**Nullable annotations:** `?` = nullable reference, `!` = non-nullable
reference. Value types do not carry these markers unless `Nullable<T>`.
### 2.4 New `override` members must be tracked too
The PublicAPI analyzer (RS0016) treats a **newly introduced `override`** of a public or
protected member as new API surface — even though the base member is already public. Whenever
you **add an `override` that did not previously exist on that type**, add a line for it to
`PublicAPI.Unshipped.txt` with the `override` prefix. This is easy to miss for paint/lifecycle
overrides added to support a feature. Examples:
```text
override System.Windows.Forms.CheckBox.OnPaint(System.Windows.Forms.PaintEventArgs! pevent) -> void
override System.Windows.Forms.CheckBox.Dispose(bool disposing) -> void
override System.Windows.Forms.ButtonBase.OnVisualStylesModeChanged(System.EventArgs! e) -> void
```
> **CI catches this, a plain `dotnet build` may not.** RS0016 is enforced as an **error** under
> the CI/Arcade build (`build.cmd`); a single-project `dotnet build` can report it as 0 warnings.
> Always re-verify API tracking with `build.cmd` (see the `building-code` skill's build tenet).
### 2.5 Related pitfalls when adding members to a control
* **Hiding an inherited member (CS0114):** if your new member intentionally hides an inherited
one (e.g. a `private new bool ShouldSerializePadding()` shadowing `Control.ShouldSerializePadding()`),
you **must** use the `new` keyword, or the CI build fails.
* **`cref` to internal types in another assembly (CS1574):** XML-doc `<see cref="..."/>` cannot
resolve a type that is `internal` in a *different* assembly (even via `InternalsVisibleTo`). Use
`<c>TypeName</c>` (plain code font) instead of a `cref` for such references.
### 2.6 Publicly accessible interfaces
If a new **public or protected interface** is introduced (or an existing one
gains new members), every member that is publicly accessible must also appear
in the PublicAPI file and be part of the API review scope.
---
## 3. New Property Conventions
### 3.1 Property backing storage — PropertyStore (mandatory)
WinForms controls **do not use regular backing fields** for public properties.
Instead, all property values are stored in the control's `Properties`
collection (a `PropertyStore` instance). This is critical for two reasons:
1. **Memory efficiency** — only properties that are explicitly set consume
space. A form with hundreds of controls does not multiply per-property
field overhead.
2. **Constructor ordering** — in an inherited control, the base class
constructor runs first, and virtual methods like `CreateParams` are called
**before** the derived class's constructor body executes. If a property
getter relied on a backing field initialized in the derived constructor,
it would read an uninitialized value. The PropertyStore avoids this because
`GetValueOrDefault` safely returns a default when no value has been stored.
**Declare a static key** for each new property (one per property, shared
across all instances):
```csharp
private static readonly int s_myPropertyProperty = PropertyStore.CreateKey();
```
**Value-type property** (int, bool, Color, enum, struct):
```csharp
public Color MyProperty
{
get => Properties.GetValueOrDefault(s_myPropertyProperty, Color.Empty);
set
{
if (Properties.GetValueOrDefault(s_myPropertyProperty, Color.Empty) != value)
{
Properties.AddOrRemoveValue(s_myPropertyProperty, value, defaultValue: Color.Empty);
OnMyPropertyChanged(EventArgs.Empty);
}
}
}
```
`AddOrRemoveValue` automatically removes the entry when the value equals the
default, keeping the store lean.
**Reference-type property** (object, string, Image):
```csharp
public Image? MyImage
{
get => Properties.GetValueOrDefault<Image>(s_myImageProperty);
set
{
if (Properties.GetValueOrDefault<Image>(s_myImageProperty) != value)
{
Properties.AddOrRemoveValue(s_myImageProperty, value);
OnMyImageChanged(EventArgs.Empty);
}
}
}
```
### 3.2 CodeDOM serialization strategy — mandatory
Every new public property **must** have a serialization strategy so the
WinForms Designer can persist it correctly. Use **one** of these approaches:
| Approach | When to use |
|---|---|
| `[DefaultValue(...)]` | Simple value-type properties with a constant default |
| `[DesignerSerializationVisibility(Hidden)]` | Properties that must not be serialized (e.g., runtime-only, bound) |
| `ShouldSerialize` + `Reset` methods | Complex defaults, reference-type defaults, or ambient properties |
**ShouldSerialize / Reset pattern** (uses PropertyStore):
```csharp
// These methods MUST be private — the Designer finds them by convention.
private bool ShouldSerializeMyProperty()
=> Properties.ContainsKey(s_myPropertyProperty);
private void ResetMyProperty()
=> Properties.RemoveValue(s_myPropertyProperty);
```
### 3.3 Change notification — `On[Property]Changed` + event
Unless explicitly stated otherwise, every new public property requires:
1. A **`protected virtual void On[Property]Changed(EventArgs e)`** method.
2. A corresponding **`[Property]Changed`** event of type `EventHandler?`.
**Event delegate storage** also uses a static key / centralized collection —
see [Section 4.3](#43-event-delegate-storage) below.
```csharp
private static readonly object s_myPropertyChangedEvent = new();
/// <summary>
/// Occurs when the value of <see cref="MyProperty"/> changes.
/// </summary>
[SRCategory(nameof(SR.CatPropertyChanged))]
[SRDescription(nameof(SR.ControlOnMyPropertyChangedDescr))]
public event EventHandler? MyPropertyChanged
{
add => Events.AddHandler(s_myPropertyChangedEvent, value);
remove => Events.RemoveHandler(s_myPropertyChangedEvent, value);
}
/// <summary>
/// Raises the <see cref="MyPropertyChanged"/> event.
/// </summary>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual void OnMyPropertyChanged(EventArgs e)
{
if (Events[s_myPropertyChangedEvent] is EventHandler handler)
{
handler(this, e);
}
}
```
### 3.4 Design-time attributes
Decorate new properties with attributes to support the **Properties window**
and **IntelliSense**. Check existing properties on the same control for
precedent:
| Attribute | Purpose |
|---|---|
| `[SRCategory(nameof(SR.CatXxx))]` | Groups the property in the Properties window |
| `[SRDescription(nameof(SR.XxxDescr))]` | Tooltip in the Properties window |
| `[Browsable(true/false)]` | Show/hide in Properties window |
| `[Bindable(true)]` | Marks the property as data-bindable |
| `[EditorBrowsable(...)]` | Controls IntelliSense visibility |
| `[Localizable(true)]` | Marks the property value as localizable |
**Resource strings:** Category and description strings go in `SR.resx` with
localization-ready keys — follow the naming conventions already present (e.g.,
`CatAppearance`, `CatBehavior`, `CatData`, `ControlOnXxxDescr`).
### 3.5 Naming conventions
* Check the target control and its base classes for naming precedent.
* Property names should follow the same naming style as peer properties.
* If similar properties exist on related controls (e.g., WPF's
`DataContext`), match the established name where it makes sense.
---
## 4. New Event Conventions
### 4.1 Standard events
Use the standard `EventHandler` delegate and `EventArgs.Empty` when no
additional data is needed:
```csharp
public event EventHandler? SomethingHappened;
```
### 4.2 Events with custom data — dedicated EventArgs + Delegate
When the event carries data beyond what `EventArgs` provides, **do not use
generics** (`EventHandler<T>`). Instead, create:
1. A **dedicated `EventArgs` subclass** (e.g., `MyActionEventArgs`).
2. A **dedicated delegate** (e.g., `MyActionEventHandler`).
```csharp
/// <summary>
/// Provides data for the <see cref="Control.MyAction"/> event.
/// </summary>
public class MyActionEventArgs : EventArgs
{
public MyActionEventArgs(string detail) => Detail = detail;
/// <summary>
/// Gets the detail information associated with this event.
/// </summary>
public string Detail { get; }
}
/// <summary>
/// Represents the method that will handle the <see cref="Control.MyAction"/> event.
/// </summary>
public delegate void MyActionEventHandler(object? sender, MyActionEventArgs e);
```
### 4.3 Event delegate storage
Just as properties use `PropertyStore`, event delegates use the inherited
`Events` collection (`EventHandlerList` from `Component`) with **static key
objects** — one object per event, shared across all instances. This avoids
allocating a delegate field per instance for events that are rarely subscribed.
**Declare a static key object** for each event:
```csharp
private static readonly object s_myActionEvent = new();
```
**Declare the event** using custom `add`/`remove` accessors:
```csharp
public event MyActionEventHandler? MyAction
{
add => Events.AddHandler(s_myActionEvent, value);
remove => Events.RemoveHandler(s_myActionEvent, value);
}
```
**Raise the event** in the `On` method by retrieving the delegate from the
collection:
```csharp
protected virtual void OnMyAction(MyActionEventArgs e)
{
if (Events[s_myActionEvent] is MyActionEventHandler handler)
{
handler(this, e);
}
}
```
> **Exception:** Components with only a single event (e.g., `Timer.Tick`) may
> use a regular field-backed delegate instead. For controls — which inherit
> dozens of events from `Control` — always use the `Events` collection.
---
## 5. New Method Conventions
### 5.1 Argument validation
Use throw helpers — never hand-roll null or range checks:
```csharp
ArgumentNullException.ThrowIfNull(parameter);
ArgumentOutOfRangeException.ThrowIfNegative(value);
```
### 5.2 Virtual methods
When adding a method that derived controls should be able to override, make
it `protected virtual`. Follow the naming conventions already in use on the
control's class hierarchy.
---
## 6. XML Documentation
Every new public or protected member **must** have XML documentation. This is
the basis for the official docs.
### 6.1 Minimum requirements
* **`<summary>`** — always present. Concise statement of what the member does.
* **`<param>`** — for every parameter, with a meaningful description.
* **`<returns>`** — for non-void methods.
* **`<exception>`** — for every exception the method can throw.
* **`<value>`** — for properties, when the summary alone is not sufficient.
### 6.2 Non-trivial members
For APIs that are not self-explanatory, also include:
* **`<remarks>`** with `<para>` blocks — design rationale, usage patterns,
threading considerations, inheritance notes.
* **`<example>`** — code samples using `<code>` blocks.
```csharp
/// <summary>
/// Gets or sets the data context for data binding purposes.
/// This is an ambient property.
/// </summary>
/// <remarks>
/// <para>
/// The data context is inherited by child controls that do not have
/// their own <see cref="DataContext"/> set. When a parent's data
/// context changes, <see cref="OnParentDataContextChanged"/> is
/// called on each child.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// var form = new MyForm();
/// form.DataContext = new MyViewModel();
/// </code>
/// </example>
public virtual object? DataContext { get; set; }
```
### 6.3 On-methods and events
```csharp
/// <summary>
/// Raises the <see cref="MyPropertyChanged"/> event.
/// </summary>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
protected virtual void OnMyPropertyChanged(EventArgs e)
```
### 6.4 Formatting
* Indent XML structure with **1 space** per nesting level.
* Use **Unicode characters**, not HTML entities.
* Use `<see cref="..."/>` for cross-references.
* Use `<inheritdoc/>` on overrides when the base documentation is sufficient.
---
## 7. API Stability: Experimental vs. Stable — and Version Guards
### 7.1 New APIs are STABLE by default — do NOT mark them `[Experimental]`
New public APIs ship as **normal, stable APIs by default**. Do **not** add the
`[Experimental(...)]` attribute, a `WFO5xxx` diagnostic ID, or `[WFO5xxx]`
PublicAPI prefixes unless the work item **explicitly** asks for an experimental
API.
> **Never make an API experimental implicitly.** Experimental status is a
> deliberate, requested decision (it changes the customer contract and requires a
> diagnostic ID + suppression to consume). If the context does not explicitly call
> for it, the API is stable.
### 7.2 When an experimental API *is* explicitly requested
Only when the task explicitly requests an experimental API:
1. Add (or reuse) a diagnostic ID in the `WFO500x` group in
`src\System.Windows.Forms.Analyzers\src\System\Windows\Forms\Analyzers\Diagnostics\DiagnosticIDs.cs`
(e.g. `ExperimentalDarkMode = "WFO5001"`, `ExperimentalAsync = "WFO5002"`,
`ExperimentalAsyncDropTarget = "WFO5003"`). New IDs continue the sequence.
2. Decorate the API:
```csharp
[Experimental(DiagnosticIDs.ExperimentalXxx, UrlFormat = DiagnosticIDs.UrlFormat)]
```
3. Prefix every PublicAPI entry for that API with the diagnostic ID, e.g.
`[WFO5001]System.Windows.Forms.SomeNewApi.get -> ...`.
4. Add a row to **both** `docs\analyzers\Experimental.Help.md` and
`docs\list-of-diagnostics.md`.
5. Suppress the diagnostic where the framework itself consumes the API
(`#pragma warning disable WFOxxxx` / `#Disable Warning WFOxxxx` in VB).
When the API later **graduates to stable** (typically the next release), reverse
all five steps: remove the attribute, the `[WFOxxxx]` PublicAPI prefixes, the
suppressions, the docs rows, and the unused diagnostic ID.
### 7.3 Version guards
This repository **single-targets the current in-development .NET** (see
`TargetFramework` / `NetCurrent`), so source is **not** wrapped in
`#if NETxx_0_OR_GREATER` guards — there are none in `System.Windows.Forms`. Do
**not** add `#if NET11_0_OR_GREATER` blocks around new APIs. Add the member
directly:
```csharp
/// <summary>
/// Gets or sets the corner radius for the control's border.
/// </summary>
public int CornerRadius
{
get => Properties.GetValueOrDefault(s_cornerRadiusProperty, 0);
set
{
ArgumentOutOfRangeException.ThrowIfNegative(value);
if (Properties.GetValueOrDefault(s_cornerRadiusProperty, 0) != value)
{
Properties.AddOrRemoveValue(s_cornerRadiusProperty, value, defaultValue: 0);
OnCornerRadiusChanged(EventArgs.Empty);
}
}
}
```
Tests do not need a version guard either.
---
## 8. Checklist Before Submitting
Before considering the implementation complete, verify:
* [ ] API proposal issue exists (upstream or fork) with full proposal format
* [ ] All new public/protected members are in `PublicAPI.Unshipped.txt`
* [ ] API is **stable** (no `[Experimental]`/`WFO5xxx`) unless experimental was
explicitly requested; no `#if NETxx_0_OR_GREATER` guards
* [ ] Property values stored via `PropertyStore` (not backing fields)
* [ ] Every property has a CodeDOM serialization strategy
* [ ] Every property has `On[Property]Changed` + `[Property]Changed` event
(unless explicitly exempted)
* [ ] Event delegates stored via `Events` collection (not field-backed)
* [ ] Design-time attributes are applied (`SRCategory`, `SRDescription`, etc.)
* [ ] Resource strings added to `SR.resx` as XML entries
* [ ] Events with custom data use dedicated `EventArgs` + `Delegate` (no generics)
* [ ] XML documentation on every new public/protected member
* [ ] Naming follows precedent on the control and its base classes
* [ ] Publicly accessible interface members are tracked in PublicAPI files
* [ ] Unit tests cover the new API surface
### 8.1 API issue checklist
The API proposal issue itself should contain the following checklist at the
bottom (to be maintained as part of the issue, not just at PR time):
```markdown
### Status Checklist
- [ ] API proposal has `api-suggestion` label
- [ ] Background, API Proposal, API Usage, and Risks sections are complete
- [ ] API shape has been discussed with the team
- [ ] Review the issue for compatibility with what the API review board expects
- [ ] Change label to `api-ready-for-review`
- [ ] If late in the release cycle, also add the `blocking` label to expedite
the review appointment
- [ ] API review completed — label changed to `api-approved`
```
---
## 9. Resource Strings and Localization
All user-facing strings — categories, descriptions, exception messages — must
be added as resource entries, never hard-coded.
### 9.1 Adding entries to `SR.resx`
Add new entries as XML to the `SR.resx` file in the project. Follow the
existing naming conventions:
| Purpose | Key pattern | Example |
|---|---|---|
| Property category | `Cat[CategoryName]` | `CatAppearance`, `CatData` |
| Property description | `[Control]On[Property]Descr` | `ControlOnMyPropertyChangedDescr` |
| Event description | `[Event]Descr` | `CommandChangedEventDescr` |
| Exception message | `[Context]_[Error]` | `InvalidArgument_OutOfRange` |
### 9.2 Generating localization files
After adding English entries to `SR.resx`, **build the solution once**. The
build automatically generates `.xlf` translation files for all supported
languages from the English originals. In a subsequent pass, localize the
generated `.xlf` entries into their respective languages.
---
## Open Issues
1. **Ambient property pattern:** Properties like `DataContext` that cascade
from parent to children require a dedicated `OnParent[Property]Changed`
propagation pattern. This is out of scope for this Skill — a separate
**Ambient Properties Skill** is being tracked (see upstream issue).Colle ce Markdown dans ton agent ou utilise les boutons ci-dessus pour l’écrire dans ton projet.
Ce que fait New Control API (.NET)
[ ] API proposal has `api-suggestion` label
[ ] Background, API Proposal, API Usage, and Risks sections are complete
[ ] API shape has been discussed with the team
[ ] Review the issue for compatibility with what the API review board expects
Comment utiliser New Control API (.NET)
1
Copie le prompt
Un clic copie le prompt packagé (ou l'envoie à ton agent).
2
L'agent installe le skill
Il ajoute le SKILL.md et ses ressources à ton projet.
3
Activation automatique
Le skill s'active dès que le contexte correspond.
Déclencheurs pour New Control API (.NET)
Dis simplement à ton agent quelque chose comme :
Applique le skill new-control-api__dotnet-dotnet__src-winforms-github-skills-64f99c75d249 à cette tâche
Utilise new-control-api__dotnet-dotnet__src-winforms-github-skills-64f99c75d249 pour améliorer cette implémentation
Passe en revue ce sujet avec new-control-api__dotnet-dotnet__src-winforms-github-skills-64f99c75d249
Skills liés à New Control API (.NET)
Add CLI Command (.NET)
Add or change a dotnet CLI command, subcommand, or option across the relevant CLI projects. USE FOR: adding or changing a dotnet CLI command/subcommand, adding a new Option<T> or Argument<T>, registering a subcommand in the command tree, changing an option's help description or a command's runtime message, wiring a command parser, or updating --help output.Add Dotnet Aot Command (.NET)
Add, enable, or review a dotnet CLI command or feature in the Native AOT CLI (src/Cli/dotnet-aot) and prove its compatibility. USE FOR: migrating a command or option to AOT, reviewing an AOT migration PR, defining conservative eligibility and managed fallback, changing AotSourceFiles.props or AotDependencies.props, validating AOT/managed parity, NativeAOT-publishing tests, checking binary-size impact, or using the dn harness and separated SDK layout. DO NOT USE FOR: resolving IL trim/AOT analyzer warnings alone (use dotnet-aot-compat), running dotnet.Tests incrementally (use incremental-test), or pure managed CLI work.Add Net Analyzer (.NET)
Add, port, or change a .NET code analysis rule (CA####) under src/Microsoft.CodeAnalysis.NetAnalyzers. USE FOR: implementing a new CA analyzer and its code fixer, porting a rule or PR from the retired dotnet/roslyn-analyzers repo, allocating a diagnostic ID from DiagnosticCategoryAndIdRanges.txt, choosing RuleLevel/severity/category, wiring resx + xlf strings, recording the rule in AnalyzerReleases.Unshipped.md, regenerating the analyzer documentation/sarif files, and writing MSTest analyzer/code-fix tests with the VerifyCS/VerifyVB harness. DO NOT USE FOR: NETSDK#### MSBuild diagnostics (src/Tasks), CS####/BC#### compiler diagnostics or IDE#### analyzers (dotnet/roslyn), or CONTAINER#### diagnostics (src/Containers).