Open sandboxFocusImprove this doc

LA0220: Type Argument of a Generic Advice Cannot Be Inferred

Severity Error
Reported by PostSharp
Message The type argument "<TypeParameter>" of the advice "<Advice>" cannot be inferred for "<Target>": <Reason>.

Cause

An advice method can be generic. For each target, PostSharp infers its type arguments from the values that the parameters bind, such as the arguments bound by ArgumentAttribute, with rules similar to the type inference of C#, and then checks the constraints of the type parameters. When it cannot find a type argument for a target, it reports LA0220 on the target. <Reason> says why:

  • The constraint is not satisfied: for example, the type argument is not a reference type, not a non-nullable value type, has no public parameterless constructor, is not an unmanaged type, or does not derive from or implement the type of a constraint.
  • Two bound values of a type parameter have types with no common type, such as int and string.
  • Two ref, out or in parameters of one type parameter bind locations of different types, so the type argument would have to be both.

A violated constraint is reported even when the advice uses the MatchPointcut pointcut, because the target has the signature of the advice. Conflicting types, on the contrary, make MatchPointcut skip the target.

In the following example, the type parameter of the [LogAmount] advice must implement IFormattable, but the first argument of Refund is a string.

using System;
using System.Globalization;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Serialization;

namespace Errors.LA0220_Error;

[PSerializable]
public class LogAmountAttribute : MethodLevelAspect
{
    // The type argument is the type of the first argument, which must implement IFormattable.
    [OnMethodEntryAdvice]
    public static void OnEntry<T>( [DeclarationName] string methodName, [Argument( 0 )] T amount )
        where T : IFormattable
    {
        Console.WriteLine( $"{methodName}: {amount.ToString( "N2", CultureInfo.InvariantCulture )}" );
    }
}

public class PaymentService
{
    [LogAmount]
    public void Charge( decimal amount )
    {
        Console.WriteLine( "Charged." );
    }

    // The first argument is a string, which does not implement IFormattable.
    [LogAmount]
    public void Refund( string paymentId )
    {
        Console.WriteLine( "Refunded." );
    }
}

public static class Program
{
    public static void Main()
    {
        PaymentService service = new PaymentService();
        service.Charge( 1234.5m );
        service.Refund( "P-100" );
    }
}
error LA0220: LA0220_Error.cs(31,17): The type argument "T" of the advice "Errors.LA0220_Error.LogAmountAttribute.OnEntry``1(System.String, ``0)" cannot be inferred for "Errors.LA0220_Error.PaymentService.Refund(System.String)": "string" does not satisfy the constraint "System.IFormattable".

Resolution

  • Apply the aspect only to targets whose bound values fit the type parameters and their constraints.
  • Relax the constraint of the type parameter, or bind another value, for example another argument.
  • In a type-level aspect, write one advice per kind of target and select them with the MatchPointcut pointcut, when the targets differ by their signature.

In the following example, the aspect is applied only to Charge, whose argument is an amount.

using System;
using System.Globalization;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Serialization;

namespace Errors.LA0220_Fixed;

[PSerializable]
public class LogAmountAttribute : MethodLevelAspect
{
    // The type argument is the type of the first argument, which must implement IFormattable.
    [OnMethodEntryAdvice]
    public static void OnEntry<T>( [DeclarationName] string methodName, [Argument( 0 )] T amount )
        where T : IFormattable
    {
        Console.WriteLine( $"{methodName}: {amount.ToString( "N2", CultureInfo.InvariantCulture )}" );
    }
}

public class PaymentService
{
    [LogAmount]
    public void Charge( decimal amount )
    {
        Console.WriteLine( "Charged." );
    }

    // The aspect is applied only to methods whose first argument is an amount.
    public void Refund( string paymentId )
    {
        Console.WriteLine( "Refunded." );
    }
}

public static class Program
{
    public static void Main()
    {
        PaymentService service = new PaymentService();
        service.Charge( 1234.5m );
        service.Refund( "P-100" );
    }
}
Charge: 1,234.50
Charged.
Refunded.

See Also

Reference

ArgumentAttribute
MatchPointcut

Other Resources

Advice Parameters