全局对象实例?

Global object instance?

我想创建一个全局实例,不知道是否可行,以下面为例

我有一个 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;
        }
    }
}

这是我的 Form1 代码:

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();
    }
}

这是我的主要代码:

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);
        }

这是我想象中会发生的事情:

  1. 创建 "test" 对象 null
  2. 将对象作为对表单的引用传递
  3. Form 构造采用 "test" 引用并将其分配给对象 "cadena"(因为它们是对象并且通过引用传递,我猜 Cadena 现在有 test 的地址)
  4. 当按下表单 1 中的按钮时,调用构造函数并分配文本框字符串,关闭表单
  5. 使用消息框检查测试对象中的字符串(应该在表单 1 中分配字符串,但我们只有空引用。

我认为当我调用构造函数时我正在破坏第一个引用,这就是为什么最终对象是 null 对吗?

是否应该在主代码中创建对象,在后面的表单中只修改参数?

您的代码在做什么

  1. 创建 "test" 对象 null

您实际上并不是在创建对象。您正在将 test 设置为空引用。

  1. 将对象作为对表单的引用传递

您实际上是在传递对 test 的引用。如果您只想传递引用,请删除 ref 关键字。引用类型的变量的值已经只是对该对象的引用。 ref 对象不是对对象的引用,而是对对象引用的引用。如果您不清楚,请参阅 this link

  1. Form 构造采用 "test" 引用并将其分配给对象 "cadena"

您正在用空值填充变量 cadena

3a. (因为它们是对象并且通过引用传递我猜想 Cadena 现在有测试地址)

没有。它的值为 test,这是一个空引用。我认为您正在尝试存储对对象引用的引用(即指向 test 的指针)。实际上,您只存储了一个指针,该指针等于 test 中保存的值,在本例中为 null。

  1. 当按下表单 1 中的按钮时,调用构造函数并分配文本框字符串,关闭表单。

正确

  1. 使用消息框检查测试对象中的字符串(应该在表单 1 中分配字符串,但我们只有一个空引用。

您的示例中没有返回并使用有效引用填充 test 的代码。

为什么这永远行不通

根据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:

(强调)

你不能在 c# 中这样做...在 c++ 中我也不会这样做,因为不能保证指针会被垃圾收集。这就是托管代码中不允许使用此模式的原因。

如何做您想做的事

我给你三个选择。可能还有更多(Singleton、ByRef 等),但我认为这些是最常见的。

在程序和表单之间设置parent/child

如果您正在编写一个应用程序,其中有一系列需要访问全局状态的表单,一个常见的模式是使 Program 本身成为全局状态的容器,并将其作为 parent 参数传递给任何需要它的东西。所以这里又是你的代码,遵循这种模式:

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);
    }
}

将全局变量设为静态

或者,如果您不想将指针传递给子对象,您可以使全局变量 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);
    }
}

"best"方式

最好的方式,或者说最近流行的最现代的方式,就是

  1. 创建一个专门用于包含全局状态的 class(或 classes)
  2. 将状态变量实现为成员变量
  3. 使用像 Autofac 这样的工厂来获取状态容器的实例
  4. 告诉工厂无论调用多少次都只需要一个实例

例如

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)
    }
}

您可以自由使用 IoC toolkit you want, e.g. AutoFac or Unity. This article 比较可用于 .NET 的主要 IoC 库。

首选此方法,因为它允许单元测试人员为全局状态提供自己的存根,而无需 ,这通常是设置单元测试中最困难的部分。

请注意,在上面的示例中,我通过执行代码获取了对 ProgramGlobals 的引用。您还可以使用 Dependency Injection 将对象设置为自动获取引用。很好的话题,但是我的回答已经很长了,所以你可能需要自己研究一下。

在不使用 global state 的情况下实现您想要的效果的最简单方法是使用一些 class,例如:

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

然后在Main:

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

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();
    }
}