-2

I have a MainForm and a UserConfigForm and used the pattern from this answer for UserConfigForm i.e.

private static UserConfigForm openForm = null;

public static UserConfigForm GetInstance() 
{
    if (openForm == null)
    {
        openForm = new UserConfigForm();
        openForm.FormClosed += delegate { openForm = null; };
    }
    return openForm;
}

Inside UserConfigForm I also have an auto-property UserHasSaved i.e.

public bool UserHasSaved { get; private set; }

Now in the MainForm I need to check if I have to reload the user config when the config form is closed. So in the MainForm I have,

private UserConfigForm userCfgForm;

private void OpenEditFormClick(object sender, EventArgs e)
{
    userCfgForm = UserConfigForm.GetInstance();
    userCfgForm.FormClosed += ConfigFormClosed;
    userCfgForm.Show();
{

private void ConfigFormClosed(object sender, FormClosedEventArgs e)
{
    if (userCfgForm.UserHasSaved)
    {
        MessageBox.Show(message, caption);
        //Reload config
    }
}

The problem is that this works but I don't understand why it does. I have two event handlers registered to FormClosed so I decided it would be prudent to check in what order event handlers are processed.

It seems that event handlers are processed in the order they are registered. So it doesn't make sense that I can access userCfgForm.UserHasSaved after delegate { openForm = null }.

Should I worry about this or just be happy that it works?

skwear
  • 563
  • 1
  • 5
  • 24
  • I don't see how `userCfgForm` is accessible from within the `ConfigFormClosed` method when it's defined locally inside the `OpenEditFormClick` method. Is there some other declaration of that variable with the same name? – Rufus L Feb 20 '19 at 18:58
  • Sorry, I edited. I also defined a `UserConfigForm` locally in `MainForm` – skwear Feb 20 '19 at 19:01
  • I don't understand why you think this is behaving strangely. You've written the same information into two places. You've erased one of them. Suppose you are going on holiday, and you write your hotel room number down in two places. When you check out of the hotel, you throw away one of those pieces of paper. The other one still has your hotel room number written on it. They're separate pieces of paper. Can you explain why you think that nulling one variable has an effect on another? – Eric Lippert Feb 20 '19 at 19:01
  • When and where do you set the internal static variable _openform_ to null? Its value doesn't disappear even if you set the _userCfgForm_ to null. – Steve Feb 20 '19 at 19:01
  • 2
    I think you might have a very wrong idea about what it means to set a variable to null. The effect of setting a variable to null is: that variable now has the value null. If you think that setting a variable to null somehow destroys the object that used to be referenced by that variable, then you have a very wrong idea about how C# works and you should learn how it actually works. **That is the thing you should worry about**. – Eric Lippert Feb 20 '19 at 19:03
  • My understanding is when I call `UserConfigForm.GetInstance()` `userCfgForm` is referenced to `openForm`. Then when `openForm` is nulled then I should not be able to access the things inside `userCfgForm`, not because the config form has been destroyed but because it is now a null reference. – skwear Feb 20 '19 at 19:07
  • 1
    No, that is not at all right. `userCfgForm` and `openForm` *both reference the same object*. They do not reference *each other*. **It is absolutely crucial that you understand the difference** and you will not be successful doing C# programming if you don't have that clear. C# *does* allow you to make one variable an alias for another using the `ref` keyword, but you should not attempt to do so until you understand what references are. *References are values*. – Eric Lippert Feb 20 '19 at 19:07
  • There's no good reason to make that form static. Just create it when you need it, close it when you're done. – LarsTech Feb 20 '19 at 19:31
  • @LarsTech: I think the idea is to use the static variable to ensure that there is only ever one created at a time. – Eric Lippert Feb 20 '19 at 20:12

1 Answers1

9

You have a common beginner misunderstanding about how variables of reference type work in C#.

Let's look at a simpler example:

class F1
{
  static int x = 0;  
  public static int Start()
  {
    x = 1;
    return x;
  }
  public static void Stop()
  {
    x = 0;
  }
}

class F2
{
  int y;
  void DoIt()
  {
    this.y = F1.Start();
    F1.Stop();
    Console.WriteLine(this.y);
  }
}

Suppose we call DoIt on an instance of F2. What is the value of this.y?

Trace through the action of the program:

  • F1.x starts as zero.
  • F2.DoIt calls F1.Start()
  • F1.x becomes 1
  • F1.Start() returns 1
  • F2.y becomes 1
  • F2.DoIt calls F1.Stop()
  • F1.x becomes 0

F2.y is still 1. Changing F2.x didn't change anything about F2.y. It's a completely different variable. We did not create any sort of magical connection that said "when you read F2.y, really read the current value of F2.x".

The same is true in your program. We can change it to reference types and nothing changes:

class F1
{
  public static F1 x = null;  
  public static F1 Start()
  {
    x = new F1();
    return x;
  }
  public static void Stop()
  {
    x = null;
  }
}

class F2
{
  F1 y;
  void DoIt()
  {
    this.y = F1.Start();
    F1.Stop();
    Console.WriteLine(this.y == null); // false
  }
}

What happens? Same thing.

  • F1.x starts as null.
  • F2.DoIt calls F1.Start()
  • F1.x becomes a reference to a valid object
  • F1.Start() returns a reference to a valid object
  • F2.y becomes a reference to a valid object
  • F2.DoIt calls F1.Stop()
  • F1.x becomes null

What is F2.y? Still a reference to a valid object. F2.y was never a reference to F1.x. They were both a reference to the same valid object. References are values, just like integers.

Now, if you want to make an alias to a variable, C# 7 lets you do that:

class F1
{
  static int x = 0;  
  public static ref int Start()
  {
    x = 1;
    return ref x;
  }
  public static void Stop()
  {
    x = 0;
  }
}

class F2
{
  void DoIt()
  {
    ref int y = ref F1.Start();
    F1.Stop();
    Console.WriteLine(y); // 0
  }
}

The ref means local variable y is an alias to the variable F1.x, so when we change F2.x, we change y also, because they are just two names for the same variable; they are aliased.

Notice that an alias to a variable has nothing to do with the type of the variable. You can make an alias to an int variable, you can make an alias to an object variable, whatever. The aliased variable and the aliasing local must have exactly the same type (exercise: why?) but that type can be whatever you want.

The rule though is: the aliased variable can be any variable; the aliasing variable can only be a local or formal parameter. There's no way to make a "ref int" field, for instance. Notice that ref int y is a local, not a field.

Returning variable aliases is an advanced feature that we debated for many years whether it was right to add to C#. You should not use this feature until you have a thorough and deep understanding of reference semantics in C#.

Eric Lippert
  • 630,995
  • 172
  • 1,214
  • 2,051