2015-01-24 24 views
9

Ich habe ein bisschen Kopf-Scratcher hier, dass ich frage mich, ob jemand die Antwort wissen kann.Statische Variable Null In Methode Call, aber in Programm initialisiert

Das Setup ist im Grunde diese:

//in Visual Studio plug-in application 
SpinUpProgramWithDebuggerAttached(); 

//in spun up program 
void Start() 
{ 
    StaticClass.StaticVariable = "I want to use this."; 
    XmlSerializer.Deserialize(typeof(MyThingie), "xml"); 
} 

class MyThingie : IXmlSerializable 
{ 
    ReadXml() 
    { 
     //why the heck is this null?!? 
     var thingIWantToUse = StaticClass.StaticVariable; 
    } 
} 

Das Problem, das ich mir die Haare aus hat ziehen ist, dass StaticClass.StaticVariable im IXmlSerializable.ReadXml null ist() -Methode, obwohl es direkt nach der Variable genannt ist eingestellt.

Beachten Sie, dass Haltepunkte nicht getroffen werden und Debugger.Launch() an der genauen Stelle ignoriert wird, an der das Problem auftritt.

Mysteriös, ich festgestellt, durch Auslösen von Ausnahmen, dass die AppDomain.CurrentDomain.FriendlyName -Eigenschaft ist das gleiche für die Stelle, die die statische Variable bevölkert vs. null ist!

Warum zum Teufel ist die statische Variable außerhalb des Umfangs?!? Was ist los?!? Wie kann ich meine Variable teilen?

EDIT:

Ich habe einen statischen Konstruktor, pro einem Vorschlag in den Antworten, und es hatte eine Debug.WriteLine zu tun. Ich habe bemerkt, dass es zweimal aufgerufen wurde, obwohl der gesamte Code in derselben AppDomain ausgeführt wird. Hier ist, was ich im Ausgabefenster sehen, was ich hoffe, wird ein nützlicher Hinweis sein:

Statische Konstruktor aufgerufen am: 2015-01-26T13: 18: 03.2852782-07: 00

. ..Loaded 'C: ... \ GAC_MSIL \ System.Numerics \ v4.0_4.0.0.0__b77a5c561934e089 \ System.Numerics.dll' ...

... Loaded 'Microsoft.GeneratedCode' ...

... Loaded 'C: ... \ GAC_MSIL \ System.Xml.Linq \ v4.0_4.0.0.0__b77a5c561934e089 \ System.Xml.Linq.dll' ....

... Geladen 'C: \ BENUTZER \ APPDATA \ LOCAL \ MICROSOFT \ VISUALSTUDIO \ 12.0EXP \ EXTENSIONS ... SharePointAdapter.dll'. Symbole geladen.

... Geladen "Microsoft.GeneratedCode".

Statische Konstruktor aufgerufen am: 2015-01-26T13: 18: 03.5196524-07: 00

ZUSÄTZLICHE DETAIL:

Hier ist der eigentliche Code, seit ein paar commen dachte, es könnte helfen :

