在C#中,字段初始化器和对象初始化器如何交互?

5

我主要是一名C++开发人员,但最近我在使用C#开发一个项目。今天我在使用对象初始化器时遇到了一些意外的行为,至少对我来说是这样。我希望这里有人能够解释一下发生了什么。

示例A

public class Foo {
    public bool Bar = false;
}

PassInFoo( new Foo { Bar = true } );

示例 B

public class Foo {
    public bool Bar = true;
}

PassInFoo( new Foo { Bar = false } );

示例A按预期工作。传递给PassInFoo的对象具有Bar设置为true。然而,在示例B中,即使在对象初始化程序中分配了false,foo.Bar也被设置为true。是什么导致示例B中的对象初始化程序似乎被忽略?


6
没事,你看到的不是你想看到的。 - mqp
3
我无法复现这个问题,你用的是 .Net 3.5 版本吗?请确认你是否看到了这种行为。 - Noon Silk
4
@Scott - 不是要说你不好,但你的评论是错误的。一些评论者(包括我)已经尝试过了,它可以编译得很好。 - Joel Lee
2
这段代码实际上是 Unity3D 项目的一部分,使用 MonoDevelop 进行编译。我原本以为 Unity 使用标准的 C#,但这可能并不完全正确。在 Visual Studio 中尝试给定的代码后,我无法重现它。因此,要么 Unity 在做一些奇怪的事情,要么示例代码并不代表我的实际代码。我会进一步研究哪种情况是真实的。 - nschrag
3
@Scott:只要您可以访问相关字段,对象初始化程序在字段上也能很好地工作。请参阅C#规范的第7.6.10.2节。 - Jon Skeet
显示剩余3条评论
4个回答

5
我确认在Mono(Mono 2.6.5,Unity3d 4.1.2f1,OSX)的Unity3d构建中存在这个丑陋的bug。
看起来它不喜欢使用ValueType的默认值,因此您可以正常使用 int! = 0 ,(bool)true 等,但是传递像(int)0 或(bool)false 之类的默认值将忽略其值。
证明:
using UnityEngine;
using System.Collections;

public class Foo1 {
    public bool Bar=false;
}

public class Foo2 {
    public bool Bar=true;
}

public class Foo1i {
    public int Bar=0;
}

public class Foo2i {
    public int Bar=42;
}

public class PropTest:MonoBehaviour {

    void Start() {
        PassInFoo(new Foo1 {Bar=true}); // FOO1: True (OK)
        PassInFoo(new Foo2 {Bar=false});/// FOO2: True (FAIL!)
        PassInFoo(new Foo1i {Bar=42});  // FOO1i: 42 (OK)
        PassInFoo(new Foo2i {Bar=0});/// FOO2i: 42 (FAIL!)
        PassInFoo(new Foo2i {Bar=13});/// FOO2i: 13 (OK)
    }

    void PassInFoo(Foo1 f) {Debug.Log("FOO1: "+f.Bar);}

    void PassInFoo(Foo2 f) {Debug.Log("FOO2: "+f.Bar);}

    void PassInFoo(Foo1i f) {Debug.Log("FOO1i: "+f.Bar);}

    void PassInFoo(Foo2i f) {Debug.Log("FOO2i: "+f.Bar);}
}

在非Unity3D的OSX上,Mono 2.10.11(mono-2-10/2baeee2 于2013年1月16日 星期三 EST 16:40:16发布)测试运行良好:

FOO1: True
FOO2: False
FOO1i: 42
FOO2i: 0
FOO2i: 13

编辑:在Unity3D的错误跟踪器中填写了一个错误:https://fogbugz.unity3d.com/default.asp?548851_3gh8hi55oum1btda


1
刚刚在Unity 5(仅编辑器)上进行了检查 - 这个错误仍然存在。愉快调试! - Krzysztof Bociurko
1
刚刚收到 Unity QA 的消息 - 他们将在未来的某个版本中修复此错误。 - Krzysztof Bociurko
厉害的东西,@ChanibaL - Fattie

3
最简单的方法是将语句拆分成逐行执行的等效语句,以便更好地理解其运行过程。
PassInFoo( new Foo { Bar = false } );

拆分出来:

var tmp = new Foo();    //Bar initialized to true
tmp.Bar = false;
PassInFoo( tmp );

顺便提一下,我在.NET 3.5和4.0中测试了上述内容,在两种情况下,PassInFoo内的Bar实际上是false。 - Thomas
我在我的Unity3D项目中尝试了一下,它正确地工作了。Unity或MonoDevelop必须对忽略对象初始化器赋值进行一些“优化”,如果这些赋值是类型的默认值(例如在我的示例代码中,布尔值为false)。我可能需要把这个问题带到那些社区里寻求更具体的答案。 - nschrag
2
@nschrag - 发现其中一个环境干扰了评估顺序将是非常令人不安的。祝你好运! - Thomas
顺便提一下:如果您正在初始化匿名类型,比如 new { Bar = false },那么没有默认构造函数被调用。除非匿名类型是 new { },否则匿名类型根本没有默认的空构造函数。只是说,因为命名类型 new Foo { Bar = false } 和匿名类型 new { Bar = false } 看起来都使用了相同类型的对象初始化器。 - nawfal

3

我在使用Unity和Mono时遇到了类似的问题。

我通过在构造函数中初始化字段,然后使用对象初始化程序进行覆盖来解决了这个问题。

但是!起初这也不起作用。所以我用具有支持字段的属性替换了字段,它似乎强制实现了预期行为。


2
class Program
{
    static void Main(string[] args)
    {
        PassInFoo( new Foo { Bar = false } );
    }
    public class Foo
    {
        public bool Bar = true;
    }

    public static void PassInFoo(Foo obj)

    {
        Console.WriteLine(obj.Bar.ToString());
        Console.ReadLine();
    }
}

当使用 Framework 3.5 进行验证时,上述代码可以正常工作(控制台窗口中显示 false)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接