9

so, object initializers are all kinds of handy - especially if you're doing linq, where they're downright necessary - but I can't quite figure out this one:

public class Class1 {
   public Class2 instance;
}

public class Class2 {
   public Class1 parent;
}

using like this:

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

as an initializer:

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

this doesn't work, class1 is supposedly an unassigned local variable. it gets even trickier in Linq when you are doing something like

select new Class1() { ...

it doesn't even have a name to refer to it by!

how do I get around this? can I simply not make nested references using object initializers?

6 Answers 6

7

can I simply not make nested references using object initializers?

You are right - you cannot. There would be a cycle; A requires B for initialization but B requires A before. To be precise - you can of course make nested object initializers but not with circular dependencies.

But you can - and I would suggest you should if possible - work this around as follows.

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;
}

Building the relation inside the property has the benifit that you cannot get an inconsitent state if you forget one of the initialization statements. It is quite obvious that this is an suboptimal solution because you need two statements to get one thing done.

b.Parent = a;
a.Child = b;

With the logic in the properties you get the thing done with only one statement.

a.Child = b;

Or the other way round.

b.Parent = a;

And finally with object initializer syntax.

A a = new A { Child = new B() };
Sign up to request clarification or add additional context in comments.

2 Comments

I like this answer best but it doesn't work for me, because I left out that Class2 is actually List<Class2>, to make the examples clearer. drat, it backfired.
Update from 2024: Unfortunately this approach does not work if #nullable is enabled.
2

I don't think there is anyway to get around this, when the application is instantiating the Class1 object it needs to have created the Class2 object first so that it knows where in memory the reference is located. You can see this if you try the following:

        Class1 myClass1 = null;

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

This will compile and run, but the parent property of Class2 will be null since that is what the value was at the time the inner line of code was run, meaning that the inner most line was the first to be executed.

Comments

2

You can't do that with Object Initializers. However, you can do the trick using propery code:

class A
{
    B b;

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

class B
{
    public A a;
}

Calling it:

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

Comments

1

This problem is not specific to object initializers, it's a general restriction in the C# language. You cannot use a local variable until it has been definitely assigned. Here is a simpler repro

Class1 Foo(Class1 c1) {
  return c1;
}

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

Comments

0

You can certainly use nested object initializers, I do it all the time.

However, in your particular case, your objects have a circular reference. You need to finish instantiating one before assigning it to the other. Otherwise, you are handing the compiler a classic chicken and egg problem it can't handle.

2 Comments

it's not a chicken and egg problem, at least not how I understand initializers: just syntactic sugar for declaring an object and setting its properties.
sorry - it "shouldn't" be one according to how the object initializers are explained. The first object is created, and then its parameters are assigned values. Since I can do this in the style that the object initiailizers supposedly compile down to, I should be able to do it with object initializers.
0

Your example does not reflect good class design, IMO; it's inappropriately cohesive and creates a circular reference. That's what makes it impossible to instantiate them together in a single expression.

I would suggest you go back to the drawing board and refactor your classes into a parent/child relationship. I'd use constructor injection on the child class and have the child tell the parent that it's a child of it.

For example:

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

Then, in your calling code:

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

And, yes, that works in LINQ:

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

but then how do I make all the ChildClass have the same ParentClass instance? – Andy Hohorst

Then you must know the parent class in advance.

var parent = new ParentClass();
var child = new ChildClass(parent);

or

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

1 Comment

but then how do I make all the ChildClass have the same ParentClass instance?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.