Вызов нестатического метода через отражение статическим способом - PullRequest
2 голосов
/ 07 октября 2019

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

Таким образом, можно постоянно вызывать этот метод не только для конкретного объекта, но и для самого класса. Поскольку это, конечно, невозможно на синтаксическом уровне, я думаю, что это должно быть, по крайней мере, возможно через рефлексию. То есть я хочу перебрать все классы в моей сборке и определить, какой класс возвращает какое значение в качестве ответа от указанного метода.

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

Как я могу заставить его работать? (конечно, исключая «решение», чтобы определить второй действительно статический метод, который возвращает то же значение).

using System;
using System.Reflection;

namespace StaticInvoke
{
    public abstract class Foo
    {
        public abstract string StaticValue {get;}
    }

    public class MyFirstFoo: Foo
    {
        public override string StaticValue {get {return "A first attempt to foo-ize Foo.";}}
    }

    class Program
    {
        public static void Main(string[] args)
        {
            Type myFirstFooType = typeof(MyFirstFoo);
            PropertyInfo myFirstStaticValueProperty = myFirstFooType.GetProperty("StaticValue");
            MethodInfo myFirstStaticValueMethod = myFirstStaticValueProperty.GetGetMethod();

            string result = (string)myFirstStaticValueMethod.Invoke(null, null);

            Console.WriteLine("MyFirstFoo.StaticValue == "+result);

            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }
}

Ответы [ 3 ]

6 голосов
/ 07 октября 2019

Однако конкретные реализации этого метода явно не ссылаются на «этот» объект.

Да, язык c # не требует префикса this, потому что в этом нет необходимости.

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

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

Поскольку это, конечно, невозможно на синтаксическом уровне, я думаю, что это должно быть по крайней мере возможно посредством рефлексии.

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

Но мой наивный подход потерпел неудачу с исключением нулевой ссылки, когдапытаясь вызвать метод. Почему?

Поскольку вы вызвали Метод экземпляра в ноль. Ваш пример может быть упрощен до:

(null as string).Count()

То же самое, без отражения. Вы не можете вызвать Count () для NULL.

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

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

Как можноЯ заставляю это работать? (конечно, исключая «решение», чтобы определить второй действительно статический метод, который возвращает то же значение).

Вы не можете , исходя из ваших требований:

вызовите этот метод не только для определенного объекта, но и для самого класса.

Обновление 1:

Скорее мое требование - получить очевидно статическийинформация, содержащаяся в теле метода экземпляра (значение) в статическом контексте, каким бы ни было решение.

Это действительно сбивает с толку, вот пример кода:

public class Person
{
  // So this is "Static Inforamtion"
  public static int StaticInformation()
  {
    return 1;
  }

  // instance method
  public static int InstanceMethod()
  {
    return StaticInformation();
  }
}

public static class StaticClass
{
  public static int StaticContext()
  {
    return Person.InstanceMethod();
  }
}

Это предполагается на основании вашего заявления. Это лучшее, что я могу сделать, чтобы описать ваше предложение как код. Сказать статическую информацию должно означать для большинства, если не всех разработчиков .Net, метод, свойство или поле, помеченные как статические. Тем не менее, крайне неоднозначно указывать статическую информацию, содержащуюся в теле экземпляра . Что такое тело ? Тело метода экземпляра или тело класса?

Мне также интересно, ПОЧЕМУ кто-нибудь захочет это сделать. Кажется, что это просто теоретическая проблема, и она не решит никаких реальных сценариев. При какой ситуации в C # у меня будет своего рода указатель на метод ( MethodInfo ) и я хочу вызвать его, не зная, есть ли у меня экземпляр или нет? И если ожидается тот же результат, то зачем вообще нужен метод Instance?

3 голосов
/ 07 октября 2019

Это возможно, но не без некоторого злоупотребления системой отражения. Я публикую это больше в информационных целях, потому что я думаю, что это иллюстрирует некоторые интересные части того, как работают C # и .NET. Тем не менее, я бы предупредил, что это чрезвычайно хрупко и определенно не по назначению использует рефлексию.

То, что нам нужно сделать, это сделать в два раза. Один из них - передать нулевой получатель методу экземпляра. Это возможно с помощью таких механизмов, как CreateDelegate, которые могут преобразовывать вызов экземпляра в статический вызов, где первый параметр - это экземпляр, на который будет ссылаться this (часто называемый получателем). Что интересно в .NET, так это то, что на низком уровне все методы статичны;Методы экземпляра имеют первый слот параметра, зарезервированный для ссылки this, доступной в методе.

Однако есть еще одна проблема. Вы также хотите сделать то, что фактически является не виртуальным вызовом виртуального метода. Компилятор C # предоставляет это только в очень ограниченных сценариях, например, при вызове метода через базовую ссылку. Чтобы сделать не виртуальный вызов напрямую, единственный способ - это ввести код операции Call (вместо CallVirt) в сгенерированном вручную IL.

static void Main(string[] args)
{
    Type myFirstFooType = typeof(MyFirstFoo);

    // locate the underlying methodinfo, properties have get and set methods.
    PropertyInfo myFirstStaticValueMethod = myFirstFooType.GetProperty("StaticValue");
    MethodInfo getMethod = myFirstStaticValueMethod.GetGetMethod();

    // we need to generate a method call that is non-virtual to a virtual method
    // this is normally not allowed in C# because it is prone to error and brittle.
    // Here we directly emit IL to do so.
    var method = new DynamicMethod("NonVirtualGetter", typeof(string), Type.EmptyTypes, typeof(MyFirstFoo), true);
    var ilgen = method.GetILGenerator();
    ilgen.Emit(OpCodes.Ldnull); // load a null value for the receiver
    ilgen.Emit(OpCodes.Call, getMethod); // invoke the getter method
    ilgen.Emit(OpCodes.Ret);

    // generate a delgate to the dynamic method.
    var getter = (Func<string>)method.CreateDelegate(typeof(Func<string>));
    string result = getter();

    Console.WriteLine("MyFirstFoo.StaticValue == " + result);
}
0 голосов
/ 07 октября 2019

Я решил решить проблему наоборот. Если информация внутри StaticValue является статической, то я буду сохранять ее действительно статичной.

public abstract class Foo
{
    public string StaticValue 
    {
        get
        {
            FieldInfo targetField = GetType().GetField("staticValue");
            return (string)targetField.GetValue(null);
        }
    }
}

public class MyFirstFoo: Foo
{
    public static string staticValue = "A first attempt to foo-ize Foo.";
}

public class MySecondFoo: Foo
{
    public static string staticValue = "A second attempt to foo-ize Foo.";
}

Поэтому, когда я перебираю все мои классы foo в сборке по отражению, я могу просто получить поле «staticValue» изкаждый, тогда как, если у меня уже есть определенный объект Foo, я мог бы просто вызвать метод "StaticValue ()".

Уродливая вещь в этом состоит, конечно, в том, что компилятор не может проверить, является ли конкретный объектКласс Foo определяет поле «staticValue». Вместо этого создание статического метода не помогает, потому что в C # нет такого понятия, как статический виртуальный метод или метод статического интерфейса. Так что мое решение кажется единственным разумным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...