| Severity | Error |
| Reported by | Metalama.Framework |
| Message | The aspect '<Aspect>' cannot override '<Member>' because the C# compiler adds this member to record '<Record>' unconditionally, so it cannot be declared explicitly. Override the strongly typed 'Equals' overload instead. The equality operators and 'Equals(object)' both call it. |
Cause
To override a member that the C# compiler generates, Metalama must declare the member explicitly in the record. The compiler generates some members of a record only when the record doesn't declare them, such as the strongly typed Equals overload, GetHashCode or ToString. Metalama can override these members.
The compiler, however, always adds the following members to a record, even if the record declares them, so a declaration would be a duplicate that the compiler rejects:
Equals(object);- in a derived record, the
Equalsoverload whose parameter is the base record; - the
==and!=operators.
Metalama reports LAMA0552 when an aspect tries to override one of these members, for instance by calling IntroduceMethod with OverrideStrategy.Override for Equals(object).
In the following example, the aspect overrides Equals(object) of the Order record.
1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4
5namespace Doc.LAMA0552.Error;
6
7public class TraceEqualsAttribute : TypeAspect
8{
9 public override void BuildAspect( IAspectBuilder<INamedType> builder )
10 {
11 // Error: the compiler adds Equals(object) to every record, so it cannot be overridden.
12 builder.IntroduceMethod( nameof(this.IntroducedEquals), whenExists: OverrideStrategy.Override );
13 }
14
15 [Template( Name = "Equals" )]
16 public bool IntroducedEquals( object? other )
17 {
18 System.Console.WriteLine( "Comparing." );
19
20 return meta.Proceed();
21 }
22}
23
1namespace Doc.LAMA0552.Error;
2
3[TraceEquals]
Error LAMA0552: The aspect 'TraceEquals' cannot override 'Order.Equals(object?)' because the C# compiler adds this member to record 'Order' unconditionally, so it cannot be declared explicitly. Override the strongly typed 'Equals' overload instead. The equality operators and 'Equals(object)' both call it.
4internal record Order( int Id );
5
Resolution
Override the Equals overload whose parameter is the record itself. As the message says, Equals(object) and the equality operators call this overload, so your override applies to them as well. In the following example, the aspect overrides the strongly typed Equals method.
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using Metalama.Framework.Code.Comparers;
4using System.Linq;
5
6namespace Doc.LAMA0552.Fixed;
7
8public class TraceEqualsAttribute : TypeAspect
9{
10 public override void BuildAspect( IAspectBuilder<INamedType> builder )
11 {
12 // Override the strongly typed Equals method, which the record declares for its own type.
13 var equals = builder.Target.Methods.OfName( "Equals" )
14 .Single( m => m.Parameters[0].Type.Equals( builder.Target, TypeComparison.Default ) );
15
16 builder.With( equals ).Override( nameof(this.OverrideEquals) );
17 }
18
19 [Template]
20 private dynamic? OverrideEquals()
21 {
22 System.Console.WriteLine( "Comparing." );
23
24 return meta.Proceed();
25 }
26}
27
1namespace Doc.LAMA0552.Fixed;
2
3[TraceEquals]
4internal record Order( int Id );
5
1using System;
2using System.Collections.Generic;
3
4namespace Doc.LAMA0552.Fixed;
5
6[TraceEquals]
7internal record Order(int Id)
8{
9 public virtual bool Equals(Order? other)
10 {
11 Console.WriteLine("Comparing.");
12 return (object)this == (object?)other || ((object?)other != null && this.EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(this.Id, other.Id));
13 }
14}