#134218·runtime

VM: (bug) open virtual delegates compare equal but have different hash codes

Author: EgorBoCreated Sep 18, 2026Updated Sep 18, 2026
Labelsarea-VM-coreclr

Description

Two open delegates created over the same virtual slot (from Base.M and from Derived.M) behave inconsistently on main: Release reports Equals == true with different hash codes, violating the Equals/GetHashCode contract and breaking dictionary lookups; Checked reports Equals == false. .NET 10.0.12 is self-consistent.

Repro

csharp
using System;
using System.Collections.Generic;

public class Base { public virtual void M() { } }
public class Derived : Base { public override void M() { } }

public static class Program
{
    public static void Main()
    {
        var d1 = (Action<Derived>)Delegate.CreateDelegate(typeof(Action<Derived>), typeof(Base).GetMethod("M")!);
        var d2 = (Action<Derived>)Delegate.CreateDelegate(typeof(Action<Derived>), typeof(Derived).GetMethod("M")!);

        Console.WriteLine($"Equals={d1.Equals(d2)} hashEq={d1.GetHashCode() == d2.GetHashCode()}");

        var dict = new Dictionary<Action<Derived>, int> { { d1, 1 } };
        Console.WriteLine($"ContainsKey(d2)={dict.ContainsKey(d2)}");
    }
}

Expected

Equals=True hashEq=True
ContainsKey(d2)=True

Actual

main Release:

Equals=True hashEq=False
ContainsKey(d2)=False

main Checked:

Equals=False hashEq=False
ContainsKey(d2)=False

Analysis

In Release, Delegate.Equals treats matching _methodPtrAux virtual-dispatch stubs as equal, and that stub is keyed by slot, so Base.M and Derived.M produce the same stub. Delegate.GetHashCode instead hashes the MethodDesc, which stays distinct. Checked compiles out the _methodPtrAux shortcut and returns false from Equals.

Either way, Equals and GetHashCode must agree.

Regression?

Yes. .NET 10.0.12 reports Equals=True hashEq=True and finds the dictionary key.

Platform

Windows x64, CoreCLR main (Checked and Release); .NET 10.0.12 is unaffected.