//this starts a process called "Emulator.exe" 
var testDebugInfo = new VsDebugTargetInfo4 
{ 
    fSendToOutputWindow = 1, 
    dlo = (uint)DEBUG_LAUNCH_OPERATION.DLO_CreateProcess, 
    bstrArg = "\"" + paramPath + "\"", 
    bstrExe = EmulatorPath, 
    LaunchFlags = grfLaunch | (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_StopDebuggingOnEnd | (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_WaitForAttachComplete, 
    dwDebugEngineCount = 0, 
    guidLaunchDebugEngine = VSConstants.CLSID_ComPlusOnlyDebugEngine, 
}; 

var debugger = Project.GetService(typeof(SVsShellDebugger)) as IVsDebugger4; 
var targets = new[] { testDebugInfo }; 
var processInfos = new[] { new VsDebugTargetProcessInfo() }; 

debugger.LaunchDebugTargets4(1, targets, processInfos); 

//this is in the emulator program that spins up 
public partial class App : Application 
{ 
    //***NOTE***: static constructors added to static classes. 
    //Problem still occurs and output is as follows (with some load messages in between): 
    // 
    //MefInitializer static constructor called at: 2015-01-26T15:34:19.8696427-07:00 
    //ContainerSingleton static constructor called at: 2015-01-26T15:34:21.0609845-07:00. Type: SystemTypes.ContainerSingleton, SystemTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=... 
    //ContainerSingleton static constructor called at: 2015-01-26T15:34:21.3399330-07:00. Type: SystemTypes.ContainerSingleton, SystemTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=... 

    protected override void OnStartup(StartupEventArgs e) 
    { 
     //... 

     //initializes a MEF container singleton (stored as static variable) 
     MefInitilizer.Run(); 

     //here's where it blows up. the important details are that 
     //FullSelection implements IXmlSerializable, and its implemention 
     //ends up referencing the MEF container singleton, which ends up 
     //null, even though it was initialized in the previous line. 
     //NOTE: the approach works perfectly under a different context 
     //so the problem is not the code itself, per se, but a problem 
     //with the code in the environment it's running in. 
     var systems = XmlSerialization.FromXml<List<FullSelection>>(systemsXml); 
    } 
} 

public static class MefInitilizer 
{ 
    static MefInitilizer() { Debug.WriteLine("MefInitializer static constructor called at: " + DateTime.Now.ToString("o")); } 

    public static void Run() 
    { 
     var catalog = new AggregateCatalog(); 
     //this directory should have all the defaults 
     var dirCatalog = new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); 
     //add system type plug-ins, too 
     catalog.Catalogs.Add(dirCatalog); 

     var container = new CompositionContainer(catalog); 
     ContainerSingleton.Initialize(container); 
    } 
} 

public class ContainerSingleton 
{ 
    static ContainerSingleton() 
    { 
     Debug.WriteLine("ContainerSingleton static constructor called at: " + DateTime.Now.ToString("o") + ". Type: " + typeof(ContainerSingleton).AssemblyQualifiedName); 
    } 
    private static CompositionContainer compositionContainer; 

    public static CompositionContainer ContainerInstance 
    { 
     get 
     { 
      if (compositionContainer == null) 
      { 
       var appDomainName = AppDomain.CurrentDomain.FriendlyName; 
       throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName); 
      } 
      return compositionContainer; 
     } 
    } 

    public static void Initialize(CompositionContainer container) 
    { 
     compositionContainer = container; 
    } 
} 
+3

Wir können dies mit den gegebenen Informationen nicht beantworten. Dies zeigt Ihre Situation nicht so gut an, wir können Ihnen eine "yup, sieht komisch" -Antwort geben. Zeigen Sie die Kette von Methodenaufrufen und wie diese Snippets miteinander verknüpft sind. –

+2

Entweder Visual Studio oder WinDbg sollten Sie mindestens Call-Stack geben, wenn eine Ausnahme ausgelöst wird. Mit der Menge an Informationen, die Sie bereitgestellt haben, ist dies nicht beantwortbar ... Beachten Sie, dass es wahrscheinlich Möglichkeiten gibt, statische Felder zu vermeiden (aber es sollte eine separate Frage sein). –

+0

Wenn es die gleiche AppDomain und statischen Konstruktor zweimal aufgerufen wird, bedeutet das, dass Sie tatsächlich 2 Typen haben - dumpen detaillierte Informationen über den Typ selbst ('typof (StaticClass) .FullName' kann ausreichen), um zu sehen, ob dies der Fall ist. –

Antwort

0

Vielen Dank an alle, die Vorschläge gemacht haben! Ich habe nie genau herausgefunden, was vor sich geht (und werde weiterhin nachforschen/Updates veröffentlichen, wenn ich das jemals tue), aber ich habe mir einen Workaround ausgedacht.

