2015-01-24 3 views
9

У меня здесь немного головной уборщик, и мне интересно, может ли кто-нибудь узнать ответ.Static Variable Null In Method Call, но инициализирован в программе

Установка в основном это:

//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; 
    } 
} 

Проблема, которую я потянув меня за волосы, что StaticClass.StaticVariable является недействительным в IXmlSerializable.ReadXml() метод, даже если он вызывается сразу после переменной установлен.

Следует отметить, что точки останова не пострадали, а Debugger.Launch() игнорируется в точном месте, когда проблема возникает.

Загадочно, я определил путем создания исключений, что свойство AppDomain.CurrentDomain.FriendlyName одинаково для места, где статическая переменная заполняется против нуля!

Почему heck - это статическая переменная из сферы?!? Что происходит?!? Как я могу поделиться своей переменной?

EDIT:

Я добавил статический конструктор, за предложением в ответах, и она была сделать Debug.WriteLine. Я заметил, что он вызывается дважды, хотя весь код работает в том же AppDomain. Вот то, что я вижу в окне вывода, который я надеюсь, будет полезной подсказкой:

Статический конструктор вызывается в: 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' ....

... Загружено 'C: \ USERS ... \ APPDATA \ LOCAL \ MICROSOFT \ VISUALSTUDIO \ 12.0EXP \ EXTENSIONS ... SharePointAdapter.dll'. Загружены символы.

... Загружено «Microsoft.GeneratedCode».

Статический конструктор вызывается в: 2015-01-26T13: 18: 03.5196524-07: 00

ДОПОЛНИТЕЛЬНЫЕ ДЕТАЛИ:

Вот фактический код, так как пару комментаторам думал, что это могло бы помочь :

//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

Мы не можем ответить на это с помощью данной информации. Это не отображает вашу ситуацию, поэтому в лучшем случае мы можем дать вам ответ «yup, выглядит странно». Покажите цепочку вызовов методов и как эти фрагменты относятся к eachother. –

+2

Либо Visual Studio, либо WinDbg должны предоставить вам хотя бы стек вызовов при вызове исключения. С объемом информации, которую вы предоставили, это не отвечает ... Обратите внимание, что, вероятно, есть способы избежать статических полей (но это должен быть отдельный вопрос). –

+0

Если это один и тот же объект AppDomain и статический конструктор, который вызывается дважды, это означает, что у вас на самом деле есть 2 типа - дамп подробной информации о самом типе ('typof (StaticClass) .FullName' может быть достаточно), чтобы убедиться, что это так. –

ответ

0

Спасибо всем, кто предложил предложения! Я никогда не выяснял, что происходит (и продолжайте исследовать/публиковать обновления, если я когда-либо сделаю), но я в конечном итоге придумал обходной путь.

Некоторые предположили, что инициализация статического класса была интернализована, но из-за характера фактической проблемы логика инициализации должна была быть внешней (я в основном загружал контейнер местоположения DI/службы, состав которого варьировался от среды к окружающей среде).

Кроме того, я подозреваю, что это не помогло бы, так как я мог заметить, что статический конструктор вызывался дважды (таким образом, любая логика инициализации была бы просто вызвана дважды, что напрямую не затрагивало проблему).

Однако предложение набрало меня на правильном пути.

В моем случае ни одна из служб, которые я загрузил, не требовала сохранения состояния, поэтому на самом деле не имело значения, что инициализация произошла дважды в стороне от удара производительности.

Поэтому у меня просто была проверка статического класса, если был загружен контейнер MEF, и если бы я не читал файл конфигурации, в котором указывался класс, который обрабатывал инициализацию.

Таким образом, я все еще могу изменять состав контейнера MEF из окружающей среды в окружающую среду, который в настоящее время работает очень хорошо, даже если это не идеальное решение .

Я хотел бы разделить щедрость между всеми, кто помогал, но поскольку это не представляется возможным, я, вероятно, вознагражу OakNinja, так как он был героем, выплевывая столько хороших идей, сколько можно было бы ожидать учитывая предоставленную мной информацию. Еще раз спасибо!

1

Попробуйте добавить статический конструктор ContainerSingleton. Я считаю, что это BeforeFieldInit снова поднимает свою уродливую голову.

+0

Хорошо, но это не сработало, к сожалению. Однако это привело к пониманию. У меня был статический конструктор Debug.WriteLine(), и я увидел, что он был вызван дважды, второй раз после загрузки некоторого сгенерированного кода. Я добавлю детали к OP. – Colin

+0

Тот факт, что он выполняется дважды, означает, что у вас есть 2 домена приложения (возможно, у них одно и то же имя?). В статье, которую я связал, он цитирует спецификацию C# как: ** Статический конструктор для класса выполняется не более одного раза в данном домене приложения ** – user1620220

