Is there a way to "override" a method with reflection?
Asked Answered
J

1

26

Without inherit but only with reflection is it possible to dynamically change the code of a method in C#?

something like :

nameSpaceA.Foo.method1 = aDelegate;

I cannot change/edit The Foo Class.

namespace nameSpaceA
{
  class Foo
  {
       private void method1()
       {
           // ... some Code
       }
  }
}

My final objective is to change dynamicaly the code of :

public static IList<XPathNavigator> EnsureNodeSet(IList<XPathItem> listItems);

In System.Xml.Xsl.Runtime.XslConvert.cs

to turn :

if (!item.IsNode)
    throw new XslTransformException(Res.XPath_NodeSetExpected, string.Empty); 

into :

if (!item.IsNode)
    throw new XslTransformException(Res.XPath_NodeSetExpected, item.value); 
Jolenejolenta answered 13/3, 2012 at 13:22 Comment(3)
No, C# cannot be monkey-patched, if that is the question...Flamingo
@MarcGravell emission enables that. Also, re-mix can do it too. C# can totally be monkey-patched!Rodmann
@Baboon replied on your answerFlamingo
R
16

The first part of this answer is wrong, I'm only leaving it so that the evolution in the comments makes sense. Please see the EDIT(s).

You're not looking for reflection, but emission (which is the other way around).

In particular, there's a method that does just what you want, lucky you!

See TypeBuilder.DefineMethodOverride

EDIT:
Writing this answer, I just remembered that re-mix allows you to do this too. It's way harder though.

Re-mix is a framework that "simulates" mixins in C#. In its basic aspect, you can think of it as interfaces with default implementations. If you go further, it becomes much more than that.

EDIT 2: Here is an example of use for re-mix (implementing INotifyPropertyChanged on a class that doesn't support it, and has no idea of mixins).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Remotion.Mixins;
using System.ComponentModel;
using MixinTest;

[assembly: Mix(typeof(INPCTester), typeof(INotifyPropertyChangedMixin))]

namespace MixinTest
{
    //[Remotion.Mixins.CompleteInterface(typeof(INPCTester))]
    public interface ICustomINPC : INotifyPropertyChanged
    {
        void RaisePropertyChanged(string prop);
    }

    //[Extends(typeof(INPCTester))]
    public class INotifyPropertyChangedMixin : Mixin<object>, ICustomINPC
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisePropertyChanged(string prop)
        {
             PropertyChangedEventHandler handler = this.PropertyChanged;
             if (handler != null)
             {
                 handler(this, new PropertyChangedEventArgs(prop));
             }
        }
    }

    public class ImplementsINPCAttribute : UsesAttribute 
    {
        public ImplementsINPCAttribute()
            : base(typeof(INotifyPropertyChangedMixin))
        {

        }
    }

    //[ImplementsINPC]
    public class INPCTester
    {
        private string m_Name;
        public string Name
        {
            get { return m_Name; }
            set
            {
                if (m_Name != value)
                {
                    m_Name = value;
                    ((ICustomINPC)this).RaisePropertyChanged("Name");
                }
            }
        }
    }

    public class INPCTestWithoutMixin : ICustomINPC
    {
        private string m_Name;
        public string Name
        {
            get { return m_Name; }
            set
            {
                if (m_Name != value)
                {
                    m_Name = value;
                    this.RaisePropertyChanged("Name");
                }
            }
        }

        public void RaisePropertyChanged(string prop)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(prop));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

And the test:

static void INPCImplementation()
        {
            Console.WriteLine("INPC implementation and usage");

            var inpc = ObjectFactory.Create<INPCTester>(ParamList.Empty);

            Console.WriteLine("The resulting object is castable as INPC: " + (inpc is INotifyPropertyChanged));

            ((INotifyPropertyChanged)inpc).PropertyChanged += inpc_PropertyChanged;

            inpc.Name = "New name!";
            ((INotifyPropertyChanged)inpc).PropertyChanged -= inpc_PropertyChanged;
            Console.WriteLine();
        }

static void inpc_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine("Hello, world! Property's name: " + e.PropertyName);
        }
//OUTPUT:
//INPC implementation and usage
//The resulting object is castable as INPC: True
//Hello, world! Property's name: Name

Please note that:

[assembly: Mix(typeof(INPCTester), typeof(INotifyPropertyChangedMixin))]

and

