tags:

views:

176

answers:

4

Long-time joelonsoftware follower, 1st-time stackoverflow poster.

I want to know "how safely" I can do the following (C#):

Form formDlg = new Form();
TextBox box = new TextBox();
formDlg.Controls.Add( box );
formDlg.ShowDialog();
formDlg.Dispose();
string sUserEntered = box.Text; // After parent Dispose'd!

In practice, this (apparently) works, because box (as a Control) has a private text field (a string) which it uses to implement its Text property after its window handle is destroyed.

I won't be satisfied by a general answer that "you can't access an object after it's Disposed" because (1) I can't find any such blanket prohibition in MS docs, (2) I'm not accessing an unmanaged resource, and (3) this code doesn't throw any exception (including ObjectDisposedException).

I would like to do this so I can create and use a combined "ShowAndDispose" method to reduce the risk of forgetting to always call Dispose() after ShowDialog().

To complicate, the behavior changes in the debugger. If I break before Dispose(); then Quick Watch box and drill down into its Control base class; then step past Dispose(); then box.Text returns ""! In other scenarios box.Text returns the user-entered text.

+2  A: 

You can use the 'using' statement to ensure an object gets disposed when you're done with it:

using(Form frmDialog = new Form())
{
    //Do stuff
}

frmDialog will get disposed once the block has run I believe.

RSlaughter
Doesn't really change his question. In comparison the `string sUserEntered = box.Text;` would come after the using block.
Zyphrax
Yes, that is a better cure than a ShowAndDispose() method.
Henk Holterman
@Zyphrax, no, it would solve the OP's problem about forgetting Dispose and of course the box.Text code would go inside the using.
Henk Holterman
Conrad Albrecht
@Conrad: get used to `using`, everybody does. And you get a try/finally for free.
Henk Holterman
@Conrad: do use `using` if you can, it's far nicer than those try finally blocks. @Henk: Then why isn't the `string sUserEntered = box.Text;` called before the `formDlg.Dispose` line. In comparison with the example in the question, it would come after the using block (to do exactly the same). - I'm not saying it makes sense though ;)
Zyphrax
A: 

I put the sUserEntered value into a public property so it could be accessed:

    public string UserInput
    {
        get;
        set;
    }

    public frmDialog()
    {
        //
        // The InitializeComponent() call is required for Windows Forms designer support.
        //
        InitializeComponent();

        //
        // TODO: Add constructor code after the InitializeComponent() call.
        //
    }

    void Button1Click(object sender, EventArgs e)
    {
        UserInput = userInput.Text;
        this.Dispose();
    }

Then in my mainform:

        using (dialog = new frmDialog())
        {
            dialog.ShowDialog();
            stringUserInput.Text = dialog.UserInput;
        };
dboarman
Thx, I know I can do that, creating a whole new class is a lot more extra code than I want.
Conrad Albrecht
+1  A: 

It is an implementation detail that this code runs without a problem. The Control.Text property happens to be cached by the Control class so disposing the TextBox doesn't cause an ObjectDisposed exception.

That's fairly rare btw, lots of control property getters and setters generate a Windows message to ask the native Window control for the property value. You'll get a kaboom on those because the Handle property is no longer valid. Notable also is that the Text property setter updates the cached value but also generates a Window message to update the native control. Kaboom here.

I assume this is just general interest, don't ever use code like that in your program. Well, you'd find out quick enough.

Hans Passant
Thx, best answer yet.
Conrad Albrecht
A: 

It occurs to me, I can create & use a Form-derived class with a BeginShowDialog() method which calls ShowDialog(), and an EndShowDialog() method which calls Dispose(). The "Begin" in the method name will make the need for the "End" call more obvious.

I miss C++'s determinate destruction of locals on leaving scope.

Conrad Albrecht
You'd still have to wrap it in a try/finally block, so the gain over using is debatable.
Henk Holterman
I'll skip the try/finally. In the never-happens-in-the-real-world case that ShowDialog() throws, I'll live with the resource leak.
Conrad Albrecht
I disagree that "using" "solves" the problem I want to address, which is never forgetting, for the hundreds of classes I use, when/whether each one needs to be disposed. If I don't remember that, then I won't remember to use "using" for that class any better than I'll remember to call Dispose().
Conrad Albrecht
Yes, the way I reuse my own code, if I had a ModalDialog class which hid ShowDialog() then I think I'd use it. But honestly, after spending this much time discussing Dispose() it'll probably be a long time before i forget it again. ;)
Conrad Albrecht