#134219·runtime

VM: (bug) Delegate.Method on an open delegate to an inherited private generic-base method pollutes the derived type's reflection cache

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

Description

Reading Delegate.Method on an open-instance delegate to a private method of a generic base class exposes that private method through the derived type's reflection cache. Checked CoreCLR terminates on an assertion in the member cache merge; Release silently pollutes the cache so typeof(Derived).GetMethods(...) starts returning the base class's private method.

Repro

csharp
using System;
using System.Linq;
using System.Reflection;

public class Base<T>
{
    private int Secret() => 42;

    public static Delegate MakeOpen() =>
        Delegate.CreateDelegate(typeof(Func<Derived, int>),
            typeof(Base<string>).GetMethod("Secret", BindingFlags.Instance | BindingFlags.NonPublic)!);
}

public class Derived : Base<string> { }

public static class Program
{
    static string Methods() => string.Join(",",
        typeof(Derived).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Select(m => m.Name));

    public static void Main()
    {
        Delegate d = Base<string>.MakeOpen();
        Console.WriteLine(Methods());
        Console.WriteLine(d.Method + " reflected=" + d.Method.ReflectedType);
        Console.WriteLine(Methods());
    }
}

Expected

MemberwiseClone,Finalize
Int32 Secret() reflected=Base`1[System.String]
MemberwiseClone,Finalize

Actual

CoreCLR main (Checked) terminates on an assertion:

MemberwiseClone,Finalize
Process terminated.
Assertion failed.
false
   at System.RuntimeType.RuntimeTypeCache.MemberInfoCache`1.MergeWithGlobalList(T[] list)
   at System.RuntimeType.GetMethodBase(RuntimeType reflectedType, RuntimeMethodHandleInternal methodHandle)
   at System.Delegate.GetMethodImplUncached()

Release silently pollutes the derived type's cache:

MemberwiseClone,Finalize
Int32 Secret() reflected=Derived
MemberwiseClone,Finalize,Secret

Analysis

The open-delegate branch in Delegate.GetMethodImplUncached (src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs) substitutes Derived as the reflected type instead of normalizing back to the exact generic base type that declares the private method. A private method is not a member of the derived type, so it must not be entered into that type's member cache.

Regression?

No. Reproduces on .NET 10.0.12 (Release) and on current main.

Platform

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