簡體   English   中英

覆蓋內部方法(反射、發射)

[英]Overriding an internal method (reflection, emitting)

我想創建以下 class:

    public class MultiDataOrTrigger : DataTrigger
    {
        public MultiDataOrTrigger()
        {

        }

        // evaluate the current state of the trigger
        internal override bool GetCurrentState(DependencyObject container, UncommonField<HybridDictionary[]> dataField)
        {
            bool retVal = false;

            for (int i = 0; !retVal && i < TriggerConditions.Length; i++)
            {
                retVal = TriggerConditions[i].ConvertAndMatch(StyleHelper.GetDataTriggerValue(dataField, container, TriggerConditions[i].Binding));
            }

            return retVal;
        }
    }

如何調用內部方法,創建內部類型的實例——我或多或少想通了。 為此,使用反射對我來說就足夠了。

但是我仍然無法通過覆蓋另一個程序集中指定的虛擬內部方法來解決這個問題。

更新

回應評論:

我知道這可以在沒有這樣的 class 的情況下解決 - 我知道還有許多其他解決方案或解決方法。 我自己在實踐中使用了很多。 由於我不知道如何解決這個問題,我現在還沒有停止任何開發。

如果這是您真正想要做的,那么是的,可以使用反射發射覆蓋另一個程序集中的內部方法。

如果您閱讀CLI 規范 (ECMA-335) (特別是第 II.10.3.3 節,“可訪問性和覆蓋”),您會發現:

[注意:即使派生的 class 可能無法訪問方法,也可以覆蓋該方法。

如果方法具有程序集可訪問性,則如果它被不同程序集中的方法覆蓋,則它應具有公共可訪問性。 類似的規則適用於 famandassem,其中 famorassem 也允許在大會之外。 在這兩種情況下,assembly 或 famandassem 分別可以在同一個程序集中使用。 尾注]

(這里的assemblyfamandassemfamorassem對應 C# internalprotected privateprotected internal 。)

但是有一個問題! 在同一部分中,您還會發現:

如果指定了嚴格標志(§II.23.1.10),則只能覆蓋可訪問的虛擬方法。

C# 編譯器在所有非公共虛擬方法上設置此標志。 因此,您不能從 C# 編譯的程序集中擴展 class 並覆蓋在該程序集中聲明的內部方法,即使使用反射發射,除非您能夠從該方法中刪除嚴格標志(可能使用二進制編輯器,並且如果程序集是強命名然后這將使簽名無效)。 但是您可以使用反射發射創建兩個程序集,在第一個程序集中使用虛擬內部方法定義基礎 class,然后擴展 class 並覆蓋第二個程序集中的方法,可以使用以下代碼進行演示:

using System;
using System.Reflection;
using System.Reflection.Emit;

public interface IBase {
    void X();
}

class Program {
    
    public static void Main() {
        ILGenerator ilGenerator;

        var assembly1 = AssemblyBuilder.DefineDynamicAssembly(
            new AssemblyName("EmittedAssembly1"),
            AssemblyBuilderAccess.Run
        );

        var module1 = assembly1.DefineDynamicModule("EmittedModule1");

        // Define the base class.
        var typeBuilderBase = module1.DefineType("Base", TypeAttributes.Public);
        typeBuilderBase.DefineDefaultConstructor(MethodAttributes.Public);
        typeBuilderBase.AddInterfaceImplementation(typeof(IBase));

        // This is the internal method that will be overridden.
        var methodBuilderBaseX = typeBuilderBase.DefineMethod(
            "X",
            MethodAttributes.Assembly | MethodAttributes.Virtual | MethodAttributes.NewSlot,
            typeof(void),
            Array.Empty<Type>()
        );

        ilGenerator = methodBuilderBaseX.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldstr, "X from Base");
        ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] {typeof(string)}));
        ilGenerator.Emit(OpCodes.Ret);

        // Define an explicit interface implementation that will be used to call
        // Base.X() from the created instance of Derived.
        var methodBuilderBaseInterfaceX = typeBuilderBase.DefineMethod(
            "IBase.X",
            MethodAttributes.Private | MethodAttributes.Virtual,
            typeof(void),
            Array.Empty<Type>()
        );

        ilGenerator = methodBuilderBaseInterfaceX.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Callvirt, methodBuilderBaseX);
        ilGenerator.Emit(OpCodes.Ret);

        typeBuilderBase.DefineMethodOverride(methodBuilderBaseInterfaceX, typeof(IBase).GetMethod("X"));

        typeBuilderBase.CreateType();

        // This is the assembly in which the internal method will be overridden.
        var assembly2 = AssemblyBuilder.DefineDynamicAssembly(
            new AssemblyName("EmittedAssembly2"),
            AssemblyBuilderAccess.Run
        );
        var module2 = assembly2.DefineDynamicModule("EmittedModule2");
        
        var typeBuilderDerived = module2.DefineType("Derived", TypeAttributes.Public);
        typeBuilderDerived.SetParent(typeBuilderBase);
        typeBuilderDerived.DefineDefaultConstructor(MethodAttributes.Public);

        // Override the internal method in Base. Note that the accessibility of the overridden
        // method must be public.
        var methodBuilderDerivedX = typeBuilderDerived.DefineMethod(
            "X",
            MethodAttributes.Public | MethodAttributes.Virtual,
            typeof(void),
            Array.Empty<Type>()
        );

        ilGenerator = methodBuilderDerivedX.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldstr, "X from Derived");
        ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] {typeof(string)}));
        ilGenerator.Emit(OpCodes.Ret);

        var typeDerived = typeBuilderDerived.CreateType();

        // Create an instance of the emitted Derived type.
        var instance = (IBase)typeDerived.GetConstructor(Array.Empty<Type>()).Invoke(Array.Empty<object>());

        // Call the overridden method. This outputs "X from Derived"!
        instance.X();
    }

}

如果將MethodAttributes.CheckAccessOnOverride (這是strict標志)添加到BaseX的定義中,您將收到此錯誤(與嘗試使用 C# 編譯類型執行此操作時遇到的相同):

未處理的異常。 System.TypeLoadException:來自程序集“EmittedAssembly2,Version=0.0.0.0,Culture=neutral,PublicKeyToken=null”的類型“Derived”的方法“X”正在覆蓋從該程序集中不可見的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM