【问题标题】:Overriding an internal method (reflection, emitting)覆盖内部方法(反射、发射)
【发布时间】:2021-06-10 15:58:28
【问题描述】:

我想创建以下类:

    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;
        }
    }

如何调用内部方法,创建内部类型的实例——我或多或少地想通了。对于这一点,反射的使用对我来说就足够了。

但是我仍然无法通过覆盖另一个程序集中指定的虚拟内部方法来解决这个问题。

更新

回应cmets:

我知道这可以在没有这样的课程的情况下解决 - 我知道还有许多其他解决方案或解决方法。我自己在实践中使用了很多。由于我不知道如何解决这个问题,我现在没有停止任何开发。

【问题讨论】:

  • 我不确定您是否可以覆盖内部方法。不过,与其他类的内部结构混在一起感觉有点笨拙。你不能只创建自己的包装类吗?
  • 可以。在最近的一些话题中,它已经完成了。但我想覆盖 MultiDataTrigger。在 XAML 中,它比包装器实现更容易使用。
  • 我知道有很多解决方法。因此,我写道,没有迫切需要这样做。但是有一种理解和学习新事物的愿望。
  • 我不认为你可以。如果您的代码位于不同的程序集中,则它不属于内部代码。
  • 我看到了一个使用 Reflection.Emit 创建动态类型的解决方案。在夏普它可以使用。但是对于 XAML,我没有想到如何使用它。

标签: c# wpf reflection reflection.emit internals


【解决方案1】:

如果这是您真的想要做的,那么是的,可以使用反射发射覆盖另一个程序集中的内部方法。

如果您阅读CLI specification (ECMA-335)(特别是第 II.10.3.3 节“可访问性和覆盖”),您会发现:

[注意:即使派生类可能无法访问方法,也可以重写该方法。

如果方法具有程序集可访问性,则如果它被不同程序集中的方法覆盖,则它应具有公共可访问性。类似的规则适用于 famandassem,其中 famorassem 也允许在大会之外。在这两种情况下,程序集或 famandassem 可以分别在同一个程序集中使用。尾注]

(这里,assemblyfamandassemfamorassem 分别对应于 C# internalprotected privateprotected internal。)

但有一个问题。在同一部分,您还会发现:

如果指定了严格标志 (§II.23.1.10),则只能覆盖可访问的虚拟方法。

C# 编译器在所有非公共虚拟方法上设置此标志。因此,即使使用反射发射,您也不能从 C# 编译的程序集中扩展类并覆盖在该程序集中声明的内部方法,除非您能够从该方法中删除严格标志(可能使用二进制编辑器,并且如果程序集是强命名然后这将使签名无效)。但是您可以创建两个带有反射发射的程序集,在第一个程序集中定义一个具有虚拟内部方法的基类,然后在第二个程序集中扩展该类并覆盖该方法,这可以用这个来演示代码:

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”正在覆盖从该程序集中不可见的方法。

【讨论】:

  • 我开始研究 Emit,但有一刻让我感到困惑。作为所有这些操作的结果,我将获得一个动态类型。在夏普,我仍然可以联系到他。但是如何在 XAML 中使用它? XAML 需要来自真实程序集的真实类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-29
  • 1970-01-01
  • 1970-01-01
  • 2010-10-19
  • 2011-08-16
  • 1970-01-01
  • 2023-04-10
相关资源
最近更新 更多