20

My problem here is that I would like to pass an object to a derived class, but it must be done before the base class constructor, since the base class will immediately call the derived class's Start() method that uses the object.

Here's an excerpt from the base class, (renamed from BarcodeScanner for convenience).

public abstract class MyBase
{    
    public MyBase()
    {
        if (Initialize())
            this.Start();
    }

    public abstract bool Initialize();
    public abstract void Start();
}

Here's the derived class that I'm creating.

class MyDerived : MyBase
{
    private string sampleObject;

    public MyDerived (string initObject)
    {
        sampleObject = initObject;
    }

    public override bool Initialize() 
    { 
        return GetDevice();
    }
    public override void Start() 
    { 
        Console.WriteLine("Processing " + sampleObject.ToString()); 
    }
}

I doubt you can make C# execute a derived constructor before the base constructor; so I'm really just looking for a solution to pass an object to the derived class before the object is used.

I've gotten around this by putting the Initialize/Start if block inside the MyDerived constructor. However, there are other classes deriving from the base class; so I ended up having to repeat this block of Initialize/Start code in every derived class. I'd like to see an alternative to modifying the base class.

James
  • 659
  • 1
  • 5
  • 15

4 Answers4

23

What you are trying to do is impossible in C#. A constructor in a base class must be run before the constructor of any derived class otherwise there would be potential for corrupt object state. A child object must be able to assume that its base is fully constructed and available.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
  • 2
    Well, it is possible to create empty override Initialize/Start methods, and move those code into derivedInitialize/derivedStart methods, and then in the derived constructor, call `if (derivedInitialize()) derivedStart()`. There may be no elegant solutions, but there are still other ways. – James Apr 09 '09 at 20:12
  • While it is often good for a framework to try to prevent the exposure of incompletely-constructed objects, there are times when it would be more useful for a class to have access to such objects, with the caveat that it must be prepared to deal with partially-constructed objects. For example, if a class will encapsulate an `IDisposable` object whose lifetime will match its own, it would be convenient to use a field initializer to set it up; alas in C# there is no clean way to do that safely. If the base-class constructor throws, nothing will be able to get a ref to the thing needing disposal. – supercat Sep 26 '12 at 16:26
22

IMHO your design is wrong. You shouldn't start the process from within the constructor. Your consuming code should explicitly call the Start() method when required.

Adam Ralph
  • 29,453
  • 4
  • 60
  • 67
  • +1. This guy is correct, you need .Start() / .Initialise(). Don't call virtual methods in constructors. – Quibblesome Apr 09 '09 at 17:32
  • +1. C++ restricts virtual calls from constructors for a reason. – Anton Tykhyy Apr 09 '09 at 17:39
  • I wholeheartedly agree that the design from MSDN isn't perfect, but it's been already implemented and it works well with the derived classes that don't need a passed in object. – James Apr 09 '09 at 20:01
  • MSDN is advocating such a design? Can you post a link please? – Adam Ralph Apr 09 '09 at 23:41
  • 3
    +1. This is why famous "InitializeComponent" appeared. You should spit construction and initialization logic. Make your "Initialize" let's say "protected virtual" and override it in the derived classes. You'll be able to decide whether "base.InitializeComponent" call should go first or last. – Denis Vuyka Apr 10 '09 at 06:09
  • The link to the MSDN page is in the question. BarcodeScanner is the abstract class on the page titled "Bar Code Scanners with the .NET Compact Framework". It's an old page, but still useful. – James Apr 13 '09 at 21:24
  • Call the virtual method from the constructor. Just use a static method like StashArgs to "stash" your derived constructors arguments in an Object and pass it to the base class like "base(StashArgs(a,b,c)). Then, when your base class constructor has to do work where the virtual method must be called between two steps the base class must take (e.g. private object Args = stashedArgs; BeginTran(); virtual CustomDerivedWork(); OtherWork()). That way, the virtual method can access the arguments and handle derived initialization, rather than the constructor doing it. – Triynko Aug 10 '18 at 14:05
  • You'd essentially be saying, screw C#, I want my constructor logic to run when I want it to run, and so, there you go. It works flawlessly, 100% of the time, and is 100% reliable. You're just moving constructor logic out of the actual constructor, and letting it run when the base class says it should run, and not when the prude C# says it must run. This kind of feature is available in ActionScript/JavaScript, where you can call base() whenever you want in the constructor. – Triynko Aug 10 '18 at 14:07
  • This kind of pattern is necessary when you need everything to run as part of construction. For example, I have a BatchQueryScope that derives from a DbTransactionScope object. To use the "using" pattern, you have to all the work of the base class AND the derived class (which inserts a batch of records) in the constructor. Otherwise, you'll have goofy stuff that defeats its purpose like having to call some Initialize method at the start of the scope. Totally pointless. Much easier to just make C# bend to do what you want it to. We're the designers. We make the machine do what we want it to. – Triynko Aug 10 '18 at 14:09
1

I would rework your design so that Initialize (and potentially Start() - though I'd normally have this be a public method that's called by the user) are called after construction.

If you're making a BarcodeScanner, you could do this the first time you go to scan. Just lazy-initialize your members using the data from the derived class.

This will work around your issue, with no real change in usage from the user.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • The scan is initiated asynchronously by the device, so you can't initialize based on scan start. In hindsight, Microsoft should've renamed their Start() method to be Ready() method. Since the Start() method only readies the scanner, but doesn't actually start the scan. – James Apr 09 '09 at 20:00
0

Sorry for adding to an old thread, but maybe someone is interested in another answer. I found an (IMO) neat way to handle logic doing something more than (and after) just assigning fields in class constructors where inheritance is involved here. If you just want to have if for this specific hierarchy and not use the generic solution with an interface and an extension method, you could use the same concept in one class tree like this:

public abstract class MyBase
{    
    protected MyBase()
    {
        Initialize(this) // just to illustrate this will never do anything as MyBase can never be the run time type.
    }

    protected bool IsInitialized { get; private set; } = false;

    protected static bool Initialize<T>(T instance) where T: MyBase
    {
        if (instance?.GetType() == typeof(T)) // check if this is called from the constructor of instance run time type
            return instance.IsInitialized || ( instance.IsInitialized = instance.Initialize() );
        return false;
    }

    protected abstract bool Initialize();
    public abstract void Start();
}

and derived:

class MyDerived : MyBase
{
    private string sampleObject;
    protected bool started = false;

    public MyDerived (string initObject)
    {
        sampleObject = initObject;
        if (Initialize(this)) // if this is the most derived constructor, this will run Initialize() and return whether it was successful
            this.Start();// EDIT: Just to illustrate. Normally constructors should only initialize an instance and not perform operations on it (as mentioned in other answers).
    }

    protected override bool Initialize() 
    { 
       return GetDevice();
    }

    public override void Start() 
    { 
        // if Start() would be protected, we don't need the IsInitialized property and we can move this check to the constructor on the returned value of the Initialize<T>() call.
        if (!IsInitialized) throw new InvalidOperationException("Initialization failed.");
        // if you want to have this method exposed public, we need to check if this instance is successfully initialized from the constructor and not in started state already.
        if (started) return;

        Console.WriteLine("Processing " + sampleObject.ToString()); 
        started = true;
        if (!Run(sampleObject)) started = false;
    }
}