Замените метод установки свойств Ptr - PullRequest
0 голосов
/ 05 сентября 2018
public delegate void SetProp(object obj);

void Main()
{
    TestObj obj = new TestObj();

    SetProp setPropDel = (SetProp)SetProp.CreateDelegate(typeof(SetProp), 
    obj, obj.GetSetPropDelegate().GetMethodInfo());
    MethodInfo setPropMethod = setPropDel.GetMethodInfo();

    ///Replacing Count Set-Method Pointer with new Method
    typeof(TestObj).GetProperty("Count").GetSetMethod().ReplaceMethodPtr(setPropMethod);
    obj.Count = 1;

}

public class TestObj
{
    public SetProp GetSetPropDelegate() => SetPropValue;

    private void SetPropValue(object obj) => Console.WriteLine(obj); ///<--- NullReferenceException obj is null.

    public int Count { get; set; }

}

Привет, я пытался заменить закрытый метод set для автоматического свойства "Count" в моем классе TestObj.

Сама замена работает. Потому что, когда я использую сеттер, такой как 'obj.Count = 1', вызывается новый метод.

Мой вопрос: возможно ли передать параметр в этот новый метод? По крайней мере, мне нужно новое значение, которое выделяется для свойства Count. В этом случае: 1.

Моя цель состоит в том, чтобы сделать возможным заменить Set-Methods на автоматические свойства, чтобы вызвать событие при вызове нового Set-Method, а также сохранить базовую функцию Set-Method.

Надеюсь, понятно, чего я хочу достичь. Я мог столкнуться с чем-то невозможным здесь, но я уверен, что многие люди гораздо лучше понимают, что происходит.

Спасибо за чтение.

1 Ответ

0 голосов
/ 08 сентября 2018

Хочу поделиться, как я решил эту проблему.

Я написал целую библиотеку с большим количеством функций, таких как события, которые вызываются на съемочной площадке или случаются. Но для ответа я хочу разбить его на простейший пример.

void Main()
{
    MethodInfo targetMethod = typeof(TargetObj).GetProperty("Count").GetSetMethod();
    MethodInfo injectMethod = typeof(InjectObj).GetMethod("SetCount");

    targetMethod.InjectMethod(injectMethod);

    var targetInstance = new TargetObj();

    targetInstance.Count = 3;
}

public class TargetObj
{
    public int Count { get; set; }
}

public class InjectObj
{
    public void SetCount(int value)
    {
        GetBackingField(this, "Count").SetValue(this, value);
        Console.WriteLine($"Count has been set to [{value}]");
    }
}

public static FieldInfo GetBackingField(object obj, string propertyName) => obj.GetType().GetField($"<{propertyName}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);

Если этот код запущен, вывод консоли будет: Счет был установлен на [3]

Возможности этого подхода безграничны, но я хотел бы кратко взглянуть на то, как мне удалось этого достичь.

Обратите внимание, что я сделал этот пример, используя Linqpad.

Я не хочу без необходимости удлинять пост, но мне нужно включить метод расширения MethodInfo "InjectMethod" для полного понимания происходящего.

public static class MethodInfoExtensions
{
    public static void InjectMethod(this MethodInfo target, MethodInfo inject)
    {
        RuntimeHelpers.PrepareMethod(inject.MethodHandle);
        RuntimeHelpers.PrepareMethod(target.MethodHandle);

        unsafe
        {
            if (IntPtr.Size == 4)
            {
                int* inj = (int*)inject.MethodHandle.Value.ToPointer() + 2;
                int* tar = (int*)target.MethodHandle.Value.ToPointer() + 2;

                if (Debugger.IsAttached)
                {
                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;

                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
                }
                else
                {
                    *tar = *inj;
                }
            }
            else
            {
                long* inj = (long*)inject.MethodHandle.Value.ToPointer() + 1;
                long* tar = (long*)target.MethodHandle.Value.ToPointer() + 1;

                if (Debugger.IsAttached)
                {
                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;

                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
                }
                else
                {
                    *tar = *inj;
                }
            }
        }
    }
}

Используя этот вид замены метода, я бы порекомендовал только для целей тестирования или, возможно, в качестве своего рода инструмента отладки. Я уверен, что реализация этого в реальном выпуске программного обеспечения вызовет больше проблем, чем решит.

...