Einige haben vorgeschlagen, dass die Initialisierung der statischen Klasse verinnerlicht wird, aber aufgrund der Natur des eigentlichen Problems musste die Initialisierungslogik externalisiert werden (ich habe im Grunde einen DI/Service-Standort-Container geladen, dessen Zusammensetzung von Umgebung zu Umgebung variierte).

Auch ich vermute, dass es nicht geholfen hätte, da ich beobachten konnte, dass der statische Konstruktor zweimal aufgerufen wurde (also, welche Initialisierungslogik es gab, würde nur zweimal aufgerufen, was das Problem nicht direkt ansprach).

Aber der Vorschlag hat mich auf den richtigen Weg gebracht.

In meinem Fall brauchte keiner der Dienste, die ich geladen habe, statusbehaftet zu sein, also war es nicht wirklich wichtig, dass die Initialisierung zweimal neben dem Performance-Treffer erfolgte.

Daher hatte ich einfach die statische Klasse überprüfen, ob der MEF-Container geladen wurde, und wenn es nicht war, würde ich eine Konfigurationsdatei lesen, die eine Klasse angegeben, die Initialisierung behandelt.

Dadurch könnte ich immer noch die Zusammensetzung des MEF-Containers von Umgebung zu Umgebung variieren, die derzeit ziemlich gut funktioniert, auch wenn es keine ideale Lösung ist.

Ich möchte das Kopfgeld zwischen allen teilen, die geholfen haben, aber da das nicht möglich scheint, werde ich wahrscheinlich OakNinja belohnen, da er ein Held war, der so viele gute Ideen ausspuckte, wie realistisch zu erwarten war gegeben die Informationen, die ich zur Verfügung gestellt habe. Danke noch einmal!

1

Versuchen Sie, einen statischen Konstruktor ContainerSingleton hinzufügen. Ich glaube, das ist BeforeFieldInit Anhebung seines hässlichen Kopfes wieder.

+0

Gute Blei, aber es hat leider nicht funktioniert. Es ergab jedoch einen Hinweis. Ich hatte den statischen Konstruktor eine Debug.WriteLine() und ich sah, dass es zweimal aufgerufen wurde, das zweite Mal, nachdem einige generierten Code geladen wurde. Ich werde dem OP Details hinzufügen. – Colin

+0

Die Tatsache, dass es zweimal ausgeführt wird, bedeutet, dass Sie 2 App-Domänen haben (vielleicht haben sie den gleichen Namen?). In dem Artikel, den ich verlinkt habe, wird die C# -Spezifikation zitiert: ** Der statische Konstruktor für eine Klasse wird höchstens einmal in einer gegebenen Anwendungsdomäne ausgeführt. ** – user1620220

+0

Ja, das ist so verwirrend; Es sollte nur einmal pro App-Domäne ausgeführt werden, aber wenn ich AppDomain.CurrentDomain.FriendlyName oder AppDomain.CurrentDomain.ID in die Ausnahmedetails einbeziehe, ist alles identisch. Es könnte sehr gut sein, dass es tatsächlich zwei AppDomains gibt, die mit identischen Details gesponnen sind, aber ich muss herausfinden, warum und muss einen Weg finden, um das Problem zu lösen, sobald ich es tue. Da stecke ich fest. – Colin

2

Denken Sie daran, ich habe gerade Ihren Code kopiert, um zu versuchen, Ihr Problem zu replizieren. Wenn ich diesen Code ausführe, erhalte ich eine NullReferenceException für Debug.Write, AnotherClass wurde nicht ordnungsgemäß initialisiert, bevor der Anruf aufgelöst wird.

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      MefInitilizer.Run(); 
      Debug.Write(AnotherClass.Test); 
     } 
    } 

    public class AnotherClass 
    { 
     public static String Test = ContainerSingleton.ContainerInstance; 
    } 

    public static class MefInitilizer 
    { 
     public static void Run() 
     { 
      ContainerSingleton.Initialize("A string"); 
     } 
    } 

    public class ContainerSingleton 
    { 
     private static String compositionContainer; 

     public static String ContainerInstance 
     { 
      get 
      { 
       if (compositionContainer != null) return compositionContainer; 

       var appDomainName = AppDomain.CurrentDomain.FriendlyName; 
       throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName); 
      } 
     } 

     public static void Initialize(String container) 
     { 
      compositionContainer = container; 
     } 
    } 


} 

