По моему мнению, в подавляющем большинстве случаев разработчики .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.