user2982010
user2982010

Reputation: 77

Global object instance?

I want to create a global instance and don't know if its possible, take the following for example

I have a String_Example Class:

namespace Pass_Object_as_Reference_Example
{
    public class String_Example
    {
        public string _str {get;set;}

        public String_Example(string Cadena)
        {
            _str = Cadena;
        }
    }
}

And this is my Form1 code:

public partial class Form1 : Form  
{
    String_Example cadena;

    public Form1(ref String_Example str)
    {
        InitializeComponent();
        cadena = str;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        cadena = new String_Example(textBox1.Text);
        this.Close();
    }
}

This is my Main code:

static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            String_Example test = null;

            Form1 frm = new Form1(ref test);
            Application.Run(frm);

            MessageBox.Show(test._str);
        }

This is what I imagine would happen:

  1. Create "test" object null
  2. Pass the object as reference to the form
  3. Form construct takes the "test" reference and assign it to object "cadena" (since they are objects and are passed by reference I would guess that Cadena now has the address of test)
  4. when the button in form 1 is pressed the constructor is called and the textbox string is assigned, the form is closed
  5. Use a messagebox to check the string in the test object (which should have the string assigned in form 1 but all we have is a null reference.

I think that when I'm calling the constructor I'm destroying the first reference and that is why the final object is null right?

Should I create the object in the main code and just modify the parameters in the subsequent forms?

Upvotes: 0

Views: 5019

Answers (2)

John Wu
John Wu

Reputation: 52240

What your code is doing

  1. Create "test" object null

You actually aren't creating an object. You are setting test to a null reference.

  1. Pass the object as reference to the form

You are actually passing a reference to a reference to test. If you just want to pass a reference, remove the ref keyword. The value of a variable that is a reference type is already just reference to the object. A ref object is not a reference to an object but a reference to an object reference. See this link if this is not clear to you.

  1. Form construct takes the "test" reference and assign it to object "cadena"

You are populating the variable cadena with a null.

3a. (since they are objects and are passed by reference I would guess that Cadena now has the address of test)

No. It has the value of test, which is a null reference. I think you are trying to store a reference to an object reference (i.e. a pointer to test). You are in fact only storing a pointer that is equivalent to the value held in test, which in this case is null.

  1. When the button in form 1 is pressed the constructor is called and the textbox string is assigned, the form is closed.

Correct

  1. Use a messagebox to check the string in the test object (which should have the string assigned in form 1 but all we have is a null reference.

There is no code in your example which goes back and populates test with a valid reference.

Why this will never work

If you want to pass a pointer to a variable, you will need to use unmanaged code, according to this article:

In an unsafe context, a type may be a pointer type, a value type, or a reference type. A pointer type declaration takes one of the following forms:

(emphasis added)

You can't do it in c#... and in c++ I wouldn't do it, either, since there is no guarantee the pointer will get garbage collected. That's why this pattern is not allowed in managed code.

How to do what you're trying to do

I'll give you three options. There are probably a few more (Singleton, ByRef<T>, etc.) but I think these are the most common.

Set up parent/child between program and form(s)

If you are writing an app where you have a series of forms that need to access global state, one common pattern is to make the Program itself the container for global state, and pass it as a parent parameter to anything that needs it. So here's your code again, following this pattern:

public class String_Example
{
    public string _str {get;set;}

    public String_Example(string Cadena)
    {
        _str = Cadena;
    }
}

public partial class Form1 : Form  
{
    readonly Program _parent;  //Reference to class which contains global state.  readonly = can only be set in constructor and can never change.

    public Form1(Program parent)
    {
        InitializeComponent();
        _parent = parent;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        _parent.test = new String_Example(textBox1.Text);
        this.Close();
    }
}

public class Program
{
    public String_Example test;  //Notice this is now a member variable

    static public void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        test = null;

        Form1 frm = new Form1(this);
        Application.Run(frm);

        MessageBox.Show(test._str);
    }
}

Make the global variables static

Or, if you don't want to pass pointers around to child objects, you can make the global variables static:

public partial class Form1 : Form  
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Program.test = new String_Example(textBox1.Text);
        this.Close();
    }
}

public class Program
{
    static public String_Example test;  //Notice this is now a static variable, so it can be accessed without a reference to an instance of Program

    static public void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        test = null;

        Form1 frm = new Form1(this);
        Application.Run(frm);

        MessageBox.Show(test._str);
    }
}

The "best" way

The best way, or the most modern way that seems to be trendy these days, is to

  1. Create a class (or classes) specifically intended to contain global state
  2. Implement the state variables as member variables
  3. Use a factory like Autofac to get an instance of the state container(s)
  4. Tell the factory you only want a single instance no matter how many times you call it

For example

class ProgramGlobals
{
    public String_Example Test {get; set; }
}

class Program
{
    static void SetupFactory()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<ProgramGlobals>().SingleInstance();
    }

    static void Main()
    {
        SetUpFactory();
        var globals = Container.Resolve<ProgramGlobals>();
        globals.Test = null;

        Form1 frm = new Form1();
        Application.Run(frm);

        MessageBox.Show(globals.Test._str);
    }
}

public partial class Form1: Form
{
    private voide button1_Click(object sender, EventArgs e)
    {
        var globals = Container.Resolve<ProgramGlobals>();
        globals.Test._str = new String_Example(textbox1.Text)
    }
}

You are free to use whatever IoC toolkit you want, e.g. AutoFac or Unity. This article compares the major IoC libraries that are available for .NET.

This method is preferred because it allows a unit tester to provide their own stub for global state without mocking, which is often the hardest part of setting up unit tests.

Note that in the example above I am getting references to ProgramGlobals with executing code. You can also set up your objects to get a reference automatically, using Dependency Injection. Great topic, but my answer is already pretty long, so you may have to research that on your own.

Upvotes: 2

Eugene Podskal
Eugene Podskal

Reputation: 10401

The simplest way to achieve what you want without using global state is to use some class like:

public class ByRef<T> 
{
    public T Value
    {
        get;
        set;
    }   
}

Then in Main:

var byRef = new ByRef<String_Example>();
Form1 frm = new Form1(byRef);

And in Form1:

public partial class Form1 : Form
{
    ByRef<String_Example> cadena;

    public Form1(ByRef<String_Example> str)
    {
        if (str == null)
            throw new ArgumentNullException("str");

        InitializeComponent();
        cadena = str;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        cadena.Value = new String_Example(textBox1.Text);
        this.Close();
    }
}

Upvotes: 0

Related Questions