¿Alguien sabe si es posible definir el equivalente de un «personalizada de java de la clase loader» en .NETA?

Para dar un poco de fondo:

Estoy en el proceso de desarrollo de un nuevo lenguaje de programación que se enfoca en el CLR, se llama «Libertad». Una de las características del lenguaje es su capacidad para definir «los constructores de tipos», que son métodos que son ejecutadas por el compilador en tiempo de compilación y generar los tipos de salida. Ellos son una especie de generalización de los medicamentos genéricos (el lenguaje normal genéricos en ella), y permitir que el código como este para ser escrito (en «la Libertad» de la sintaxis):

var t as tuple<i as int, j as int, k as int>;
t.i = 2;
t.j = 4;
t.k = 5;

Donde «tupla» se define así:

public type tuple(params variables as VariableDeclaration[]) as TypeDeclaration
{
   //...
}

Este ejemplo en particular, el constructor de tipo tuple ofrece algo similar a los tipos anónimos en VB y C#.

Sin embargo, a diferencia de los tipos anónimos, «tuplas» tienen nombres y puede ser usado dentro de un método público de firmas.

Esto significa que tengo una manera para que el tipo que finalmente termina siendo emitidas por el compilador para ser compartido a través de múltiples asambleas. Por ejemplo, quiero

tuple<x as int> definido en el Ensamblado a terminar siendo el mismo tipo que tuple<x as int> definido en Asamblea B.

El problema con esto, por supuesto, es que la Asamblea y la Asamblea de la B va a ser compilado en diferentes momentos, lo que significa que ambos acaban de emitir sus propias versiones incompatibles de la tupla tipo.

Miré en el uso de algún tipo de «tipo de borrado» de hacer esto, así que me gustaría tener una biblioteca compartida con un montón de tipos como este (esta es la «Libertad» de la sintaxis):

class tuple<T>
{
    public Field1 as T;
}

class tuple<T, R>
{
    public Field2 as T;
    public Field2 as R;
}

y, a continuación, simplemente redirigir el acceso desde la i, j, y k tupla campos Field1, Field2, y Field3.

Sin embargo, no es realmente una opción viable. Esto significaría que en tiempo de compilación tuple<x as int> y tuple<y as int> acabaría siendo diferentes tipos, mientras que en tiempo de ejecución el tiempo de ellos sería tratado como un mismo tipo. Que iba a causar muchos problemas para cosas como la igualdad y el tipo de identidad. Que es demasiado permeable de una abstracción para mi gusto.

Otras opciones posibles sería el uso de «estado de la bolsa de objetos». Sin embargo, el uso de un estado de la bolsa de derrotaría el propósito de contar con el apoyo de «los constructores de tipos» en el idioma. La idea es permitir que la «costumbre extensiones de lenguaje» para generar nuevos tipos en tiempo de compilación que el compilador puede hacer la comprobación de tipo estático con.

En Java, esto se podría hacer mediante clases personalizadas cargadores. Básicamente el código que utiliza tipos de tupla puede ser emitida sin llegar a la definición del tipo de disco. Una costumbre «de la clase loader» podría ser definida que podría generar dinámicamente la tupla de tipo en tiempo de ejecución. Que permita la comprobación de tipo estático dentro del compilador, y con el fin de unificar los tipos de tupla a través de la compilación de los límites.

Desafortunadamente, sin embargo, el CLR no ofrece soporte para la costumbre de la carga de clases. Toda la carga en el CLR se hace al nivel de la asamblea. Sería posible definir un conjunto separado para cada «tipo construido», pero que llevaría muy rápidamente los problemas de rendimiento (teniendo en muchas de las asambleas con sólo un tipo de ellos se utiliza demasiados recursos).

Así, lo que quiero saber es:

Es posible simular algo así como la Clase Java Cargadores en .NET, donde puedo emitir una referencia a un no-tipo existente en y, a continuación, generar dinámicamente una referencia a ese tipo en tiempo de ejecución antes de que el código de las necesidades de uso se ejecuta?

NOTA:

*En realidad yo ya sé la respuesta a la pregunta, que me proporcionan como una respuesta a continuación. Sin embargo, me tomó cerca de 3 días de investigación, y un poco de IL hacking con el fin de llegar a una solución. Pensé que sería una buena idea documento aquí por si alguien más se topó con el mismo problema. *

