Getting started

edit

Install the package

Add the Meziantou.Framework.TaggedValues package to your project:

dotnet package add Meziantou.Framework.TaggedValues

The package contains the [ValueTag] attribute, and a Roslyn analyzer with its code fixes for C#. The analyzer runs during the build and in the IDE: there is nothing to register.

The examples of this documentation omit the using directives: they need using Meziantou.Framework.TaggedValues;.

Tag your ids

Add [ValueTag] to the properties, fields and parameters that hold ids. The analyzer then reports the places where a value with one tag meets a value with another tag:

public sealed class Order
{
    [ValueTag("OrderId")] public Guid Id { get; set; }
    [ValueTag("ProjectId")] public Guid ProjectId { get; set; }
}

public sealed class OrderService
{
    public Order Load([ValueTag("OrderId")] Guid orderId) => new() { Id = orderId };

    public void M(Order order)
    {
        _ = order.Id == order.ProjectId; // MFTV0001: values with different tags are compared
        Load(order.ProjectId);           // MFTV0002: a ProjectId flows to a parameter that expects an OrderId
    }
}
  • Id is tagged "OrderId", and ProjectId is tagged "ProjectId". Both stay Guid properties.
  • Comparing an order id with a project id is reported by MFTV0001.
  • Passing a project id to a parameter that expects an order id is reported by MFTV0002.

Values that are not tagged are never reported, so tagging one property or one parameter is enough to start.

Fix the warnings

The fix is usually to use the right value:

_ = order.ProjectId == other.ProjectId; // ok, both are ProjectIds
Load(order.Id);                         // ok, an OrderId flows to an OrderId

Sometimes the value is right and a declaration is tagged incorrectly. The code fix of the warning then changes the tag of the declaration the value flows to. Every message names both declarations and their tags, for instance:

property 'Order.ProjectId' is [ValueTag("ProjectId")] and flows to parameter 'orderId' of 'OrderService.Load' (line 12), which is [ValueTag("OrderId")]

Let the tags flow

You do not need to tag every declaration. A local variable takes the tag of the value it is initialized with, and the tag follows the value through collections, LINQ, await and arithmetic:

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);     // MFTV0002

When a method returns tagged values but its return value is not tagged, the callers lose the tag. The analyzer suggests the missing attribute (MFTV0008).

Next steps