C#对象初始化器的嵌套使用

9

那么,对象初始化程序非常方便 - 特别是在使用 Linq 时,它们绝对是必需的 - 但我无法弄清楚这个:

public class Class1 {
   public Class2 instance;
}

public class Class2 {
   public Class1 parent;
}

使用方式如下:

Class1 class1 = new Class1();
class1.instance = new Class2();
class1.parent = class1;

作为初始化器:

Class1 class1 = new Class1() {
    instance = new Class2() {
        parent = class1
    }
};

这段代码无法正常工作,class1应该是一个未分配的本地变量。在使用Linq时进行如下操作时,情况会更加复杂:

select new Class1() { ...

它甚至没有一个名称来引用它!

我该怎么办?我是否不能使用对象初始化程序来进行嵌套引用?

6个回答

7

我能否不使用对象初始化程序创建嵌套引用?

你是正确的 - 不能。会出现循环依赖; A需要B进行初始化,但在此之前B需要A。确切地说 - 当然可以使用嵌套对象初始化程序,但不能有循环依赖。

但是你可以 - 并且我建议你应该尽可能这样做 - 按以下方式解决此问题。

public class A
{
   public B Child
   {
      get { return this.child; }
      set
      {
         if (this.child != value)
         {
            this.child = value;
            this.child.Parent = this;
         }
      }
   }
   private B child = null;
}

public class B
{
   public A Parent
   {
      get { return this.parent; }
      set
      {
         if (this.parent != value)
         {
            this.parent = value;
            this.parent.Child = this;
         }
      }
   }
   private A parent = null;
}

在属性内部建立关系的好处是,如果您忘记其中一个初始化语句,就不会出现不一致的状态。显然,这是一个次优解,因为您需要两个语句才能完成一件事。
b.Parent = a;
a.Child = b;

通过属性的逻辑,您只需一条语句就可以完成任务。

a.Child = b;

或者反过来。
b.Parent = a;

最后是使用对象初始化语法。
A a = new A { Child = new B() };

我最喜欢这个答案,但它对我不起作用,因为我忽略了Class2实际上是List<Class2>,以使示例更清晰。该死,它适得其反了。 - Andy Hohorst

2

我认为没有什么方法可以绕过这个问题,当应用程序实例化 Class1 对象时,它需要先创建 Class2 对象,以便知道引用在内存中的位置。您可以通过尝试以下操作来查看这一点:

        Class1 myClass1 = null;

        myClass1 = new Class1()
        {
            instance = new Class2
            {
                parent = myClass1
            }
        };

这段代码可以编译并运行,但是Class2的父级属性将为null,因为在执行内部代码行时它的值也是null,这意味着最内层的代码行首先被执行。

2
您不能使用对象初始化程序来完成这个操作。不过,您可以通过适当的属性代码来实现此功能:
class A
{
    B b;

    public B B
    {
        set
        {
            b = value;
            b.a = this;
        }
        get
        {
            return b;
        }
    }
}

class B
{
    public A a;
}

调用它:

var a = new A  {  B = new B() };

1

这个问题不仅限于对象初始化器,它是 C# 语言的一般限制。在变量被明确赋值之前,您不能使用它。以下是一个更简单的复现:

Class1 Foo(Class1 c1) {
  return c1;
}

void Example() {
  Class1 c1 = Foo(c1);
}

0

在我看来,你的示例并不反映出良好的类设计;它过于紧密耦合并且创建了循环引用。这就是为什么无法在单个表达式中一起实例化它们的原因。

我建议你回到草图板上,将你的类重构为父/子关系。我会在子类上使用构造函数注入,并让子类告诉父类它是它的子类。

例如:

public class ParentClass 
{
    public List<ChildClass> Children;
    public void AddChild(ChildClass child)
    {
        Children.Add(child);
        // or something else, etc. 
    }
    // various stuff like making sure Children actually exists before AddChild is called
}

public class ChildClass 
{
    public ParentClass Parent;
    public ChildClass(ParentClass parent)
    {
        Parent = parent;
        Parent.AddChild(this);
    }
}

然后,在您的调用代码中:

var parent = new ChildClass(new ParentClass()).Parent;

是的,在LINQ中也可以这样做:

// qry, etc.
select new ChildClass(new ParentClass()).Parent

“但是我如何让所有的ChildClass都有相同的ParentClass实例呢?”- Andy Hohorst
那么你必须事先知道父类。
var parent = new ParentClass();
var child = new ChildClass(parent);

或者

var parent = new ParentClass();
// qry, etc.
select new ChildClass(parent)

但是我该如何让所有的ChildClass都拥有相同的ParentClass实例? - Andy Hohorst

0

您可以肯定地使用嵌套对象初始化程序,我经常这样做。

然而,在您的特定情况下,您的对象具有循环引用。您需要先完成一个实例化,然后再将其分配给另一个。否则,您会将一个经典的鸡和蛋问题交给编译器,它无法处理。


这并不是一个鸡生蛋的问题,至少在我理解初始化器时不是:它只是声明对象并设置其属性的语法糖。 - Andy Hohorst
抱歉 - 根据对象初始化器的解释,它“不应该”是一个。首先创建第一个对象,然后为其参数分配值。由于我可以按照对象初始化器所编译的样式执行此操作,因此我应该能够使用对象初始化器来完成此操作。 - Andy Hohorst

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