Collections, dictionaries and wrappers

edit

Tag the elements of a collection

On a collection, a Nullable<T>, a Task<T>, a ValueTask<T>, a Lazy<T>, a span, or a memory, the tag describes the element or the value, not the container:

[ValueTag("OrderId")] public List<Guid> OrderIds { get; } = [];

public void M(Order order)
{
    OrderIds.Add(order.ProjectId);                        // MFTV0002
    _ = OrderIds.Where(id => id == order.ProjectId);      // MFTV0001
    Load(OrderIds.Where(id => id != Guid.Empty).First()); // ok
}

The tag flows through indexers, foreach, LINQ, lambda parameters (including IQueryable expression trees), ConfigureAwait, WithCancellation, and any generic method whose signature preserves the element type.

A string is not a collection: the tag of a string describes the string, not its characters.

Tag the keys and values of a dictionary

Use Key and Value for dictionaries:

[ValueTag(Key = "OrderId", Value = "ProjectId")]
public Dictionary<Guid, Guid> ProjectIdByOrderId { get; } = [];
  • Either one can be omitted, to tag only the keys or only the values.
  • A dictionary declaration tagged with [ValueTag("Tag")] instead of Key and Value is reported, and so are Key and Value on a type that is not a dictionary (MFTV0005). An attribute cannot combine tags with Key and Value.
  • For a local variable, use the comment // ValueTag Key=OrderId Value=ProjectId, or tag the type arguments. See Tagging local variables.

Keep the tags of projections

Anonymous type properties take the tag of their initializer, so projections keep their tags. A collection initializer takes the tags of the elements it adds:

var rows = orders.Select(order => new { order.Id, order.ProjectId });
foreach (var row in rows)
{
    Load(row.Id);        // ok
    Load(row.ProjectId); // MFTV0002
}

var ids = new List<Guid> { orders[0].Id }; // a list of OrderIds
Load(ids[0]);                              // ok

In the same way, new Dictionary<Guid, Guid> { [order.Id] = order.ProjectId } maps OrderIds to ProjectIds.

Elements that are checked

Adding a value to a tagged collection is a flow: the value must have the tag of the elements (MFTV0002). The elements of an array, a collection expression, or a collection initializer must share a tag (MFTV0003). The arguments of params and the elements of a collection of object are not combined, as they hold values of different kinds, such as the arguments of a log message.