方法调用中的静态变量空,但在程序中初始化

时间:2015-01-24 01:59:16

标签: c# .net appdomain visual-studio-sdk static-constructor

我在这里有点头疼,我想知道是否有人知道答案。

设置基本上是这样的:

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

让我脱头发的问题是,在IXmlSerializable.ReadXml()方法中,StaticClass.StaticVariable为null,即使在设置变量后它被称为RIGHT。

值得注意的是,没有命中断点,并且在问题发生的精确位置忽略Debugger.Launch()。

神秘的是,我通过提出异常来确定AppDomain.CurrentDomain.FriendlyName属性对于静态变量填充的位置与null相同!

为什么heck是超出范围的静态变量?!?这是怎么回事?!?我如何分享我的变量?

编辑:

我根据响应中的建议添加了一个静态构造函数,并让它执行Debug.WriteLine。我注意到它被调用了两次,即使所有代码似乎都在同一个AppDomain中运行。这是我在输出窗口中看到的内容,我希望这将是一个有用的线索:

静态构造函数调用于:2015-01-26T13:18:03.2852782-07:00

...已加载'C:... \ GAC_MSIL \ System.Numerics \ v4.0_4.0.0.0__b77a5c561934e089 \ System.Numerics.dll'...

...加载'Microsoft.GeneratedCode'......

...已加载'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;
    }
}

4 个答案:

答案 0 :(得分:2)

请记住,我只是复制了您的代码以尝试复制您的问题。 运行此代码时,我在Debug.Write上得到NullReferenceException,在解析调用之前,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问题。

答案 1 :(得分:2)

据我所知,您的代码是Visual Studio的插件,您的应用程序的主要问题是您的类被实例化两次,一次是普通AppDomain,一次是其他一些你无法真正发现的原因。

首先,我在这里看到Visual Studio中潜在的sandboxing - 它希望在各种权限集中测试您的代码,以确保您的代码不会损害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;
}

<强>更新

@Colin您甚至可以使用[LazyTask类型] [https://msdn.microsoft.com/en-us/magazine/dn683795.aspx] - 只需将Func传递给您的构造函数,这将是一种线程安全的方法,请参阅文章。 Id的{​​{1}}同样没有任何意义 - 沙箱可以通过AppDomain.ExecuteAssembly方法运行您的代码(它在4.5中已经过时,但仍可能是一种可能的变体)它在各种权限集中的表现如何。

可能在.NET 4.5中有另一种技术,但现在找不到相关的文章。

更新2:

正如我在您的代码中看到的那样,您正在从磁盘中读取一些信息。尝试为此添加Code Access Security规则,以查看您的代码是否在受限制的权限下运行,如下所示:

AppDomain

有关MSDN上的FileIOPermission类的更多信息。

答案 2 :(得分:1)

尝试向ContainerSingleton添加静态构造函数。我相信这是BeforeFieldInit再次抬起丑陋的脑袋。

答案 3 :(得分:0)

感谢所有提出建议的人!我从来没有弄清楚到底发生了什么(如果我做的话,我会继续调查/发布更新),但我最终想出了一个解决方法。

一些人建议静态类的初始化是内化的,但由于实际问题的性质,初始化逻辑必须外化(我基本上是加载一个DI /服务位置容器,其组成因环境而异) 。

另外,我怀疑它不会有帮助,因为我可以观察到静态构造函数被调用了两次(因此,无论初始化逻辑是什么,只会被调用两次,它没有直接解决问题)。

然而,这个建议让我走上正轨。

就我而言,我加载的服务都不需要是有状态的,因此除了性能命中之外,初始化发生两次并不重要。

因此,如果MEF容器已加载,我只需要进行静态类检查,如果我没有读取指定处理初始化的类的配置文件。

通过这样做,我仍然可以改变MEF容器的组成,从环境到环境,目前工作得很好,即使它不是理想的解决方案。

我想在所有帮助过的人之间分配赏金,但由于这似乎不可能,我可能会奖励OakNinja,因为他是一个英雄,尽可能多地提出好主意。根据我提供的信息,实际可以预期。再次感谢!