Тип данных методов - PullRequest
       1

Тип данных методов

0 голосов
/ 25 октября 2018

Вопрос

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


Original Post

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

void foo() { ... }

... будет написано в Javascript как ...

Function foo = function():void { ... };

По моему опыту, анонимные функции, как правило, плохая форма, но здесь они переполненына протяжении всего языкового стандарта.Поскольку вы не можете определить функцию с ее типом данных (и, по-видимому, подразумевается / обрабатывается компилятором), как хранить ссылку на метод , если тип никогда не объявляется?

Я очень стараюсь избегать Delegates и его вариантов (Action & Func), потому что оба ...

  1. это еще одна абстракция от того, что на самом деле происходит
  2. ненужные накладные расходы, необходимые для создания экземпляров этих классов (которые, в свою очередь, несут свои собственные указатели на вызываемые методы).

Глядя на исходный код для Delegate.cs, кажется, что ссылка на функцию просто называется Object (см. Строки 23-25).


Если это действительно объекты, как мы их называем?Согласно следу delegate.cs, он заходит в тупик по следующему пути:

Delegate.cs:DynamicInvoke()> DynamicInvokeImpl()> methodinfo.cs:UnsafeInvoke()> UnsafeInvokeInternal()> RuntimeMethodHandle.InvokeMethod()> runtimehandles.cs:InvokeMethod()

internal extern static object InvokeMethod(object target, object[] arguments, Signature sig, bool constructor);

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

Ваша помощь приветствуется.


Ответ на предыдущие комментарии

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

private Dictionary<string, Function> ops = new Dictionary<string, Function> {
    {"foo", int (int a, int b) { return a + b } }
};

В таком виде C # не позволяет вам писатьистинные анонимные функции и стены, которые отключены за Delegates и Lambda expressions.

@ 500 Внутренняя ошибка сервера: Я ужеобъяснил, что я пытался сделать.Я даже смел это.Вы предполагаете, что здесь есть какой-то скрытый мотив;Я просто пытаюсь понять, как C # хранит ссылку на метод.Я даже предоставил ссылки на исходный код, чтобы другие могли прочитать код для себя и помочь ответить на вопрос.

@ Dialecticus: Очевидно, если я уже нашел типичный ответ в Google,только другое место, где можно найти ответ, который я ищу, будет здесь.Я понимаю, что это за пределами знаний большинства разработчиков C #, и именно поэтому я предоставил ссылки на исходный код.Вам не нужно отвечать, если вы не знаете ответа.

Ответы [ 5 ]

0 голосов
/ 03 ноября 2018

Вы явно неправильно понимаете основные различия между языками сценариев, C / C ++ и C #.

Я предполагаю, что основная трудность заключается в том, что не существует в C # такой вещи, как функция.Совсем.В C # 7 появилась новая функция «локальная функция», но это не то, чем является функция в JS.Все фрагменты кода методы .Это имя намеренно отличается от функция или процедура , чтобы подчеркнуть тот факт, что весь исполняемый код в C # принадлежит классу.

Анонимные методы и лямбда-выражения являются простосинтаксис сахар.Компилятор сгенерирует реальный метод в том же (или вложенном) классе, к которому относится метод с объявлением анонимного метода.

В этой простой статье 1016 * это объясняется.Вы можете взять примеры, скомпилировать их и проверить сгенерированный код IL самостоятельно.

Итак, все методы (анонимные или нет) принадлежат классу. Невозможно ответить на ваш обновленный вопрос.кроме сказанного Он не хранит ссылку на функцию, так как в C # .

такого нет. Как хранить ссылку на метод?

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

  1. Экземпляр класса MethodInfo , используемый для ссылки на информацию об отражении для метода,
  2. RuntimeMethodHandle (доступный через RuntimeMethodInfo.MethodHandle) хранит указатель реальной памяти на код метода JITed
  3. Делегат, который очень отличается от всего лишьуказатель памяти, но логически может использоваться для «передачи ссылки на метод другому методу».

Я полагаю, вы ищете опцию MethodInfo , она имеет MethodInfo.Invoke метод, очень похожий Function..apply function в JS.В исходном коде Delegate вы уже видели, как используется этот класс.

Если под "ссылкой" вы имеете в виду указатель на функцию в стиле C, он находится в RuntimeMethodHandle struct.Вы никогда не должны использовать его без четкого понимания того, как работает конкретная реализация платформы .Net и компилятор C #.

Надеюсь, это немного прояснит ситуацию.

0 голосов
/ 03 ноября 2018

Делегат - это просто указатель (место в памяти для перехода) на метод с указанными параметрами и типом возвращаемого значения.Любой метод, соответствующий сигнатуре (параметры и тип возвращаемого значения), может выполнять роль независимо от определенного объекта.Аноним просто означает, что делегат не назван.

В большинстве случаев подразумевается тип (если это не так, вы получите ошибку компилятора): C # - язык со строгой типизацией.Это означает, что каждое выражение (включая делегаты) ДОЛЖНО иметь тип возвращаемого значения (включая void), а также строго типизированные параметры (если они есть).Обобщения были созданы для того, чтобы разрешить использование явных типов в общих контекстах, таких как списки.

Другими словами, делегаты - это безопасная от типов версия управляемых обратных вызовов C ++.

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

Делегат похож на терминологию Closure в Javascript.

В своем ответе Эми выпытаются приравнять слабо типизированный язык, такой как JS, и строго типизированный язык C #.В C # невозможно нигде передать произвольную (свободно типизированную) функцию.Лямбды и делегаты - единственный способ гарантировать безопасность типов.

Я бы порекомендовал попробовать F #, если вы хотите передать функции.

EDIT :

Если вы пытаетесь имитировать поведениеJavascipt, я хотел бы попытаться использовать наследование через интерфейсы.Я могу имитировать множественное наследование и быть одновременно безопасным для типов.Но имейте в виду, что он не может полностью заменить модель внедрения зависимостей Javascript.

0 голосов
/ 29 октября 2018

Как вы, вероятно, узнали, C # не имеет понятия функции, как в вашем примере JavaScript.

C # - это язык статической типизации, и единственный способ использовать указатели на функции - это использовать встроенныйв типах (Func, Action) или пользовательских делегатах. (Я говорю о безопасных, строго типизированных указателях)
Javascript - это динамический язык, поэтому вы можете делать то, что описываете

Если вы хотитеПотеряв безопасность типов, вы можете использовать «динамические» возможности C # или refection для достижения желаемого в следующих примерах (не делайте этого, используйте Func / Action)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ConsoleApp1
{
    class Program
    {
        private static Dictionary<string, Func<int, int, int>> FuncOps = new Dictionary<string, Func<int, int, int>>
        {
            {"add", (a, b) => a + b},
            {"subtract", (a, b) => a - b}
        };

        //There are no anonymous delegates
        //private static Dictionary<string, delegate> DelecateOps = new Dictionary<string, delegate>
        //{
        //    {"add", delegate {} }
        //};

        private static Dictionary<string, dynamic> DynamicOps = new Dictionary<string, dynamic>
        {
            {"add", new Func<int, int, int>((a, b) => a + b)},
            {"subtract", new Func<int, int, int>((a, b) => a - b)},
            {"inverse", new Func<int,  int>((a) => -a )} //Can't do this with Func
        };

        private static Dictionary<string, MethodInfo> ReflectionOps = new Dictionary<string, MethodInfo>
        {
            {"abs", typeof(Math).GetMethods().Single(m => m.Name == "Abs" && m.ReturnParameter.ParameterType == typeof(int))}
        };

        static void Main(string[] args)
        {

            Console.WriteLine(FuncOps["add"](3, 2));//5
            Console.WriteLine(FuncOps["subtract"](3, 2));//1

            Console.WriteLine(DynamicOps["add"](3, 2));//5
            Console.WriteLine(DynamicOps["subtract"](3, 2));//1
            Console.WriteLine(DynamicOps["inverse"](3));//-3

            Console.WriteLine(ReflectionOps["abs"].Invoke(null, new object[] { -1 }));//1
            Console.ReadLine();
        }
    }
}

еще один пример, который вы не должны использовать

delegate object CustomFunc(params object[] paramaters);
private static Dictionary<string, CustomFunc> CustomParamsOps = new Dictionary<string, CustomFunc>
{
    {"add", parameters => (int) parameters[0] + (int) parameters[1]},
    {"subtract", parameters => (int) parameters[0] - (int) parameters[1]},
    {"inverse", parameters => -((int) parameters[0])}
};

Console.WriteLine(CustomParamsOps["add"](3, 2)); //5
Console.WriteLine(CustomParamsOps["subtract"](3, 2)); //1
Console.WriteLine(CustomParamsOps["inverse"](3)); //-3
0 голосов
/ 01 ноября 2018

Я приведу очень короткий и упрощенный ответ по сравнению с другими.Все в C # (классы, переменные, свойства, структуры и т. Д.) Снабжено множеством вещей, в которые могут зацепиться ваши программы.Эта сеть бэкэнда немного снижает скорость C # по сравнению с «более глубокими» языками, такими как C ++, но также дает программистам гораздо больше инструментов для работы и упрощает использование языка.В этот бэкэнд включены такие вещи, как «сборка мусора», которая является функцией, которая автоматически удаляет объекты из памяти, когда не осталось переменных, которые ссылаются на них.Говоря о ссылке, вся система передачи объектов по ссылке, которая по умолчанию в C #, также управляется в бэкэнде.В C # делегаты возможны благодаря функциям этого бэкэнда, которые допускают нечто, называемое «отражением».

Из Википедии:

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

Итак, когда C # компилируется и находит делегат, он просто собирается создать функцию, а затем сохранить отражающую ссылку на эту функцию впеременная, позволяющая вам передавать ее и делать с ней все классные вещи.Вы на самом деле не храните саму функцию в переменной, хотя вы храните ссылку, которая напоминает адрес, указывающий на то, где функция хранится в ОЗУ.

0 голосов
/ 29 октября 2018

Хотя я не совсем понимаю ваши идеи о «истинно анонимных функциях», «без префикса типа данных» и т. Д., Я могу объяснить вам, как приложения, написанные на C #, вызывают методы.

Прежде всегоВ C # нет такой вещи как «функция».Каждый исполняемый объект в C # на самом деле является методом, то есть он принадлежит class.Даже если вы определяете лямбда-выражения или анонимные функции, например:

collection.Where(item => item > 0);

, компилятор C # создает закулисный класс , сгенерированный компилятором, и помещает тело лямбда-выражения return item > 0 в сгенерированный компилятором метод.

Итак, предположим, что у вас есть этот код:

class Example
{
    public static void StaticMethod() { }
    public void InstanceMethod() { }
    public Action Property { get; } = () => { };
}

static class Program
{
    static void Main()
    {
        Example.StaticMethod();
        var ex = new Example();
        ex.InstanceMethod();
        ex.Property();
    }
}

Компилятор C # создаст из этого код IL.Код IL не является исполняемым сразу, его нужно запускать на виртуальной машине.

Код IL будет содержать класс Example с двумя методами (фактически четыре - конструктор по умолчанию и метод получения свойства).будет сгенерирован автоматически) и сгенерированный компилятором класс , содержащий метод, тело которого является телом лямбда-выражения.

Код IL Main будет выглядеть следующим образом (упрощенно):

call void Example::StaticMethod()
newobj instance void Example::.ctor()
callvirt instance void Example::InstanceMethod()
callvirt instance class [mscorlib]System.Action Example::get_Prop()
callvirt instance void [mscorlib]System.Action::Invoke()

Обратите внимание на инструкции call и callvirt: это вызовы методов.

Чтобы фактически выполнить вызванные методы, их код IL необходимо скомпилировать в машинный код(Инструкция процессора).Это происходит на виртуальной машине с именем .NET Runtime.Существует несколько из них, например .NET Framework, .NET Core, Mono и т. Д.

.NET Runtime содержит JIT-компилятор (точно по времени).Он преобразует IL-код в реально исполняемый код во время выполнения вашей программы .

Когда .NET Runtime впервые сталкивается с IL-кодом "метод вызова StaticMethod из класса Examplemsgstr "сначала он смотрит во внутренний кеш уже скомпилированных методов.Когда совпадений нет (что означает, что это первый вызов этого метода), среда выполнения просит JIT-компилятор создать такой скомпилированный и готовый к запуску метод с использованием кода IL.Код IL преобразуется в последовательность операций процессора и сохраняется в памяти процесса.Указатель на этот скомпилированный код хранится в кэше для последующего повторного использования.

Все это произойдет за инструкциями call или callvirt IL (опять же, упрощенно).

После этогопроизошло, среда выполнения готова к выполнению метода.Процессор получает адрес первой операции скомпилированного кода в качестве следующей выполняемой операции и продолжается до тех пор, пока код не вернется.Затем среда выполнения снова вступает во владение и переходит к следующим инструкциям IL.

Метод делегатов DynamicInvoke делает то же самое: он предписывает среде выполнения вызывать метод (после проверки некоторых дополнительных аргументов и т. Д.).Упомянутый вами «тупик» RuntimeMethodHandle.InvokeMethod является внутренним вызовом среды выполнения напрямую.Параметры этого метода:

  • object target - объект, для которого делегат вызывает метод экземпляра (this параметр).
  • object[] arguments - аргументы дляперейти к методу.
  • Signature sig - фактический метод для вызова, Signature является внутренним классом, обеспечивающим соединение между управляемым кодом IL и собственным исполняемым кодом.
  • bool constructor - true, если это вызов конструктора.

Итак, в итоге, методы не представлены как object s в C # (хотя вы, конечно, можете иметь delegate экземпляр, который - это и object, но он не представляет исполняемый метод , он скорее предоставляет вызываемую ссылку на него).

Методывызываются средой выполнения, JIT-компилятор делает методы исполняемыми.

Вы не можете определить глобальную "функцию" вне классов в C #.Вы могли бы получить прямой собственный указатель на скомпилированный (скомпонованный) код метода и, возможно, даже вызвать его вручную, непосредственно манипулируя памятью собственного процесса.Но почему?

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