Кто-нибудь знает, возможно ли определить эквивалент «загрузчика пользовательских классов Java» в .NET?
Чтобы дать немного фона:
Я нахожусь в процессе разработки нового языка программирования, предназначенного для CLR, под названием «Свобода». Одной из особенностей языка является его способность определять «конструкторы типов», которые представляют собой методы, которые выполняются компилятором во время компиляции и генерируют типы в качестве вывода. Они являются своего рода обобщением обобщений (в языке действительно есть обычные обозначения) и позволяют писать код, подобный этому (в синтаксисе «Liberty»):
var t as tuple<i as int, j as int, k as int>;
t.i = 2;
t.j = 4;
t.k = 5;
Где "кортеж" определяется так:
public type tuple(params variables as VariableDeclaration[]) as TypeDeclaration
{
//...
}
В этом конкретном примере конструктор типов tuple
предоставляет нечто похожее на анонимные типы в VB и C #.
Однако, в отличие от анонимных типов, «кортежи» имеют имена и могут использоваться внутри сигнатур открытых методов.
Это означает, что мне нужен способ, позволяющий типу, который в конечном итоге испускается компилятором, быть доступным для нескольких сборок. Например, я хочу
tuple<x as int>
определен в сборке A, и в конечном итоге будет соответствовать типу tuple<x as int>
, определенному в сборке B.
Проблема с этим, конечно, заключается в том, что сборка A и сборка B будут скомпилированы в разное время, что означает, что они оба в конечном итоге выпустят свои собственные несовместимые версии типа кортежа.
Я пытался использовать какое-то «стирание типов» для этого, чтобы у меня была общая библиотека с кучей типов, подобных этому (это синтаксис «Liberty»):
class tuple<T>
{
public Field1 as T;
}
class tuple<T, R>
{
public Field2 as T;
public Field2 as R;
}
, а затем просто перенаправить доступ из полей i, j и k в Field1
, Field2
и Field3
.
Однако это не совсем приемлемый вариант. Это будет означать, что во время компиляции tuple<x as int>
и tuple<y as int>
окажутся разными типами, тогда как во время выполнения они будут рассматриваться как один и тот же тип. Это может вызвать много проблем для таких вещей, как равенство и идентичность типов. Это слишком дырявая абстракция для моих вкусов.
Другими возможными вариантами было бы использование «объектов мешка состояния». Тем не менее, использование мешка состояния лишило бы смысла поддержку «конструкторов типов» в языке. Идея состоит в том, чтобы включить «пользовательские языковые расширения» для генерации новых типов во время компиляции, с которыми компилятор может выполнять статическую проверку типов.
В Java это можно сделать с помощью пользовательских загрузчиков классов. По сути, код, который использует типы кортежей, может создаваться без фактического определения типа на диске. Затем можно определить пользовательский «загрузчик классов», который будет динамически генерировать тип кортежа во время выполнения. Это позволило бы статическую проверку типов внутри компилятора и унифицировало бы типы кортежей через границы компиляции.
К сожалению, однако, CLR не обеспечивает поддержку загрузки пользовательских классов. Вся загрузка в CLR выполняется на уровне сборки. Можно было бы определить отдельную сборку для каждого «составного типа», но это очень быстро привело бы к проблемам с производительностью (при наличии множества сборок с одним типом в них потребовалось бы слишком много ресурсов).
Итак, я хочу знать:
Можно ли имитировать что-то наподобие Java Class Loaders в .NET, где я могу выдать ссылку на несуществующий тип в, а затем динамически сгенерировать ссылку на этот тип во время выполнения до того, как коду понадобится использовать его
Примечание:
* Я на самом деле уже знаю ответ на вопрос, который я предоставляю в качестве ответа ниже. Тем не менее, мне потребовалось около 3 дней исследований и немало хакерских атак, чтобы найти решение. Я подумал, что было бы неплохо документировать это здесь, если кто-то столкнется с той же проблемой. *