Allerdings, wenn ich statische Konstruktoren für alle Klassen mit statischen Feldern fügen Sie sich wie erwartet funktioniert:

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      MefInitilizer.Run(); 

      Debug.Write(AnotherClass.Test); 
     } 
    } 

    public class AnotherClass 
    { 
     static AnotherClass() 
     { 

     } 

     public static String Test = ContainerSingleton.ContainerInstance; 
    } 

    public static class MefInitilizer 
    { 
     static MefInitilizer() 
     { 

     } 
     public static void Run() 
     { 

      ContainerSingleton.Initialize("A string"); 
     } 
    } 

    public class ContainerSingleton 
    { 
     static ContainerSingleton() 
     { 

     } 
     private static String compositionContainer; 

     public static String ContainerInstance 
     { 
      get 
      { 
       if (compositionContainer != null) return compositionContainer; 

       var appDomainName = AppDomain.CurrentDomain.FriendlyName; 
       throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName); 
      } 
     } 

     public static void Initialize(String container) 
     { 
      compositionContainer = container; 
     } 
    } 


} 

Ich würde sagen, dass dies auf jeden Fall ein BeforeFieldInit Problem sein könnte.

+2

Excellent führen und Beispiel, aber leider funktioniert es immer noch nicht! Ich habe die statischen Konstruktoren, die ich an der Debug-Ausgabe verwende, zu meinem OP hinzugefügt. – Colin

+0

Aber wie wäre es mit der Klasse, die ContainerSingleton.ContainerInstance aufruft? Es ist möglicherweise nicht die ContainerSingletonclass, die das Problem ist, obwohl es so scheint. – OakNinja

+0

Die Klasse, die sie aufruft, ist nicht wirklich statisch. Es ist ein XAML-Code-Behind (im Beispielcode als "App" aufgeführt). – Colin

2

Wie ich verstanden habe, ist Ihr Code ein Plug-in für ein Visual Studio, und das Hauptproblem Ihrer Anwendung ist, dass Ihre Klasse zweimal instanziiert wird, einmal für eine normale AppDomain, und einmal aus einem anderen Grund können Sie Ich werde es wirklich herausfinden.

Zunächst sehe ich hier ein Potenzial sandboxing von einem Visual Studio - es möchte Ihren Code in verschiedenen Gruppen von Rechten testen, um sicherzustellen, dass Ihr Code keine anderen Teile des Visual Studio oder Endbenutzer arbeiten wird. In diesem Fall könnte Ihr Code ohne weitere Rechte in eine andere AppDomain geladen werden (Sie finden eine good article at the MSDN), damit Sie verstehen, warum Ihr Code zweimal pro Anwendung aufgerufen wird.

Zweitens möchte ich darauf hinweisen, dass Sie die Idee des statischen constructor und statische method sind Missverständnis:

public static void Initialize(CompositionContainer container) 
{ 
    compositionContainer = container; 
} 

ist nicht das gleiche wie

public static ContainerSingleton() 
{ 
    compositionContainer = container; 
} 

Also, ich schlage vor, Sie zu bewegen, die All-Initialisierungs-Logik in einen statischen Container, etwa so:

public class ContainerSingleton 
{ 
    private static CompositionContainer compositionContainer; 

    public static CompositionContainer ContainerInstance 
    { 
     get 
     { 
      if (compositionContainer == null) 
      { 
       var appDomainName = AppDomain.CurrentDomain.FriendlyName; 
       throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName); 
      } 
      return compositionContainer; 
     } 
    } 

    public static ContainerSingleton() 
    { 
     var catalog = new AggregateCatalog(); 
     //this directory should have all the defaults 
     var dirCatalog = new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); 
     //add system type plug-ins, too 
     catalog.Catalogs.Add(dirCatalog); 

     compositionContainer = new CompositionContainer(catalog); 
    } 
} 

