Open sandboxFocusImprove this doc

THR018: Field of a Mutable Struct Type in a Thread-Aware Class

Severity Error
Reported by PostSharp.Patterns.Threading
Message The type of field "<Field>" is not compatible with the [<Aspect>] aspect: mutable struct types are not allowed. Make all fields of the struct read-only, add [ExplicitlySynchronized] to this field or property if access to it is synchronized manually in source code, use [AssumeImmutable], or register a TypeAdapter.

Cause

A threading model checks the accesses to the fields of its class. A field of a struct type holds the struct itself, not a reference, so code can modify a field of the struct in place, for example this.position.Latitude = 0. PostSharp cannot check such modifications, so a threading model does not accept a field whose type is a mutable struct.

PostSharp considers a struct immutable when all its instance fields are read-only and their types are immutable, as in a readonly struct, or when the struct is marked as immutable with the AssumeImmutableAttribute custom attribute or with a registered type adapter. The primitive types, enumerations and the structs of the System namespace, such as DateTime and decimal, are immutable.

<Aspect> is the name of the class of the threading model aspect, for example SynchronizedAttribute.

In the following example, the Vehicle class has the SynchronizedAttribute threading model, and its position field is a Coordinates struct whose properties have setters.

using System;
using PostSharp.Patterns.Threading;

namespace Errors.THR018_Error;

public struct Coordinates
{
    public double Latitude { get; set; }

    public double Longitude { get; set; }
}

[Synchronized]
public class Vehicle
{
    // PostSharp cannot check the modifications made inside a mutable struct.
    private Coordinates position;

    public void MoveTo( double latitude, double longitude )
    {
        this.position = new Coordinates { Latitude = latitude, Longitude = longitude };
    }

    public string Describe()
    {
        var current = this.position;

        return $"Vehicle at {current.Latitude}, {current.Longitude}";
    }
}

public static class Program
{
    public static void Main()
    {
        var vehicle = new Vehicle();
        vehicle.MoveTo( 50.08, 14.42 );
        Console.WriteLine( vehicle.Describe() );
    }
}
error THR018: THR018_Error.cs(17,25): The type of field "Errors.THR018_Error.Vehicle.position" is not compatible with the [SynchronizedAttribute] aspect: mutable struct types are not allowed. Make all fields of the struct read-only, add [ExplicitlySynchronized] to this field or property if access to it is synchronized manually in source code, use [AssumeImmutable], or register a TypeAdapter.

Resolution

  • Make the struct immutable: declare it as a readonly struct, or make all its fields read-only. Properties with an init accessor or with a getter only have read-only backing fields. To change the value, assign a new instance of the struct to the field.
  • When your code synchronizes the accesses to the field by itself, apply the ExplicitlySynchronizedAttribute custom attribute to the field, or to the automatic property.
  • When a struct is immutable in practice, apply the AssumeImmutableAttribute custom attribute to it. For a struct of another assembly, apply the attribute to your assembly with the type of the struct as its argument, or register a TypeAdapter for it.

In the following example, Coordinates is a readonly struct.

using System;
using PostSharp.Patterns.Threading;

namespace Errors.THR018_Fixed;

// All fields of a read-only struct are read-only, so the struct is immutable.
public readonly struct Coordinates
{
    public double Latitude { get; init; }

    public double Longitude { get; init; }
}

[Synchronized]
public class Vehicle
{
    private Coordinates position;

    public void MoveTo( double latitude, double longitude )
    {
        this.position = new Coordinates { Latitude = latitude, Longitude = longitude };
    }

    public string Describe()
    {
        var current = this.position;

        return $"Vehicle at {current.Latitude}, {current.Longitude}";
    }
}

public static class Program
{
    public static void Main()
    {
        var vehicle = new Vehicle();
        vehicle.MoveTo( 50.08, 14.42 );
        Console.WriteLine( vehicle.Describe() );
    }
}
Vehicle at 50.08, 14.42

See Also

Reference

ExplicitlySynchronizedAttribute
AssumeImmutableAttribute
TypeAdapter

Other Resources

Writing Thread-Safe Code with Threading Models
Opting In and Out From Thread Safety