Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# .NET:确定对象是部分构造的还是完全构造的_C#_.net_Constructor - Fatal编程技术网

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

C# .NET:确定对象是部分构造的还是完全构造的,c#,.net,constructor,C#,.net,Constructor,有没有办法查询.NET运行时以确定对象是否已完成其构造,或者构造是否仍在进行和/或是否因异常而中止 基本上相当于: class Foo { public string ConstructionState { get; private set; } public Foo() { try { ConstructionState = "ongoing"; // ... do actual constructor stu

有没有办法查询.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;
        }    
    }
}

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

行为良好的对象在完全构造之前不应该暴露自己。如果一个部分构造的对象被泄漏,那么您已经违反了该约定

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

如果您拥有该对象,解决方案很简单-在构建过程中不要泄漏该对象。在对象初始化期间进行一些全局更改的通常方法是使用静态方法(或工厂)而不是构造函数。如果你不拥有这个东西,那你就太倒霉了

运行时规范没有明确指出无法检查对象是否是部分构造的,但也没有说明是否存在(据我所知),因此即使找到了某种方法,依赖它也不安全。手工检查表明.NET对象头没有这样的信息,对构造函数的反汇编表明在构造函数完成后没有非用户代码可以更新这样的状态

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

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

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

类测试
{
公共静态WeakReference someInstance;
公共静态void AliveTest()
{
试验t;
if(someInstance==null)Console.WriteLine(“null”);
else Console.WriteLine(someInstance.TryGetTarget(out t));
}
公开考试()
{
someInstance=新的WeakReference(this);
AliveTest();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
AliveTest();
}
}
班级计划
{
静态void Main(字符串[]参数)
{
测试t=新测试();
Test.AliveTest();
Console.ReadLine();
}
}
这个测试程序写出True、False、False(确保在发布模式下运行它,并且不使用调试器-.NET阻止了许多类似的事情,以使调试更容易)。对象在其构造函数完成之前就已经被收集,这意味着在这方面没有对构造函数进行特殊处理。不使用“构造函数更新静态内容”模式的另一个原因,尤其是不使用“终结器更新它”。如果将终结器添加到此示例代码中,它将在构造函数完成之前运行。哎哟

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

CLI的一致性实现明确不要求保证在构造函数完成之前,在构造函数内执行的所有状态更新都是一致可见的

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


对于奖励积分,如果对象未密封,也没有帮助。派生类构造函数将在基类构造函数之后运行,而在C#中,无法重写它以包含通常按顺序运行的所有构造函数。您所能做的就是为每个构造函数维护一个单独的“构造状态”,这充其量是令人困惑的(并且打破了一些OOP原则——这将要求对象的所有使用者知道对象可能具有的所有类型)。

一个行为良好的对象在完全构造之前不应该暴露自己。如果一个部分构造的对象被泄漏,那么您已经违反了该约定

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

如果您拥有该对象,解决方案很简单-在构建过程中不要泄漏该对象。在对象初始化期间进行一些全局更改的通常方法是使用静态方法(或工厂)而不是构造函数。如果你不拥有这个东西,你就是漂亮的
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();
    }
}