Может ли CLR поддерживать тип значения "указатель на функцию"? - PullRequest
11 голосов
/ 28 октября 2011

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

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

Action<string> writeLine = Console.WriteLine;

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

textBox.TextChanged += HandleTextChanged;

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

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

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

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

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

Ответы [ 4 ]

11 голосов
/ 28 октября 2011

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

4 голосов
/ 09 декабря 2011

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

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

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

Конечно.Фактически, делегаты создаются с использованием собственных указателей на функции типа значения размера 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);

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

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

. И я бы сказал, что «ссылка на функцию» является доступной для языков CLR - этоможно получить MethodInfo, представляющий метод, через свойство 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's 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 голосов
/ 28 октября 2011

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

0 голосов
/ 30 ноября 2014

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

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

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