Zweiter Ansatz: Ich möchte darauf hinweisen, dass das Muster, das Sie für das Erhalten des Singleton verwenden ist veraltet, versuchen Sie die Lazy<T> Klasse zu verwenden, etwa so:

public class ContainerSingleton 
{ 
    private static Lazy<CompositionContainer> compositionContainer; 

    public static CompositionContainer ContainerInstance 
    { 
     get 
     { 
      return compositionContainer.Value; 
     } 
    } 

    public static ContainerSingleton() 
    { 
     compositionContainer = new Lazy<CompositionContainer>(() => Initialize()); 
    } 
    public static void Initialize() 
    { 
     // Full initialization logic here 
    } 
} 

Außerdem sollten Sie daran denken einfach, dass das leere Hinzufügen statischen Konstruktoren ist nicht genug - Sie sollten alle Zuweisungen um es bewegen, so sollten Sie einen solchen Code ersetzen:

public class AnotherClass 
{ 
    static AnotherClass() 
    { 

    } 

    public static String Test = ContainerSingleton.ContainerInstance; 
} 

mit dieser:

public class AnotherClass 
{ 
    static AnotherClass() 
    { 
     Test = ContainerSingleton.ContainerInstance; 
    } 

    public static String Test; 
} 

Update:

@Colin Sie können sogar verwenden [LazyTask Typ] [https://msdn.microsoft.com/en-us/magazine/dn683795.aspx] - einfach eine Func zu Ihrem Konstruktor übergeben, und es wird eine Thread-sichere Ansatz finden Sie mehr dazu im Artikel sein. Das gleiche Id der AppDomain bedeutet nichts - die Sandbox könnte Ihren Code über AppDomain.ExecuteAssembly Methode laufen (es ist veraltet in 4.5, könnte aber immer noch eine mögliche Variante sein) zu sehen, wie es sich in verschiedenen Berechtigungen verhält.

Möglicherweise gibt es eine andere Technik dafür in .NET 4.5, aber kann einen Artikel im Zusammenhang jetzt nicht finden. 2

Update:

Wie ich in Ihrem Code sehen kann, werden Sie einige Informationen von der Festplatte zu lesen.Versuchen Sie, eine Code Access Security Regel hinzuzufügen dafür zu sehen, wenn Ihr Code unter eingeschränkten Berechtigungen lief wird, wie folgt aus:

FileIOPermission f2 = new FileIOPermission(FileIOPermissionAccess.Read, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); 
//f2.AddPathList(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, "C:\\example\\out.txt"); 
try 
{ 
    f2.Demand(); 
} 
catch (SecurityException s) 
{ 
    Console.WriteLine(s.Message); 
} 

Mehr über FileIOPermission Klasse auf MSDN.

+0

Danke für die Antwort! Ein paar Anmerkungen: Die statischen Konstruktoren sind seit einiger Zeit vorhanden (siehe Kommentare zu anderen Antworten und aktualisierter Beispielcode). Die Initialisierung kann in meinem Fall nicht im Konstruktor stattfinden, da die Initialisierung externalisiert werden sollte, da sie von Umgebung zu Umgebung variieren muss (der Code wird in mehreren Kontexten verwendet). Zusammengefasst bedeutet das, dass ich das Feld nicht faul lade, obwohl das ein netter Trick ist. – Colin

+0

Erläuterung zu meinem letzten Kommentar: ContainerSingleton wird in mehreren Kontexten wiederverwendet, aber MefInitializer und der App-Client sind für den jeweiligen Kontext spezifisch. Außerdem habe ich zuvor AppDomain.CurrentDomain.FriendlyName und AppDomain.CurrentDomain.Id überprüft und sie waren identisch im aufrufenden Client und Fehlercode, FWIW. – Colin

+0

@Colin Aktualisierte die Antwort. – VMAtm