c# 将 class 实例作为 null 传递,而不是作为 ref

c# passing class instance as null not done as ref

似乎任何 C# 问题都必须重复,但我找不到它。

C# 编码:

MyClassType mt = null;
dofunction(mt);
// mt was modified in dofunction to some non null value, but comes back still null

dofunction 类似于

public void dofunction(MyClassType mt){
mt="xxxxx";
}

要在调用方中看到更新,我必须使用 ref 关键字。为什么?我认为 class 实例总是作为 ref 传递,而不需要 ref 关键字。 MyClassType 告诉 dofunction mt 是一个 class 实例,但它并不像它那样。设置 mt=null 不是 class 实例,我想,那又怎样?

同样,问题是“为什么?”。

我编辑了.........

改写问题,这似乎与所谓的重复问题不同: 如果您在调用前将 mt 设置为 null,它就像您没有使用 ref 关键字一样。如果您在调用前将 mt 设置为 new MyClassType(),它的作用就好像您使用了 ref 关键字一样。为什么 C# 会以这种复杂的方式运行?

这是演示我的前提的示例代码。我在 Visual Studio 2019,ASP.Net Core 5.

中编写 C#

调用例程如下:

public IActionResult RefTest() {
            MrShow m1 = null; // in this case doFunction acts as if m1 does NOT have the ref keyword
            MyProject.Utils.TestIt.doFunction(m1);
            string m1contents = "result when m1 set to null: ";
            if (m1 == null) m1contents += "null / ";
            else m1contents += m1.idnmbr + " / ";
            m1contents += "result when m1 set to class instance: ";
            m1 = new MrShow(); // in this case doFunction acts as if m1 does have the ref keyword
            MyProject.Utils.TestIt.doFunction(m1);
            if (m1 == null) m1contents += "null / ";
            else m1contents += m1.idnmbr + " / ";
            ViewBag.m1contents = m1contents;
            return View();
        }

这就是所谓的:

static internal void doFunction(MrShow m1) {
            if (m1 != null) {
                m1.idnmbr = "doFunction changed this";
            }
            else {
                m1 = new MrShow();
                m1.idnmbr = "m1 did not change this";
            }
        }

在我的网络应用程序中,我得到结果:

RefTest Results
result when m1 set to null: null / result when m1 set to class instance: doFunction changed this / 

这是 C# 中一个经常被谈论且相当令人困惑的方面。我认为混淆的主要形式是因为我们谈论“按引用传递”和“按值传递”而值是副本..这些术语使人们认为在某些情况下复制对象的数据而在其他情况下传递原始数据。

引用类型(classes)总是“通过引用传递”,当我这样说时,我的意思是它是“通过发送对数据的引用作为方法传递给方法”的缩写argument”,但该方法能做什么的关键在于它们是“通过提供原始引用传递”还是“通过提供引用副本传递”

默认为“复制”;你创建了一个新的 class 并且它的数据在内存中的某个地方。作为制作它的一部分,您创建了一个变量来引用它。然后将它传递给一个方法,默认情况下会创建另一个引用内存中相同数据的自变量。因此,您可以更改任何您喜欢的数据,但如果先前创建的变量本身已被传递,则只能使先前创建的变量引用整个不同的对象。因为默认情况下会创建、附加和传递另一个变量,如果您使该新变量引用其他内容,则较早的变量不会受到影响。在“复制”模式或“原始”模式下,您可以修改对象的一些属性

当 C# 世界说“按引用传递”(原始)或“按值传递”(复制)时,他们谈论的是引用构成对象的数据的变量发生了什么;发送原始变量或发送 copy/additional 变量。他们不是在谈论对象的数据——只有一个具有引用类型的数据块,有 N 个变量引用它

我倾向于把它解释为带你的狗散步;有一只狗,就像记忆中只有一个物体。当你调用一个方法时,就像让你的朋友也一起去遛狗,当他们说“嘿,我可以带他一会儿吗?”您可以选择是将您持有的原始线索交给该人(ref 关键字),还是将一条全新的线索附加到同一只狗(因此它有两条线索)并将新线索交给另一个人(无关键字)。狗不是克隆的;只有一只狗。铅是对狗的参考;你带头,而不是狗。你通过铅引导狗。导线始终系在狗身上,而不是另一根导线。没有链条

如果那个人带上他们的牵绳并将其系在他们发现在公园里四处游荡的整条 new 狗身上,那么您的牵绳仍然系在您的狗身上。他们的行为不会改变您的领导与哪只狗有联系。如果您交出原来的牵引绳,他们将它系在一条新狗身上,那么您的狗就丢失了,当控制权回到您手中时,您会发现您养的是贵宾犬,而不是阿尔萨斯犬。

如果没有 newing 参与,那么您的朋友是使用他们的新牵引绳还是您原来的牵引绳将狗带到美容院并剃毛都没有关系;在任何一种情况下,他们都修改了您的狗的某些方面,当您取回它时,您会看到它被剃光了

ref 因此纯粹决定了方法是否可以用 new 替换传入的对象并且调用方法将看到更改。

尽量不用;如果您的方法旨在创建新对象,它应该 return 它们而不是让调用者感到惊讶“嘿,我用您的对象换了一个新对象”

你的功能应该是这样的:

public MyClassType dofunction(){
  return new MyClassType() { SomeProperty = "xxxxx"};
}

我们不会像“这里,有这个空的东西并将它设置为我的东西的实例”这样的代码 - 我们像“给我一个新东西,如果我想更新我的空东西”这样的代码“

MyClassType mt = dofunction();

如果您遇到“我必须使用 ref 因为我想 return 两件事”的情况,您仍然可以避免使用它 - 制作另一个 class 来保存你想要的两个东西 return 和 return 一个 that 的实例。甚至有内置的方法可以快速 return 一对或更多事物,而不必为它们制作 classes:

public (MyClassType X, string Y) dofunction(){
  return (new MyClassType() { SomeProperty = "xxxxx"}, "hello this is Y we love c#");
}

编译器会有效地为您编写此 class;它在幕后使用了一个 ValueTuple

var result = dofunction();
MyClassType mt = result.X;
string secondThing = result.Y;

编辑:好的,所以您发布了导致您得出结论的实验:“根据传递的参数是否为空,行为会有所不同”

首先,我想指出方法 实际上有逻辑使其根据参数是否为空而表现不同

这意味着您正在编写行为不同的代码,并观察结果并继续“哦!C# 有时表现得像 ref 而不是其他人!”

不,不是; C# 是一致的。 前后矛盾

而你正在给狗剃毛。我会用图片来说明。我将函数的参数重命名为 m1df 以帮助区分:

    //your method
    static internal void doFunction(MrShow m1df) {
        if (m1df != null) {
            m1df.idnmbr = "doFunction changed this";
        }
        else {
            m1df = new MrShow();
            m1df.idnmbr = "m1 did not change this";
        }
    }

    //your code
    MrShow m1 = null; // in this case doFunction acts as if m1 does NOT have the ref keyword
    MyProject.Utils.TestIt.doFunction(m1);

我们再来一次,这次使用非空参数:

None这跟ref有什么关系;您不需要 ref 来对传递的对象进行编辑(将 idnmbr 设置为字符串)在方法结束后继续存在。您需要 ref 来批量替换整个对象(使用 new 关键字实例化新实例)存活

=> 你总是可以剃狗,因为传递总是通过引用(到构成实例的单堆数据)。如果传球导致整条狗的复制品被创建,你就永远不能给原来的狗剃毛。传递总是通过引用并且引用被复制,除非指定 ref..