Tagging local variables
editC# does not allow attributes on local variables. Most locals need no tag: they take the tag of the value they are initialized with. When you want to declare the tag of a local, write a ValueTag comment.
Let the local take the tag of its value
A local without a comment takes the tag of its initializer:
var id = order.Id; // id is an OrderId
Load(id); // ok
id = order.ProjectId; // MFTV0002
var pair = (order.Id, order.ProjectId);
Load(pair.Item1); // ok
Load(pair.Item2); // MFTV0002The same goes for the variable of a foreach (the tag of the collection), of an out var (the tag of the parameter), and of a pattern (the tag of the value matched). In a property pattern, a list pattern, a positional pattern, or a deconstruction, the variable takes the tag of the part it matches:
order is { Id: var id }:idis an OrderId.foreach (var (id, projectId) in projectIdByOrderId):idis an OrderId when the dictionary maps OrderIds to ProjectIds.var (id, projectId) = (order.Id, order.ProjectId):idis an OrderId.var (id, projectId) = order:idis an OrderId whenOrderis a record, or has aDeconstructmethod with taggedoutparameters.var (id, projectId) = pair:idis an OrderId whenpairwas created with(order.Id, order.ProjectId).
The element of a tuple, such as pair.Item1, takes the tag of the element of the tuple literal it was created with. This follows copies of the tuple, such as var copy = pair, and nested tuples, such as outer.Item1.Item1. Each element keeps its own tag, so comparing two tuple variables, such as pair == other, or deconstructing one into existing variables, such as (orderId, projectId) = pair, checks each element separately.
Tag a local with a comment
The comment accepts three forms, written as /* ... */ or // ...:
| Comment | Equivalent attribute |
|---|---|
/* ValueTag=OrderId */ or // ValueTag=OrderId | [ValueTag("OrderId")] |
/* ValueTag=OrderId, ProjectId */ or // ValueTag=OrderId, ProjectId | [ValueTag("OrderId", "ProjectId")] |
/* ValueTag Key=OrderId Value=ProjectId */ or // ValueTag Key=OrderId Value=ProjectId | [ValueTag(Key = "OrderId", Value = "ProjectId")] |
- Spaces around
=and,are allowed. - A tag cannot contain spaces,
=, or,, so a//comment cannot continue with other text after the tags. - In the dictionary form,
KeyandValuecan be separated by a space or a comma, either one can be omitted, and each accepts a single tag.
Block comments
A /* ... */ comment must be part of the declaration: before the type, after the type, or next to the name. A comment before or after the type tags every variable of the declaration. A comment next to a name, or right after the comma that precedes it, tags only that variable:
/* ValueTag=OrderId */ Guid id1 = Guid.NewGuid();
Guid /* ValueTag=OrderId */ id2 = Guid.NewGuid();
var id3 /* ValueTag=OrderId */ = Guid.NewGuid();
using var /* ValueTag=OrderFile */ stream = File.OpenRead(path);
Guid /* ValueTag=OrderId */ a = Guid.NewGuid(), b = Guid.NewGuid(); // a and b are OrderIds
Guid c = Guid.NewGuid(), d /* ValueTag=ProjectId */ = Guid.NewGuid(); // only d is a ProjectId
Guid e = Guid.NewGuid(), /* ValueTag=ProjectId */ f = Guid.NewGuid(); // only f is a ProjectId
var /* ValueTag Key=OrderId Value=ProjectId */ projectIdByOrderId = new Dictionary<Guid, Guid>();Line comments
A // comment must be on a line before the statement, and tags every variable the statement declares. Other comments can sit between the // ValueTag comment and the statement, but a comment at the end of the previous line does not count. When a variable also has a /* ... */ comment, that comment wins:
// ValueTag=OrderId
Guid id1 = Guid.NewGuid();
// ValueTag=OrderId
// The order being processed
Guid a = Guid.NewGuid(), b /* ValueTag=ProjectId */ = Guid.NewGuid(); // a is an OrderId, b is a ProjectId
// ValueTag=OrderId
Guid c /* ValueTag=ProjectId */ = Guid.NewGuid(); // MFTV0005 on the // comment, which does not tag anything
// ValueTag Key=OrderId Value=ProjectId
var projectIdByOrderId = new Dictionary<Guid, Guid>();A comment that another comment overrides for every variable it could tag, with other tags, does not tag anything and is reported (MFTV0005).
Other declarations
Comments also work for the variables of foreach, for, out var, patterns, and deconstructions. A // comment works for foreach and for, but not for out var, patterns, and deconstructions, whose statement is not a declaration:
// ValueTag=OrderId
foreach (var id in ids) { }
foreach (Guid /* ValueTag=OrderId */ id in ids) { }
for (int /* ValueTag=OrderIndex */ i = 0; i < count; i++) { }
if (TryGetId(out var /* ValueTag=OrderId */ parsedId)) { }
if (value is Guid /* ValueTag=OrderId */ matchedId) { }
var (orderId /* ValueTag=OrderId */, projectId) = GetIds();When the comment disagrees with the value
When the comment disagrees with the initializer, the collection of a foreach, the parameter of an out var, the value matched by a pattern, or the deconstructed value, the comment wins and the value is reported (MFTV0002). The code fix changes the tag in the comment. When the comment also tags other variables of the declaration, the code fix adds a comment next to the name of the variable instead.
Tag type arguments
A /* ValueTag=... */ comment can also be written in a type argument, to tag the elements of a collection, or the keys and the values of a dictionary, without naming them:
var projectIdByOrderId = new Dictionary</* ValueTag=OrderId */ Guid, /* ValueTag=ProjectId */ Guid>();
var orderIds = new List</* ValueTag=OrderId */ Guid>();
Dictionary</* ValueTag=OrderId */ Guid, /* ValueTag=ProjectId */ Guid> map = GetMap();- The comment can be placed before or after the type argument:
</* ValueTag=OrderId */ Guid, ...>and<Guid /* ValueTag=OrderId */, ...>are equivalent. - The type argument of a collection, a
Nullable<T>, aTask<T>, aValueTask<T>, or aLazy<T>tags its elements or its value. The type arguments of a dictionary or aKeyValuePair<TKey, TValue>tag its keys and its values. Type arguments can be nested:new Dictionary</* ValueTag=OrderId */ Guid, List</* ValueTag=ProjectId */ Guid>>(). - Only the single-tag and union forms are accepted, as the position of the argument already selects the key or the value.
/* ValueTag Key=... */in a type argument is reported (MFTV0005). - The comment applies to the type of a
newexpression, and to the type of a local variable, includingforeach,for,using,outand pattern variables. The tag of anewexpression flows to avarlocal like any initializer. - The elements of a collection initializer and the arguments of the constructor are checked:
new List</* ValueTag=OrderId */ Guid> { projectId }is reported (MFTV0002). - A comment on the variable itself wins over the comments in the type arguments of its declared type.
Comments that are ignored or reported
These comments are ignored:
- comments that do not start with
ValueTag, - prose that starts with
ValueTagbut contains several words and no=, such as// ValueTag is not used here, ///documentation comments.
A ValueTag comment that cannot be parsed, or that does not tag a local variable, does not tag anything and is reported (MFTV0005). This includes a comment on a field, inside an initializer, at the end of a line, before a statement that declares no variable, in the type arguments of a field, a parameter, or a generic method, and in the type arguments of a type that is neither a collection, a dictionary, nor a wrapper, such as Tuple<Guid, Guid>. Use the [ValueTag] attribute on fields, properties, parameters, and return values.