2 Comentarios

  1. 52

    La respuesta es sí, pero la solución es un poco complicado.

    La Sistema.Reflexión.Emiten espacio de nombres define los tipos que permite a los ensamblados a ser generados de forma dinámica. También permiten que los ensamblados generados a ser definidos de forma incremental. En otras palabras, es posible añadir tipos a la dinámica de la asamblea, ejecutar el código generado y, a continuación, último añadir más tipos de la asamblea.

    La Sistema.Dominio de aplicación clase también define un AssemblyResolve evento que se activa cada vez que el marco no se puede cargar un ensamblado. Mediante la adición de un controlador para el evento, es posible definir un único «tiempo de ejecución» de la asamblea en la que todos los «construyen» los tipos son colocados. El código generado por el compilador que usa un tipo construido haría referencia a un tipo en el ensamblado en tiempo de ejecución. Debido a que el tiempo de ejecución de la asamblea realidad no existe en el disco, el AssemblyResolve evento se desencadenaría la primera vez que el código compilado intentado acceder a un tipo construido. El asa para el evento, a continuación, generar la dinámica de la asamblea y regresar a la CLR.

    Por desgracia, hay algunas difíciles de puntos para conseguir que esto funcione. El primer problema es asegurarse de que el controlador de eventos siempre va a ser instalado antes de que el código compilado se ejecuta. Con una aplicación de consola, esta es fácil. El código para la conexión al controlador de eventos sólo puede ser añadido a la Main método antes de que el otro código se ejecuta. Para bibliotecas de clases, sin embargo, no hay ningún método main. Un archivo dll puede ser cargado como parte de una aplicación escrita en otro idioma, así que no es realmente posible asumir que siempre hay un método disponible para la conexión en el código del controlador de eventos.

    El segundo problema es asegurarse de que la referencia de todos los tipos de insertarse en la dinámica de la asamblea antes de cualquier código que hace referencia a ellos se utiliza. El Sistema.Dominio de aplicación clase también define un TypeResolve evento que se ejecuta siempre que el CLR es incapaz de resolver un tipo en una dinámica de la asamblea. Se da el controlador de eventos la oportunidad de definir el tipo dentro de la dinámica de la asamblea antes de que el código que utiliza se ejecuta. Sin embargo, ese evento no funciona en este caso. El CLR no se desencadenará el evento para los ensamblados que son «estáticamente en referencia a» otros ensamblados, incluso si la referencia de ensamblado se define dinámicamente. Esto significa que necesitamos una manera de ejecutar código antes que cualquier otro código en el ensamblado compilado se ejecuta y se han dinámicamente inyectar los tipos de necesidades en el ensamblado en tiempo de ejecución si no han sido ya definidos. De lo contrario, cuando el CLR intentado cargar esos tipos se dará cuenta de que la dinámica de la asamblea no contiene los tipos que necesitan y se lanzará una carga por tipo de excepción.

    Afortunadamente, el CLR ofrece una solución a ambos problemas: Módulo de Inicializadores. Un módulo de inicializador es el equivalente de una «estática constructor de la clase», excepto que inicializa el módulo entero, no sólo en una sola clase. Baiscally, el CLR se:

    1. Ejecutar el módulo constructor antes de cualquier tipo en el interior del módulo se accede a ellos.
    2. Garantizar que sólo aquellos tipos accedido directamente por el módulo constructor será cargado mientras se está ejecutando
    3. No permitir que el código fuera del módulo para acceder a cualquiera de sus miembros hasta después de que el constructor ha terminado.

    Esto se hace para todas las asambleas, incluyendo tanto las clases como las bibliotecas y los archivos ejecutables, y para archivos Exe se ejecute el módulo constructor antes de ejecutar el método Main.

    Ver este blog para obtener más información acerca de los constructores.

    En cualquier caso, una completa solución a mi problema requiere de varias piezas:

    1. La siguiente definición de clase, que se define dentro de un «lenguaje de dll en tiempo de ejecución», que hace referencia a todas las asambleas producido por el compilador (este es el código de C#).

      using System;
      using System.Collections.Generic;
      using System.Reflection;
      using System.Reflection.Emit;
      namespace SharedLib
      {
      public class Loader
      {
      private Loader(ModuleBuilder dynamicModule)
      {
      m_dynamicModule = dynamicModule;
      m_definedTypes = new HashSet<string>();
      }
      private static readonly Loader m_instance;
      private readonly ModuleBuilder m_dynamicModule;
      private readonly HashSet<string> m_definedTypes;
      static Loader()
      {
      var name = new AssemblyName("$Runtime");
      var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
      var module = assemblyBuilder.DefineDynamicModule("$Runtime");
      m_instance = new Loader(module);
      AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
      }
      static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
      {
      if (args.Name == Instance.m_dynamicModule.Assembly.FullName)
      {
      return Instance.m_dynamicModule.Assembly;
      }
      else
      {
      return null;
      }
      }
      public static Loader Instance
      {
      get
      {
      return m_instance;
      }
      }
      public bool IsDefined(string name)
      {
      return m_definedTypes.Contains(name);
      }
      public TypeBuilder DefineType(string name)
      {
      //in a real system we would not expose the type builder.
      //instead a AST for the type would be passed in, and we would just create it.
      var type = m_dynamicModule.DefineType(name, TypeAttributes.Public);
      m_definedTypes.Add(name);
      return type;
      }
      }
      }

      La clase define un singleton que contiene una referencia a la dinámica de la asamblea que los tipos construidos será creado. También tiene un «hash» que almacena el conjunto de tipos que ya han sido generados de forma dinámica, y, finalmente, define un miembro que puede ser utilizado para definir el tipo. Este ejemplo devuelve un Sistema.Reflexión.Emiten.TypeBuilder, instancia que luego pueden ser utilizados para definir la clase que se está generando. En un sistema real, el método probablemente tendría en un AST representación de la clase, y no con la generación de la misma.

    2. Ensamblados compilados que emiten las siguientes dos referencias (que se muestra en ILASM sintaxis):

      .assembly extern $Runtime
      {
      .ver 0:0:0:0
      }
      .assembly extern SharedLib
      {
      .ver 1:0:0:0
      }

      Aquí «SharedLib» es el Idioma predefinido biblioteca de tiempo de ejecución que incluye el «Cargador» de la clase se define anteriormente, y «$tiempo de ejecución» es la dinámica ensamblado en tiempo de ejecución que el consructed tipos se inserta.

    3. Un «módulo constructor» dentro de cada ensamblado compilado en el lenguaje.

      Que yo sepa, no hay ninguna .NET lenguajes que permitan Módulo de Constructores a ser definido en la fuente. El C++ /CLI compilador es el único compilador sé de que los genera. En IL, se parecen a esto, se define directamente en el módulo y no en el interior de cualquier tipo de definiciones:

      .method privatescope specialname rtspecialname static 
      void  .cctor() cil managed
      {
      //generate any constructed types dynamically here...
      }

      Para mí, no Es un problema que tengo que escribir personalizado IL para conseguir que esto funcione. Estoy escribiendo un compilador, por lo que la generación de código no es un problema.

      En el caso de una asamblea que utiliza los tipos tuple<i as int, j as int> y tuple<x as double, y as double, z as double> el módulo constructor necesitaría generar tipos como el siguiente (aquí en la sintaxis de C#):

      class Tuple_i_j<T, R>
      {
      public T i;
      public R j;
      }
      class Tuple_x_y_z<T, R, S>
      {
      public T x;
      public R y;
      public S z;
      }

      La tupla clases se generan como tipos genéricos de conseguir alrededor de los problemas de accesibilidad. Que permitiría código en el ensamblado compilado para usar tuple<x as Foo>, donde Foo es no-de tipo público.

      El cuerpo del módulo constructor que hizo esto (aquí sólo se muestra un tipo, y escrito en la sintaxis de C#) tendría este aspecto:

      var loader = SharedLib.Loader.Instance;
      lock (loader)
      {
      if (! loader.IsDefined("$Tuple_i_j"))
      {
      //create the type.
      var Tuple_i_j = loader.DefineType("$Tuple_i_j");
      //define the generic parameters <T,R>
      var genericParams = Tuple_i_j.DefineGenericParameters("T", "R");
      var T = genericParams[0];
      var R = genericParams[1];
      //define the field i
      var fieldX = Tuple_i_j.DefineField("i", T, FieldAttributes.Public);
      //define the field j
      var fieldY = Tuple_i_j.DefineField("j", R, FieldAttributes.Public);
      //create the default constructor.
      var constructor= Tuple_i_j.DefineDefaultConstructor(MethodAttributes.Public);
      //"close" the type so that it can be used by executing code.
      Tuple_i_j.CreateType();
      }
      }

    Por lo que en cualquier caso, este fue el mecanismo que yo era capaz de llegar con habilitar el equivalente aproximado de clase personalizada de los cargadores en el CLR.

    ¿Alguien sabe de una manera más fácil de hacer esto?

    • Ugh, ¿cómo es su módulo constructor definición diferente de la ordinaria constructor de la clase? Es la diferencia en el uso de privatescope frente a private hidebysig?
    • Ahh, me lo imaginé. Ninguna diferencia, excepto el módulo de cctor no está puesto en ningún tipo en particular. No sabía que se podía hacer eso 🙂
  2. -5

    Creo que este es el tipo de cosa que el DLR se supone que debe proporcionar en C# 4.0. Tipo de difícil de encontrar la información, pero tal vez vamos a aprender más en PDC08. Esperando ansiosamente para ver su C# 3 solución, aunque… supongo que se utiliza tipos anónimos.

Dejar respuesta

Please enter your comment!
Please enter your name here