Вызов внутреннего метода в структуре

#c# #reflection #xna #touch #cil

#c# #отражение #xna #коснитесь #cil

Вопрос:

Я прибегаю к ужасному взлому, чтобы заполнить закрытый тип данных в XNA framework: в структуре есть внутренний метод, который я хочу вызвать, не загружая сборщик мусора.

Если я сохраню указанную структуру в ячейке объектной переменной и использую MethodInfo.Invoke() , этот вызов сам передаст сборщику мусора, поместив параметры в ячейку:

 private object boxedTouchCollection;

void test() {
  MethodInfo addTouchLocationMethod = typeof(TouchCollection).GetMethod(
    "AddTouchLocation", BindingFlags.Instance | BindingFlags.NonPublic
  );
  addTouchLocationMethod.Invoke(
    this.boxedState, new object[] { /* parameters being boxed */ }
  );
}
  

Я не уверен, Delegate.CreateDelegate() можно ли здесь использовать — могу ли я просто превратить первый параметр в объект, и он будет работать в штучной структуре? Или я могу сохранить свою структуру в распакованном виде и объявить первый параметр как ref TouchCollection ?

 delegate void AddTouchLocationDelegate(
  ref TouchCollection collection,
  int id,
  // ...more parameters...
);

private TouchCollection touchCollection;

void test() {
  Delegate.CreateDelegate(
    typeof(AddTouchLocationDelegate),
    typeof(ref TouchCollection), // doesn't compile
    addTouchLocationMethod
  );
}
  

Есть ли способ заставить Delegate.CreateDelegate() работать?
Или мне придется прибегнуть к динамической генерации IL?

Ответ №1:

Вот один из способов.

Он полагается на эту перегрузку Delegate.CreateDelegate , которая создает делегаты метода открытого экземпляра. Единственная сложность заключается в том, что вам придется создать соответствующий тип делегата, чтобы иметь возможность передавать структуру по ссылке.

Я не думаю, что с этим методом должен быть какой-либо бокс — либо с аргументами метода, либо с самой структурой.

Пример: (Прошу прощения за упрощение примеров-типов)

 public struct Foo
{
    // Internal method to be called. Takes a value-type parameter.
    internal void Test(int someParam)
    {
        Console.WriteLine(someParam);
    }

    // Custom delegate-type. Takes the Foo instance of interest 
    // by reference, as well as the argument to be passed on to Test.
    public delegate void MyDelegate(ref Foo foo, int someParam);

    // Creates type-safe delegate
    private static MyDelegate GetTestDelegate()
    {
        var flags = BindingFlags.Instance | BindingFlags.NonPublic;
        var methodInfo = typeof(Foo).GetMethod("Test", flags);

        return (MyDelegate) Delegate.CreateDelegate
                            (typeof(MyDelegate), methodInfo);       
    }

    static void Main()
    {
        Foo foo = new Foo();
        MyDelegate action = GetTestDelegate();

        // should dodge boxing
        action(ref foo, 42);
    }
}
  

Комментарии:

1. Большое вам спасибо! Я еще не пробовал эту перегрузку. Отлично работает, никакого мусора.

Ответ №2:

Вот еще одно решение, использующее деревья выражений Linq, которое я нашел тем временем:

 private delegate void AddTouchLocationDelegate(
  ref TouchCollection touchCollection,
  int id,
  TouchLocationState state,
  float x,
  float y,
  TouchLocationState prevState,
  float prevX,
  float prevY
);

private static AddTouchLocationDelegate createAddTouchLocationDelegate() {
  MethodInfo addTouchLocationMethod = typeof(TouchCollection).GetMethod(
    "AddTouchLocation", BindingFlags.Instance | BindingFlags.NonPublic
  );
  Type byrefTouchCollection = typeof(TouchCollection).MakeByRefType();

  ParameterExpression instance = Expression.Parameter(byrefTouchCollection, "instance");
  ParameterExpression idValue = Expression.Parameter(typeof(int), "id");
  ParameterExpression stateValue = Expression.Parameter(
    typeof(TouchLocationState), "state"
  );
  ParameterExpression xValue = Expression.Parameter(typeof(float), "x");
  ParameterExpression yValue = Expression.Parameter(typeof(float), "y");
  ParameterExpression prevStateValue = Expression.Parameter(
    typeof(TouchLocationState), "prevState"
  );
  ParameterExpression prevXValue = Expression.Parameter(typeof(float), "prevX");
  ParameterExpression prevYValue = Expression.Parameter(typeof(float), "prevY");

  Expression<AddTouchLocationDelegate> expression =
    Expression.Lambda<AddTouchLocationDelegate>(
      Expression.Call(
        instance, addTouchLocationMethod,
        idValue, stateValue, xValue, yValue, prevStateValue, prevXValue, prevYValue
      ),
      instance,
      idValue, stateValue, xValue, yValue, prevStateValue, prevXValue, prevYValue
    );

  return expression.Compile();
}
  

Использование простое:

 var d = createAddTouchLocationDelegate();
d(
  ref this.touches,
  1, TouchLocationState.Pressed, 10, 10, TouchLocationState.Released, 0, 0
);