+0

Да, это то, что так озадачивает; он должен выполняться только один раз для домена приложения, но когда я включаю AppDomain.CurrentDomain.FriendlyName или AppDomain.CurrentDomain.ID в детали Exception, все одинаково. Вполне возможно, что действительно есть два AppDomains, объединенных с одинаковыми подробностями, но мне нужно определить, почему и нужно найти способ решения проблемы, как только я это сделаю. Вот где я застрял. – Colin

2

Помните, что я только что скопировал ваш код, чтобы попытаться воспроизвести вашу проблему. При запуске этого кода я получаю исключение NullReferenceException на Debug.Write, AnotherClass не был правильно инициализирован до разрешения вызова.

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; 
     } 
    } 


} 

Однако, когда я добавить статические конструкторы для всех классов со статическими полями он работает, как ожидалось:

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; 
     } 
    } 


} 

Я бы сказал, что это определенно может быть проблемой BeforeFieldInit.

+2

Отличное руководство и пример, но, к сожалению, он НЕ работает! Я добавил статические конструкторы, которые я использую на выходе Debug для своего OP. – Colin

+0

Но как насчет класса, вызывающего ContainerSingleton.ContainerInstance? Это может быть не ContainerSingletonclass, это проблема, даже если это похоже на это. – OakNinja

+0

Класс, вызывающий его, фактически не является статическим. Это код XAML-кода (указанный в примере «Приложение»). – Colin

2

Как я понял, ваш код является плагином для Visual Studio, и основная проблема вашего приложения заключается в том, что ваш класс создается дважды, один раз для обычного AppDomain, и один раз по какой-то другой причине вы можете Я действительно узнаю.

Прежде всего, я вижу здесь потенциальный sandboxing из Visual Studio - он хочет протестировать ваш код в различных наборах прав, чтобы ваш код не повредил другим компонентам работы Visual Studio или конечного пользователя. В этом случае ваш код может быть загружен в другой AppDomain без каких-либо прав (вы можете найти good article at the MSDN), поэтому вы можете понять, почему ваш код вызывается дважды для каждого приложения.

Во-вторых, я хочу отметить, что вы непонимание идеи статической constructor и статического method:

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

не то же самое, как

public static ContainerSingleton() 
{ 
    compositionContainer = container; 
} 

Итак, я предлагаю вам двигаться вся логика инициализации в статическом контейнере, примерно такая:

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); 
    } 
} 

Второго подхода: Я хотел бы отметить, что шаблон, который вы используете для получения одноплодного устарел, попробуйте использовать Lazy<T> класс, что-то вроде этого:

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 
    } 
} 

Кроме того, вы должны помнить, что простое добавление пустого статические конструкторы не достаточно - вы должны переместить все задания к нему, так что вы должны заменить такой код:

public class AnotherClass 
{ 
    static AnotherClass() 
    { 

    } 

    public static String Test = ContainerSingleton.ContainerInstance; 
} 

с этим:

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

    public static String Test; 
} 

Update:

@Colin Вы можете даже использовать [LazyTask тип] [https://msdn.microsoft.com/en-us/magazine/dn683795.aspx] - просто передать Func в конструкторе, и это будет потокобезопасной подход, больше в этой статье. Тот же Id из AppDomain ничего не значит - песочница может запускать ваш код с помощью метода AppDomain.ExecuteAssembly (он устарел в 4.5, но все еще может быть возможным вариантом), чтобы увидеть, как он ведет себя в разных наборах разрешений.

Может быть, в .NET 4.5 есть еще одна методика, но не могу найти статью, связанную прямо сейчас.

Update 2:

Как я могу видеть в вашем коде, вы читаете какую-то информацию с диска.Попробуйте добавить Code Access Security правило для этого, чтобы увидеть, если ваш код в настоящее время бежал под ограниченными правами, как это:

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); 
} 

Подробнее о FileIOPermission класса на MSDN.

+0

Спасибо за ответ! Пара примечаний: статические конструкторы существуют на некоторое время (см. Комментарии к другим ответам и обновленный примерный код). Инициализация не может иметь место в конструкторе в моем случае, потому что инициализация должна быть экстернализована, так как она должна изменяться от среды к среде (код используется в нескольких контекстах). На соответствующей заметке это означает, что я не лениво загружаю поле, хотя это аккуратный трюк. – Colin

+0

Разъяснение к моему последнему комментарию: ContainerSingleton повторно используется в нескольких контекстах, но MefInitializer и клиент приложения специфичны для рассматриваемого контекста. Кроме того, я ранее проверял AppDomain.CurrentDomain.FriendlyName и AppDomain.CurrentDomain.Id, и они были идентичны в вызывающем клиенте и код ошибки FWIW. – Colin

+0

@Colin Обновлен ответ. – VMAtm