C# constructor chaining - changing the order of execution
Asked Answered
I

4

7

I want to know how to change the order of execution when chaining constructors in C#. The only methods I have seen require the chained constructor to be called first, outside of the current constructor.

Specifically, take the following example:

public class Foo {
  private static Dictionary<string, Thing> ThingCache = new Dictionary<string, Thing>();
  private Thing myThing;

  public Foo(string name) {
    doSomeStuff();
    if (ThingCache.ContainsKey(name)) {
      myThing = ThingCache[name];
    } else {
      myThing = ExternalStaticFactory.GetThing(name);
      ThingCache.Add(name, myThing);
    }
    doSomeOtherStuff();
  }

  public Foo(Thing tmpThing) {
    doSomeStuff();
    myThing = tmpThing;
    doSomeOtherStuff();
  }
}

Ideally, I'd like to reduce code repetition by doing this (note, I admit that in this contrived example, not much code is saved, but I am working with code that would benefit much more. I use this example for clarity):

public class Foo {
  private static Dictionary<string, Thing> ThingCache = new Dictionary<string, Thing>();
  private Thing myThing;

  public Foo(string name) {
    if (ThingCache.ContainsKey(name)) {
      this(ThingCache[name]);
    } else {
      this(ExternalStaticFactory.GetThing(name));
      ThingCache.Add(name, myThing);
    }
  }

  public Foo(Thing tmpThing) {
    doSomeStuff();
    myThing = tmpThing;
    doSomeOtherStuff();
  }
}

This is possible in VB .Net, but C# doesn't let me call a constructor in the middle of another constructor - only at the beginning using the Foo() : this() syntax.

So my question is, how does one control the order of constructor calling when chaining constructors, rather than using the colon syntax, which can only call the other constructor first?

Inoperable answered 24/3, 2011 at 23:13 Comment(0)
D
8

You can't call constructors inside other constructors. A constructor can only chain another constructor to be called directly before it.

However, to solve your particular issue, you can make a private constructor that can accept both a types of argument, and have your two original constructors both simply chain this one, passing null for the missing argument.

This has the advantage over calling private initialisation methods that it plays nicely with readonly fields (i.e. it still works if myThing is a readonly field):

public class Foo
{
    private static Dictionary<string, Thing> ThingCache =
        new Dictionary<string, Thing>();
    private Thing myThing;

    public Foo(string name)
        : this(null, name)
    {
    }

    public Foo(Thing tmpThing)
        : this(tmpThing, null)
    {
    }

    private Foo(Thing tmpThing, string name)
    {
        if (tmpThing == null && name == null)
        {
            throw new System.ArgumentException(
                "Either tmpThing or name must be non-null.");
        }

        doSomeStuff();
        if (tmpThing != null)
        {
            myThing = tmpThing;
        }
        else
        {
            if (ThingCache.ContainsKey(name))
            {
                myThing = ThingCache[name];
            }
            else
            {
                myThing = ExternalStaticFactory.GetThing(name);
                ThingCache.Add(name, myThing);
            }
        }
        doSomeOtherStuff();
    }
}

You could also use named or optional arguments if you are using C# 4.0:

http://msdn.microsoft.com/en-us/library/dd264739.aspx

Denoting answered 24/3, 2011 at 23:34 Comment(2)
I like this answer. It's a little messy - not as straightforward and nice looking as the ability to call the constructor from an arbitrary point would be, but it definitely accomplishes what I was looking for without any of the drawbacks of calling Initialize() methods. Good thinking, thank you! One thing I would probably add is that the new composite constructor (the one that takes both arguments) should probably be private - since it's not really a "normal" entry point for a caller to use. I don't think I would want anyone else ever trying to call that one directly.Inoperable
Yep. Made the two-argument constructor private now.Denoting
S
7

I want to know how to change the order of execution when chaining constructors in C#.

You don't. There is no such feature in C#.

There already is a mechanism for calling arbitrary code in an arbitrary order: make a bunch of methods and call them in the order you like.

Here's my article on the subject going into more detail.

http://blogs.msdn.com/b/ericlippert/archive/2010/01/28/calling-constructors-in-arbitrary-places.aspx

If the topic of constructor chaining design principles interests you then you might also want to read

http://blogs.msdn.com/b/ericlippert/archive/2008/02/15/why-do-initializers-run-in-the-opposite-order-as-constructors-part-one.aspx

and

http://blogs.msdn.com/b/ericlippert/archive/2008/02/18/why-do-initializers-run-in-the-opposite-order-as-constructors-part-two.aspx

Semi answered 24/3, 2011 at 23:25 Comment(2)
The point is that if I put the code in different methods, then every constructor must know that they all must be called and in the correct order. If I can chain constructors in an arbitrary order (like in VB .net), then only one constructor needs to know this. Additionally, outside methods will not work with initonly members, as Ben mentioned below. What you get is still code repetition, you just reduce it a little by only repeating method calls rather than the whole chunk of code in the method.Inoperable
Not allowing you to decide when a chained constructor is called in C# was a rather unfortunate design decision. I've run into complex scenarios several times that could be addressed very easily if that was just allowed :/Lithotrity
D
1

You create private initialization methods inside your class and have your constructor logic call those methods.

class Foo
{
 public Foo(string name) 
 {
   InitializeBefore();

   if (ThingCache.ContainsKey(name)) 
   {
      myThing = ThingCache[name];
   } else 
   {
     myThing = ExternalStaticFactory.GetThing(name);
     ThingCache.Add(name, myThing);
   }

   InitializeAfter();
 }

 public Foo(Thing tmpThing) 
 {
   InitializeBefore();
   myThing = tmpThing;
   InitializeAfter();
 }

 private void InitializeBefore() 
 {
   doSomeStuff();
   // and any other calls you want before 
 }

 private void InitializeAfter() 
 {
   doSomeOtherStuff();
   // and any other calls you want at the end of the constructor
 }

}
Duet answered 24/3, 2011 at 23:17 Comment(3)
Of course, that doesn't play nicely with initonly fields (C# readonly keyword).Yardmaster
I don't see how creating initialization methods allow you to call a constructor from within another constructor. In fact, it seems that it would only offload the problem - now you have the repetition in the initialization methods rather than in the constructors. I don't see that as an improvement.Inoperable
Your example looks exactly like my original example - the thing I was trying to avoid doing by using constructor chaining. The problem is that now the logic of calling InitialIzeBefore() and InitializeAfter() has to be in every constructor. That's exactly what I want to avoid. Only one constructor should know that those methods need to be called and every other constructor should be able to benefit from that.Inoperable
T
0

You have to use a method. The most straightforward translation would be:

public class Foo {
  private static Dictionary<string, Thing> ThingCache = new Dictionary<string, Thing>();
  private Thing myThing;

  public Foo(string name) {
    if (ThingCache.ContainsKey(name)) {
      Init(ThingCache[name]);
    } else {
      Init(ExternalStaticFactory.GetThing(name));
      ThingCache.Add(name, myThing);
    }
  }

  public Foo(Thing tmpThing) {
    Init(tmpThing);
  }

  private void Init(Thing tmpThing) {
    doSomeStuff();
    myThing = tmpThing;
    doSomeOtherStuff();
  }
}

At least as long as you have no readonly fields. I have to admit, that a syntax to call other constructors at arbitrary locations has its advantages there.

Tempi answered 24/3, 2011 at 23:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.