为什么允许委托对象调用内部方法?

How come delegate objects are allowed to call internal methods?

我可以轻松地将对内部方法的调用包装在委托中。然后委托可以在我调用 Invoke() 时调用此方法。但是,代表在 mscorlib 程序集中。为什么它可以从我的程序集中调用内部方法?

显然,委托必须能够执行此操作,C# 才能正常工作。问题不在于 为什么 它是允许的。这是如何

我假设检查可见性是 C# 的一项功能,直接从 CIL 调用方法应该可以。所以我试图从动态定义的类型中调用该方法,从而直接使用 CIL 并跳过 C#。它惨遭失败。这是代码:

public static class InternalCall
{
    internal static void InternalMethod()
    {
        Debug.WriteLine("Successfully called an internal method from: " + typeof(InternalCall).Assembly.FullName);
    }

    public interface IMyAction
    {
        void MyInvoke();
    }

    private static IMyAction MakeMyAction()
    {
        var assembly = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("Outside"), AssemblyBuilderAccess.Run);
        var module = assembly.DefineDynamicModule("Outside", false);
        var customType = module.DefineType("MyAction",
            TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit,
            typeof(object),
            new[] { typeof(IMyAction) });
        var method = customType.DefineMethod("MyInvoke",
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final);
        var il = method.GetILGenerator();
        il.Emit(OpCodes.Call, typeof(InternalCall).GetMethod("InternalMethod", BindingFlags.Static | BindingFlags.NonPublic));
        il.Emit(OpCodes.Ret);

        return (IMyAction)customType.CreateType().GetConstructor(Type.EmptyTypes).Invoke(null);
    }

    public static void RunTest()
    {
        var action = new Action(InternalMethod);
        Debug.WriteLine("Calling via action from assembly: " + action.GetType().Assembly.FullName);
        action.Invoke();

        var myAction = MakeMyAction();
        Debug.WriteLine("Calling via my type from assembly: " + myAction.GetType().Assembly.FullName);
        myAction.MyInvoke(); // MethodAccessException
    }
}

因此,假设委托仍然遵守 CIL 规则(因为这是 C# 编译的目标),他们使用什么 CIL 机制来调用任何方法而不考虑可见性?

我想我弄清楚了委托背后的基本机制。我以前应该想到的。他们使用间接调用,即 CIL 中的 calli 指令,它似乎不进行可见性检查。

我仍然不确定它们到底存储了什么以及它们如何执行,但我很满意将方法的函数指针硬编码到我发出的 CIL 中可以使这项工作正常进行,因此它说明了原理。

var toCallInfo = typeof(InternalCall).GetMethod("InternalMethod", BindingFlags.Static | BindingFlags.NonPublic);
unsafe
{
    var functionPointer = toCallInfo.MethodHandle.GetFunctionPointer();
    if (sizeof(IntPtr) == 4)
        il.Emit(OpCodes.Ldc_I4, (int)functionPointer);
    else
        il.Emit(OpCodes.Ldc_I8, (long)functionPointer);
}
il.EmitCalli(OpCodes.Calli, toCallInfo.CallingConvention, null, null, null);
il.Emit(OpCodes.Ret);

现在创建类型 运行 它不会引发异常。

了解自定义委托在 CIL 中的外观很有见地:

.class auto ansi sealed MyDeleg extends [mscorlib]System.MulticastDelegate
{

.method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
{
}

.method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
{
}

.method public hidebysig newslot virtual instance int32  EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
{
}

.method public hidebysig newslot virtual instance int32  Invoke() runtime managed
{
}

}

就是这样。 runtime 属性告诉我们是运行时为此方法提供实现,而不是代码 (cil)。就像 p/invoke 到运行时。在这个阶段,没有代码检查,没有验证器,没有 JIT。执行由 CLR 处理以决定如何调用委托,委托随后简单地调用它。

执行类似于 calli 指令,但这不是这些方法的实现方式,尽管您可以使用它很好地模拟委托。为什么没有能见度检查?因为它是传递给指令的原始方法指针(来自 GetFunctionPointer),获取其元数据可能会对性能产生负面影响(或者可能根本没有元数据)。当然,做任何可见性检查都是不需要的。