.NET:确定对象是部分构造还是完全构造

问题描述 投票:0回答:1

有没有办法查询.NET运行时以确定对象是否已完成其构造,或者构造是否仍在进行和/或是否因异常而中止?

基本上,相当于:

class Foo {
    public string ConstructionState { get; private set; }

    public Foo() {
        try {
            ConstructionState = "ongoing";

            // ... do actual constructor stuff here ...

            ConstructionState = "completed";
        }
        catch (Exception) {
            ConstructionState = "aborted";
            throw;
        }    
    }
}

...除了考虑字段初始值设定项,基类构造函数等之外,无需修改构造函数。

c# .net constructor
1个回答
2
投票

一个表现良好的对象永远不会暴露自己,直到它完全构建。如果部分构造的对象泄露,您已经违反了该合同。

当然,运行时并不关心。就运行时而言,部分构造的对象没有什么特别之处 - 它仍然受到相同的内存约束,最终化和垃圾收集作为完全构造的对象。

如果您拥有该对象,解决方案很简单 - 在施工期间不要泄漏对象。在对象初始化期间进行一些全局更改的常用方法是使用静态方法(或工厂)而不是构造函数。如果你不拥有这个物品,那你就差点不走运了。

运行时规范没有明确说没有办法检查一个对象是否是部分构造的,但它没有说(或者我可以告诉) - 所以即使你找到某种方式,它也不会依靠它是安全的。手动检查显示.NET对象头没有这样的信息,并且构造函数的反汇编表明在构造函数完成后可能没有非用户代码可以更新这样的状态。

运行时确实在“怪异”的地方存储了一些标志。例如,桌面MS.NET中的标记和清除垃圾收集器将其标记存储在指向虚拟方法表的指针的“未使用”位中。但就运行时而言,该对象甚至在其任何构造函数运行之前就已“完成” - 所有这些都是在构造函数(特殊实例方法)运行之前在newobj中的分配期间处理的。对象头(也包含对象大小)和虚方法表(因此在构造函数运行之前对象是派生类型最多的)已经在此处设置,并且该实例直接使用的所有内存都已分配(和预先归零 - 所以你没有得到指向随机内存位的指针。这意味着就运行时而言,内存安全性不受部分构造的对象的影响。

构造函数和另一个实例方法之间的主要区别在于构造函数只能在任何实例上调用一次。在CIL级别,这只是通过你不能直接调用构造函数来实现的 - 你只使用newobj,它将构造的对象推送到堆栈上。就像其他实例方法一样,它不会跟踪某个特定方法是否完成 - 毕竟,拥有一个永不完成的方法是完全合法的,并且您实际上可以使用(非静态)构造函数执行相同的操作。

如果你想要运行时不关心的证明,我会向你展示......在构造函数完成之前,GC可以收集对象:

class Test
{
    public static WeakReference<Test> someInstance;

    public static void AliveTest()
    {
        Test t;
        if (someInstance == null) Console.WriteLine("Null");
        else Console.WriteLine(someInstance.TryGetTarget(out t));
    }

    public Test()
    {
        someInstance = new WeakReference<Test>(this);

        AliveTest();

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        AliveTest();
    }
}

class Program
{
    static void Main(string[] args)
    {
        Test t = new Test();
        Test.AliveTest();
        Console.ReadLine();
    }
}

这个测试程序写出True,False,False(确保在Release模式下运行它,没有调试器 - .NET可以防止这样的很多事情使调试更容易)。在构造函数完成之前已经收集了对象,这意味着在这方面没有对构造函数进行特殊处理。另一个原因是不使用“构造函数更新某些静态”模式,尤其不是“终结器更新它”。如果为此示例代码添加终结器,它将在构造函数完成之前运行。哎哟。

在一般情况下,即使您的解决方案也不够。引用CLI规范:

显然不要求CLI的一致性实现保证在构造函数完成之前在构造函数中执行的所有状态更新是统一可见的。

不能保证另一个线程有关于构造状态的正确信息。

对于奖励积分,如果物品未密封也无济于事。派生类构造函数将在基类构造函数之后运行,而在C#中,无法重写它以包含通常在序列中运行的所有构造函数。你能做的最好的事情是为每个构造函数维护一个单独的“构造状态”,这最多会让人感到困惑(并打破一些OOP原则 - 它会要求对象的所有使用者知道对象可能拥有的所有可能的类型)。

© www.soinside.com 2019 - 2024. All rights reserved.