Pregunta

Bien, me encontré con el siguiente problema que me llamó la atención.

Por varias razones, tengo una configuración de prueba donde las clases de prueba en TestingAssembly.dll dependen de la clase TestingBase en BaseTestingAssembly.dll.Una de las cosas que hace TestBase mientras tanto es buscar un determinado recurso integrado en sí mismo y en el ensamblado que lo llama.

Entonces mi BaseTestingAssembly contenía las siguientes líneas...

public class TestBase {    
  private static Assembly _assembly;
  private static Assembly _calling_assembly;

  static TestBase() {
    _assembly = Assembly.GetExecutingAssembly();
    _calling_assembly = Assembly.GetCallingAssembly();
  }
}

Estático, desde que pensé, estos ensamblajes serían los mismos durante la vida útil de la aplicación, entonces, ¿por qué molestarse en recalcularlos en cada prueba?

Sin embargo, al ejecutar esto, noté que tanto _assembly como _calling_assembly estaban configurados en BaseTestingAssembly en lugar de BaseTestingAssembly y TestingAssembly respectivamente.

Configurar las variables como no estáticas e inicializarlas en un constructor normal solucionó este problema, pero no estoy confundido sobre por qué sucedió esto al comenzar esto.Pensé que los constructores estáticos se ejecutan la primera vez que se hace referencia a un miembro estático.Esto solo podría haber sido de mi TestingAssembly, que entonces debería haber sido la persona que llama.¿Alguien sabe qué pudo haber pasado?

¿Fue útil?

Solución

El constructor estático lo llama el tiempo de ejecución y no directamente el código de usuario.Puede ver esto estableciendo un punto de interrupción en el constructor y luego ejecutándolo en el depurador.La función inmediatamente superior en la cadena de llamadas es código nativo.

Editar: Hay muchas formas en que los inicializadores estáticos se ejecutan en un entorno diferente al de otro código de usuario.Algunas otras formas son

  1. Están implícitamente protegidos contra condiciones de carrera resultantes del subproceso múltiple.
  2. No puedes detectar excepciones desde fuera del inicializador.

En general, probablemente sea mejor no usarlos para nada demasiado sofisticado.Puede implementar un inicio único con el siguiente patrón:

private static Assembly _assembly;
private static Assembly Assembly {
  get {
    if (_assembly == null) _assembly = Assembly.GetExecutingAssembly();
    return _assembly;
  }
}

private static Assembly _calling_assembly;
private static Assembly CallingAssembly {
  get {
    if (_calling_assembly == null) _calling_assembly = Assembly.GetCallingAssembly();
    return _calling_assembly;
  }
}

Agregue bloqueo si espera acceso multiproceso.

Otros consejos

Creo que la respuesta está aquí en la discusión de Constructores estáticos de C#.Mi mejor suposición es que se llama al constructor estático desde un contexto inesperado porque:

El usuario no tiene control sobre cuándo se ejecuta el constructor estático en el programa

Assembly.GetCallingAssembly() simplemente devuelve el ensamblado de la segunda entrada en la pila de llamadas.Eso puede depender mucho de dónde se llame su método/captador/constructor.Esto es lo que hice en una biblioteca para obtener el ensamblaje del primer método que no está en mi biblioteca.(Esto incluso funciona en constructores estáticos).

private static Assembly GetMyCallingAssembly()
{
  Assembly me = Assembly.GetExecutingAssembly();

  StackTrace st = new StackTrace(false);
  foreach (StackFrame frame in st.GetFrames())
  {
    MethodBase m = frame.GetMethod();
    if (m != null && m.DeclaringType != null && m.DeclaringType.Assembly != me)
      return m.DeclaringType.Assembly;
  }

  return null;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top