Skip to content

RequireMethodImplToRemainInEffectAttribute #35315

@fadimounir

Description

@fadimounir

This new attribute will be used by the covariant return types and records C# language features posed for C# 9.0.

Today when we have an explicit override to a method (using MethodImpls), this override gets a new vtable slot on the type, and the slot for the overriden method gets updated to point to the new overriding method. Example:

// Pseudo code with some IL
class A {
    void VirtualFunction() { }
}
class B : A {
    void VirtualFunction() { .override A.VirtualFunction }
}

// VTable for type A:
// [0]: A.VirtualFunction

// VTable for type B:
// [0]: B.VirtualFunction
// [1]: B.VirtualFunction

Now when a new derived type explicitly overrides A.VirtualFunction, it would also get a new vtable slot, and only the vtable slot of the method being overriden gets updated with the new override. Example:

class C : B {
    void VirtualFunction() { .override A.VirtualFunction }
}

// VTable for type A:
// [0]: A.VirtualFunction
//
// VTable for type B:
// [0]: B.VirtualFunction
// [1]: B.VirtualFunction
//
// VTable for type C:
// [0]: C.VirtualFunction
// [1]: B.VirtualFunction   -> This slot does not get overriden
// [2]: C.VirtualFunction

With the new records and covariant return feature, a change of behavior is required to vtable slot overrides when a method gets explictly overriden. In the example above, the second slot on type C would also need to be updated to point to the latest override of the method:

// VTable for type C:
// [0]: C.VirtualFunction
// [1]: C.VirtualFunction   -> This slot needs to be updated as well
// [2]: C.VirtualFunction

The reason for this change is to ensure that any virtual call to the method, whether it uses the base signature or derived signature of the method, we always execute the most derived override:

// Given an object of type C:
callvirt A.VirtualFunction -> Executes C.VirtualFunction
callvirt B.VirtualFunction -> Executes C.VirtualFunction
callvirt C.VirtualFunction -> Executes C.VirtualFunction

To achieve this, a new attribute will be introduced and applied to the MethodImpls that need to propagate to all applicable vtable slots: RequireMethodImplToRemainInEffect attribute. Example:

class A {
    void VirtualFunction() { }
}
class B : A {
    void VirtualFunction() { .override A.VirtualFunction }
}
class C : B {
    [RequireMethodImplToRemainInEffect]
    void VirtualFunction() { .override A.VirtualFunction }
}

This attribute will only be applicable to methods with explicit overrides (MethodImpls), and takes no arguments. The proposed namespace for this attribute is the System.Runtime.CompilerServices namespace. Here is the proposed implementation of the attribute:

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public sealed class RequireMethodImplToRemainInEffectAttribute : Attribute
    {
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions