Подтвердить что ты не робот

Может ли CLR поддерживать тип значения "указатель функции"?

Несколько дней назад я спросил почему делегаты являются ссылочными типами, на основе моего ошибочного представления о том, что все, что вам нужно для делегата, - это две ссылки: одна для объекта, и один к функции. Что я полностью забыл (не потому, что я не знал, просто потому, что я забыл), так это то, что в .NET делегаты, по крайней мере, частично созданы для поддержки событий как встроенной реализации "Шаблон наблюдателя" , что означает, что каждый делегат поддерживает несколько подписчиков посредством списка вызовов.

Это заставило меня задуматься, делегаты действительно играют две разные роли в мире .NET. Один из них относится к указателю смиренной функции, например:

Action<string> writeLine = Console.WriteLine;

Другой - наблюдаемый:

textBox.TextChanged += HandleTextChanged;

Существование списка вызовов, по-видимому, исключительно для второй роли, так как в примерах, как простой пример writeLine выше, вы вообще даже не думаете о подписчиках.

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

Теперь я не утверждаю, что это должно быть так, если это возможно. Я уверен, что было бы много недостатков для проведения этого различия между делегатами регулярных и многоадресных рассылок, такими как вероятная высокая частота бокса, если делегаты были типами значений, возможная необходимость ввести новое ключевое слово (multicast?), неизбежная неразбериха разработчиков и т.д. То, что мне действительно интересно знать, просто, если бы с точки зрения CLR было возможно иметь тип значения, который мог бы действовать как указатель функции.

Я предполагаю, что другой способ спросить это: System.Delegate, с его списком вызовов и всеми, в основном базовым CLR-типом; или это оболочка вокруг более простого типа "функции ссылки", который просто не отображается на каких-либо языках CLR?

Я извиняюсь за все неофициальные термины, которые я использовал, которые, возможно, путали некоторых из более образованных разработчиков.

4b9b3361

Ответ 1

В самые ранние дни CLR существовало различие между System.Delegate(как и указателем функции) и System.MulticastDelegate(например, событие). Это было отменено до отправки .NET 1.0, нет способа создать экземпляр типа делегата, который происходит от делегата. Это просто не нужно. MulticastDelegate был оптимизирован только для создания своего списка вызовов, когда имеется более одного абонента. System.Delegate был сохранен по какой-то причине, возможно, слишком много работы, чтобы удалить его.

Ответ 2

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

Собственно, цель вопроса не так уж ясна, но я постараюсь решить отдельные моменты.

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

Конечно. Фактически, делегаты создаются с использованием собственных указателей функций типа value int (IntPtr в управляемом мире). Все надёжные колокола и свистки построены поверх этого.

IL для вашего примера Action<string> writeLine = Console.WriteLine; выглядит примерно так:

// Push null-reference onto stack.
// (Console.WriteLine is a static method)
ldnull

// Push unmanaged pointer to desired function onto stack.
ldftn void [mscorlib]System.Console::WriteLine(string)

// Create delegate and push reference to it onto stack.
instance void [mscorlib]System.Action`1<string>::.ctor(object, native int)

// Pop delegate-reference from top of the stack and store in local.
stloc.0 

где конструктор Action<T> очень удобно объявляется как:

// First arg is 'this' for closed-instance delegates.
// Second arg is pointer to function.
public Action(object @object, IntPtr method);

Я предполагаю, что другой способ задать это будет: is System.Delegate, с его список вызовов и все, в основном фундаментальный тип CLR; или это обертка вокруг более простого типа "функции ссылки", который просто не отображается на каких-либо языках CLR?

Ну, это и основной CLR-тип (в том смысле, что движок-исполнитель знает об этом и рассматривает его специально), и "обертка" вокруг "ссылки на функцию" - System.Delegate хранит указатель на функцию как поле (и ссылка на объект для делегатов с закрытым экземпляром). Многоадресная поддержка через свой подкласс System.MulticastDelegate, очевидно, намного сложнее из-за необходимости хранить список вызовов многоадресной рассылки. Унификация достигается за счет того, что все типы делегатов в дикой природе должны наследоваться от System.MulticastDelegate.

И я бы сказал, что "ссылка на функцию" доступна для языков CLR - возможно получить MethodInfo, представляющий этот метод, через свойство delegate Target, а оттуда связанный с ним метод-дескриптор и указатель функции.


Но вы, наверное, все это уже знаете. Мне кажется, что ваш настоящий вопрос:

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

Учитывая все, о чем я говорил до сих пор, это очень легко:

// Cool but mostly useless lightweight (in space, not time)
// type-safe delegate-like value-type. Doesn't support closed-instance scenarios 
// in the interests of space, but trivial to put in if desired.
public struct LeanDelegate<TDelegate>
{
    // The only storage required.
    private readonly IntPtr _functionPointer;

    public LeanDelegate(TDelegate source)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        var del = source as Delegate;

        if (del == null)
            throw new ArgumentException("Argument is not a delegate", "source");

        if (del.Target != null)
            throw new ArgumentException("Delegate is a closed-instance delegate.", "source");

        if (del.GetInvocationList().Length > 1)
            throw new ArgumentException("Delegate is a multicast delegate.", "source");

         // Retrieve and store pointer to the delegate target function.
        _functionPointer = del.Method.MethodHandle.GetFunctionPointer();
    }

    // Creates delegate-instance on demand.
    public TDelegate Delegate
    {
        get
        {
            if (_functionPointer == IntPtr.Zero)
                throw new InvalidOperationException("Uninitialized LeanDelegate instance.");

            // Use the aforementioned compiler-generated constructor 
            // to generate the delegate instance.
            return (TDelegate)Activator.CreateInstance
                              (typeof(TDelegate), null, _functionPointer);
        }
    }
}

И тогда вы можете сделать:

 var del = new LeanDelegate<Action<string>>(Console.WriteLine);
 del.Delegate("Hello world");

Что вы получили? Возможность сохранять указатель на произвольный статический метод (или экземпляр-метод, если делегат является открытым экземпляром) и выполнять его безопасным образом, все в пространстве размера машинного указателя (исключая временные выделения).

Что вы потеряли? Возможность закрытого экземпляра. Multicast. Прямая совместимость со многими другими API. Скорость (почти наверняка), хотя обходные пути мыслимы. Любовь разработчиков, которые будут использовать ваш API.

Ответ 3

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

Ответ 4

есть System.Delegate, с его списком вызовов и всеми, в основном базовым CLR-типом; или это оболочка вокруг более простого типа "функции ссылки", который просто не отображается на каких-либо языках CLR?

Я тоже не думаю. Во-первых, хотя System.Delegate имеет некоторые методы, реализованные CLR, его экземпляры обрабатываются не специально, а только на С# и других языках (это просто класс с методом Invoke и некоторые другие). Во-вторых, существует особый CLR-тип под названием fnptr, представляющий указатель на функцию (см. этот вопрос). Однако этот тип доступен, хотя и не в С#, а в CIL (method void *()) или в С++/CLI (void (*foo)()).