[Extends(typeof(INPCTester))] //commented out in my example

and

[ImplementsINPC] //commented out in my example

Have the exact same effect. It is a matter of where you wish to define that a particular mixin is applied to a particular class.

Example 2: overriding Equals and GetHashCode

public class EquatableByValuesMixin<[BindToTargetType]T> : Mixin<T>, IEquatable<T> where T : class
    {
        private static readonly FieldInfo[] m_TargetFields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        bool IEquatable<T>.Equals(T other)
        {
            if (other == null)
                return false;
            if (Target.GetType() != other.GetType())
                return false;
            for (int i = 0; i < m_TargetFields.Length; i++)
            {
                object thisFieldValue = m_TargetFields[i].GetValue(Target);
                object otherFieldValue = m_TargetFields[i].GetValue(other);

                if (!Equals(thisFieldValue, otherFieldValue))
                    return false;
            }
            return true;
        }

        [OverrideTarget]
        public new bool Equals(object other)
        {
            return ((IEquatable<T>)this).Equals(other as T);
        }

        [OverrideTarget]
        public new int GetHashCode()
        {
            int i = 0;
            foreach (FieldInfo f in m_TargetFields)
                i ^= f.GetValue(Target).GetHashCode();
            return i;
        }
    }

    public class EquatableByValuesAttribute : UsesAttribute
    {
        public EquatableByValuesAttribute()
            : base(typeof(EquatableByValuesMixin<>))
        {

        }
    }

That example is my implementation of the hands-on lab given with re-mix. You can find more information there.

Rodmann answered 13/3, 2012 at 13:27 Comment(18)
You can't use DefineMethodOverride to change a private, non-virtual method on an internal class. If you can, I'd love to see it. Equally, you'd need to also change the construction to create the sub-type, which assumes you have control over whatever creates Foo (which is not necessarily the case)Flamingo
@MarcGravell I haven't personally tried it, but I'd be inclined to think overriding private methods makes no sense. However, given the example scenario he gave after his edit, it seems to me he has access to the method he wants to override. So, it's not private. Either way, re-mix is definitely worth taking a look at!Rodmann
there's a difference between "I can see what the method does in reflector" and "it is a public virtual method that I have access to" - I really don't think emit is going to help here (and I say that as someone who uses emit regularly)Flamingo
Yes, and the method he wants to override probably isn't virtual or there wouldn't be a question in the first place. Emission won't help so easily. He could, however, rebuild the entire type by "reading it" with reflection and "writing it" by emission. Depending on how big the class is, it can be a lot of work though.Rodmann
In my case It's a static method I need to emit, should I stop my work, try with DefineMethodOverride or try with re-mix?Jolenejolenta
If I were you, I'd try with re-mix.Rodmann
ok I will try for my poor level, I hope I will learn a lot of thingsJolenejolenta
emission(with DefineMethodOverride) generate new dynamic assembly, and don't change source assemblyGarfish
@ChristopheDebove I added an example of usage or re-mix to get you started.Rodmann
@Baboon interesting, but! Subclassing something (the factory) and adding an interface implementation is very different to changing the existing implementation of a non-virtual method. I think it is an interesting answer, but to a different questionFlamingo
@ChristopheDebove you can, see my second example.Rodmann
@Baboon in your exemple EquatableByValuesAttribute know Mixin with your inherit, but I'm note able to modify the targeted class, exemple try to Replace ToString() Method of Object class without edit Object class so when I'm going to call ToString on any object(who doesn't overide ToString) it must return the new ToString definition I've madeJolenejolenta
@ChristopheDebove you need to put [Extends(typeof(Your_Target_Class)] on the mixin. Or use the [assembly: Mix(...)] assembly-level attribute.Rodmann
sorry but I don't arrive to make it work, I arrived to redefine Equals following the pdf of remix. but for a simple class it doesn't workJolenejolenta
here is my simple code christophe-debove.fr/outils/Program.cs it will very helpful if you can check I don't miss somethingJolenejolenta
You forgot to add the attribute that inherits UsesAttribute. See the end of my last example.Rodmann
@ChristopheDebove also, I'm no expert, but I believe your Mixin should be defined as ClassWithAnotherHelloMethodMixin<Address> : Mixin<Address>Rodmann
let us continue this discussion in chatJolenejolenta

© 2022 - 2024 — McMap. All rights reserved.