0

I want to create the following 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;
        }
    }

How to call internal methods, create instances of internal types - I more or less figured it out. For this, the use of reflection is enough for me.

But I still can't figure it out myself with overriding the virtual internal method specified in another assembly.

Update

In response to comments:

I understand this can be solved without such a class - I understand that there are many other solutions or workarounds. I myself use many of these in practice. I have not stopped any development right now due to the fact that I do not know how to solve this issue.

halfer
  • 19,824
  • 17
  • 99
  • 186
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • 1
    I'm not sure you can override an internal method. Messing around with internals of other classes feels a bit janky though. Can't you just create your own wrapper class instead? – DavidG Jun 10 '21 at 16:22
  • Can. And in some recent topic it was done. But I would like to override MultiDataTrigger. In XAML, it was much easier to use it than a wrapper implementation. – EldHasp Jun 10 '21 at 16:29
  • I understand there are many workarounds. Therefore, I wrote that there is no urgent need for this. But there is a desire to understand and learn new things. – EldHasp Jun 10 '21 at 16:37
  • I don't think you can. If your code is in a different assembly then it doesn't qualify as internal. – Andy Jun 10 '21 at 17:31
  • I've seen a solution with Reflection.Emit creating a dynamic type. On Sharpe it can be used. But for XAML, it doesn't occur to me how it can be used. – EldHasp Jun 10 '21 at 17:46

1 Answers1

5

If this is what you really want to do, then yes, it is possible to override an internal method in another assembly using reflection emit.

If you read the CLI specification (ECMA-335) (in particular, section II.10.3.3, "Accessibility and overriding"), you will find this:

[Note: A method can be overridden even if it might not be accessed by the derived class.

If a method has assembly accessibility, then it shall have public accessibility if it is being overridden by a method in a different assembly. A similar rule applies to famandassem, where also famorassem is allowed outside the assembly. In both cases assembly or famandassem, respectively, can be used inside the same assembly. end note]

(Here, assembly, famandassem and famorassem correspond to C# internal, protected private and protected internal, respectively.)

But there is a catch. In the same section, you will also find:

If the strict flag (§II.23.1.10) is specified then only accessible virtual methods can be overridden.

The C# compiler sets this flag on all non-public virtual methods. So you cannot extend a class from a C# compiled assembly and override an internal method declared in that assembly, even with reflection emit, unless you are able to remove the strict flag from that method (perhaps using a binary editor, and if the assembly is strong named then this will invalidate the signature). But you can create two assemblies with reflection emit, define a base class with a virtual internal method in the first one, and extend the class and override the method in the second assembly, which can be demonstrated with this code:

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

}

If you add MethodAttributes.CheckAccessOnOverride (which is the strict flag) to the definition of X in Base, you will get this error (which is the same as what you would get when attempting to do this with a C# compiled type):

Unhandled exception. System.TypeLoadException: Method 'X' on type 'Derived' from assembly 'EmittedAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is overriding a method that is not visible from that assembly.

JosephDaSilva
  • 1,107
  • 4
  • 5
  • I started to study Emit, but one moment confused me. As a result of all these actions, I will get a dynamic type. On Sharpe, I can still contact him. But how do I use it in XAML? The XAML needs a real type from a real assembly. – EldHasp Jun 10 '21 at 20:03