【问题标题】:Static Variable Null In Method Call, But Initialized In Program方法调用中的静态变量为空,但在程序中初始化
【发布时间】:2015-03-23 04:14:59
【问题描述】:

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

设置基本上是这样的:

//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,即使在设置变量后立即调用它。

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

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

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

编辑:

根据响应中的建议,我添加了一个静态构造函数,并让它执行 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;
    }
}

【问题讨论】:

  • 我们无法用给定的信息回答这个问题。这不会显示您的情况,因此我们最多可以给您一个“是的,看起来很奇怪”的回应。显示方法调用链以及这些 sn-ps 如何相互关联。
  • 在抛出异常时,Visual Studio 或 WinDbg 至少应该为您提供调用堆栈。根据您提供的大量信息,这是无法回答的......请注意,可能有一些方法可以避免使用静态字段(但它应该是单独的问题)。
  • 如果它是同一个 AppDomain 和静态构造函数调用了两次,这意味着你实际上有 2 种类型 - 转储有关类型本身的详细信息(typof(StaticClass).FullName 可能就足够了)看看是否是这种情况。
  • “这就是我知道它是同一类型的原因。” - 在这种情况下,您唯一确定的是生成消息的事实类型来自同一来源......不幸的是,仅短信不足以确认没有从同一来源创建的多个类型无论出于何种原因......
  • @VMAtm - 感谢您的反馈,但这可能是一个红鲱鱼,因为这篇文章是关于 Java 及其框架的。

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


【解决方案1】:

请记住,我刚刚复制了您的代码以尝试复制您的问题。 运行此代码时,我在 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 问题。

【讨论】:

  • 优秀的领导和榜样,但不幸的是它仍然不起作用!我将在 Debug 输出中使用的静态构造函数添加到我的 OP。
  • 但是调用 ContainerSingleton.ContainerInstance 的类呢?问题可能不是 ContainerSingleton 类,尽管它看起来很像。
  • 调用它的类实际上并不是静态的。它是 XAML 代码隐藏(在示例代码中列为“应用程序”)。
  • 您想在某个地方完整分享您的源代码吗?我们好像漏掉了什么。
  • 可能与应用程序的入口点有关。您可以尝试显式定义应用程序入口点。请参阅此问题的答案。 stackoverflow.com/questions/6156550/…
【解决方案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&lt;T&gt; 类,如下所示:

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 传递给您的构造函数,这将是一种线程安全的方法,请参阅文章中的更多内容。 AppDomain 的相同 Id 没有任何意义 - 沙盒可以通过 AppDomain.ExecuteAssembly 方法运行您的代码(它在 4.5 中已过时,但仍可能是一个可能的变体),以查看它在各种权限集下的行为。

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

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

更多关于 MSDN 上的FileIOPermission 课程。

【讨论】:

  • 感谢您的回复!一些注意事项:静态构造函数已经存在了一段时间(请参阅其他回复中的 cmets 和更新的示例代码)。在我的例子中,初始化不能在构造函数中进行,因为初始化应该被外部化,因为它需要因环境而异(代码在多个上下文中使用)。在相关说明中,这意味着我不会延迟加载该字段,尽管这是一个巧妙的技巧。
  • 澄清我的最后一条评论:ContainerSingleton 在多个上下文中重复使用,但 MefInitializer 和 App 客户端特定于相关上下文。另外,我之前检查了 AppDomain.CurrentDomain.FriendlyName 和 AppDomain.CurrentDomain.Id ,它们在调用客户端和错误代码 FWIW 中是相同的。
  • @Colin 更新了答案。
【解决方案3】:

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

【讨论】:

  • 很好,但很遗憾,它没有奏效。然而,它确实导致了一个线索。我让静态构造函数执行了 Debug.WriteLine(),我看到它被调用了两次,第二次是在加载了一些生成的代码之后。我会在 OP 中添加细节。
  • 它执行两次的事实意味着您有 2 个应用程序域(也许它们具有相同的名称?)。在我链接的文章中,它引用了 c# 规范的话说:类的静态构造函数在给定的应用程序域中最多执行一次
  • 是的,这就是令人费解的地方;它应该每个应用程序域只执行一次,但是当我在异常详细信息中包含 AppDomain.CurrentDomain.FriendlyName 或 AppDomain.CurrentDomain.ID 时,一切都是相同的。很可能确实存在两个具有相同细节的 AppDomains,但我需要确定原因,并且需要找到解决问题的方法。这就是我卡住的地方。
【解决方案4】:

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

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

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

但是,这个建议让我走上了正轨。

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

因此,我只需让静态类检查 MEF 容器是否已加载,如果未加载,我将读取一个配置文件,其中指定了一个处理初始化的类。

通过这样做,我仍然可以根据环境改变 MEF 容器的组成,目前运行良好,即使它不是一个理想的解决方案。

我想将赏金平分给所有提供帮助的人,但由于这似乎不可能,我可能会奖励 OakNinja,因为他是一位英雄,可以吐出尽可能多的好主意我提供的信息。再次感谢!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-07
    • 2017-05-13
    • 2012-05-25
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多