Статическая Переменная Null В Вызове Метода, Но Инициализируется В Программе

у меня есть немного головы царапины здесь, что я задаюсь вопросом, Может ли кто-то знать ответ.

настройка в основном такова:

//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 имеет значение null в IXmlSerializable.Метод ReadXml (), даже если он вызывается сразу после установки переменной.

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

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

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

EDIT:

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

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

...Loaded ' C:...Gac_msilSystem.Numericsv4.0_4.0.0.0__b77a5c561934e089система.Численные данные.в DLL'...

...Загрузки Майкрософт.GeneratedCode'...

...Нагруженный C:...Gac_msilSystem.XML.Linqv4.0_4.0.0.0__b77a5c561934e089система.XML.В LINQ.в DLL'....

...Загрузил C:USERS...APPDATALOCALMICROSOFTVISUALSTUDIO12.0 EXPEXTENSIONS...SharePointAdapter.в DLL'. Символы загружены.

...Загрузки Майкрософт.GeneratedCode'.

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

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

вот фактический код, так как несколько комментаторов думали, что он может помогите!--3-->

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

4 ответов


имейте в виду, что я только что скопировал ваш код, чтобы попытаться воспроизвести вашу проблему. При запуске этого кода я получаю исключение NullReferenceException при отладке.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.


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

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

во-вторых, я хочу отметить, что вы неправильно поняли идею статический!--9--> и статический!--10-->:

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

обновление:

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

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

обновление 2:

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

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.


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


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

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

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

тем не менее, предложение привело меня на правильный путь.

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

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

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

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