c#将类实例传递为null而不是作为ref [重复]
Posted
技术标签:
【中文标题】c#将类实例传递为null而不是作为ref [重复]【英文标题】:c# passing class instance as null not done as ref [duplicate] 【发布时间】:2021-10-07 21:57:09 【问题描述】:似乎任何 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 关键字。为什么?我认为类实例总是作为 ref 传递,而不需要 ref 关键字。 MyClassType 告诉 dofunction mt 是一个类实例,但它的行为不像它。我猜设置 mt=null 不是类实例,但那又怎样?
同样,问题是“为什么?”。
我已经编辑了............
改写问题,这似乎与所谓的重复问题不同: 如果您在调用之前将 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# 中,参数可以通过值或引用传递给参数。即使参数是引用类型,参数也是按值传递的。 这能回答你的问题吗? What is the use of “ref” for reference-type variables in C#? @GSerg - 是的,这是一个重复的问题。但我喜欢the answer "walking with the dog" Caius Jard 给出的。 ;-) 如果您在调用之前将 mt 设置为 null,就好像您没有使用 ref 关键字 - 这就像您没有使用ref
关键字,因为您没有使用 ref 关键字。 如果你在调用之前将 mt 设置为 new MyClassType(),它就像你使用了 ref 关键字一样 - 不,它没有
Grrrrrrrrrrrr。 “不,它没有”????????
【参考方案1】:
这是 C# 的一个经常被谈论且相当令人困惑的方面。我认为产生混淆的主要形式是因为我们谈论“按引用传递”和“按值传递”,而值是副本。这些术语导致人们认为在某些情况下对象的数据被复制,而在其他情况下原始数据通过。
引用类型(类)总是“通过引用传递”,当我这样说时,我的意思是“通过发送对数据的引用作为方法参数传递给方法”的缩写,但关键是因为该方法可以做的基本上是它们是“通过提供原始参考通过”还是“通过提供参考副本通过”
默认为“复制”;你创建了一个新类,它的数据在内存中的某个地方。作为制作它的一部分,您创建了一个变量来引用它。然后你将它传递给一个方法,默认情况下会创建另一个独立变量来引用内存中的相同数据。因此,您可以更改您喜欢的有关数据的任何内容,但您只能使先前创建的变量引用完全不同的对象,前提是该较早的变量本身已被传递。因为默认情况下,如果您使该新变量引用其他变量,则会创建、附加和传递另一个变量,那么较早的变量不会受到影响。在“复制”模式或“原始”模式下,您可以修改对象的某些属性
当 C# 世界说“通过引用传递”(原始)或“通过值传递”(复制)时,他们谈论的是引用构成对象的数据的变量会发生什么;变量要么被发送原件,要么被发送副本/附加件。他们不是在谈论对象的数据——只有一个具有引用类型的数据块,有 N 个变量引用它
我倾向于将其解释为带您的狗散步;有一只狗,就像记忆中只有一个物体一样。当你调用一个方法时,就像让你的朋友也一起来遛狗,当他们说“嘿,我可以带他一会儿吗?”您可以选择是否给该人您持有的原始线索(ref
关键字),或者将全新的线索附加到同一只狗(因此它有两条线索)并将新线索给另一个人(无关键字)。狗不是克隆的;只有一只狗。铅是对狗的参考;你掌握着领先地位,而不是狗。你通过铅引导狗。线索总是附在狗身上,而不是另一条线索。没有链条
如果该人带头并将其连接到他们发现在公园里四处游荡的整条 new
狗上,那么您的引线仍连接在您的狗身上。他们的行为不会改变您的线索与哪条狗相关联。如果您将原来的线索交出,然后他们将其附加到一只新狗身上,您的狗就会丢失,当您重新获得控制权时,您会发现自己养的是贵宾犬,而不是阿尔萨斯犬。
如果没有new
ing 参与,那么您的朋友是使用他们的新线索还是您原来的线索将狗带到美容院并剃毛都没有关系;无论哪种情况,他们都修改了你的狗的某些方面,当你把它拿回来时,你会看到它被剃了毛
ref
因此纯粹规定一个方法是否可以用new
替换传入的对象,调用方法将看到更改。
尽量不要使用;如果您的方法旨在创建新对象,则应该返回它们,而不是用“嘿,我将您的对象换成新对象”让调用者感到惊讶
你的 dofunction 应该是这样的:
public MyClassType dofunction()
return new MyClassType() SomeProperty = "xxxxx";
我们不会像“在这里,拥有这个 null 的东西并将它设置为我的某个东西的实例”这样的代码 - 我们会像“给我一个新的东西,如果我愿意,我会更新我的 null 的东西”这样的代码
MyClassType mt = dofunction();
如果您遇到“我必须使用 ref
因为我想返回两件东西”的情况,您可以仍然避免使用它 - 创建另一个类来保存您想要的两件东西返回并返回 that 的一个实例。甚至有一些内置方法可以快速返回一对或更多的东西,而不必专门为它们创建类:
public (MyClassType X, string Y) dofunction()
return (new MyClassType() SomeProperty = "xxxxx", "hello this is Y we love c#");
编译器会为你有效地编写这个类;它在幕后使用 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);
我们再来一次,这次是一个非空参数:
这与ref
无关;您不需要ref
对传递的对象进行编辑(将idnmbr
设置为字符串)在方法结束后仍然存在。你需要ref
来批量替换整个对象(使用new
关键字来实例化一个新实例)才能存活
=> 你总是可以刮胡子,因为传递总是通过引用(对构成实例的单堆数据)。如果通过导致创建了整个狗的副本,那么您永远无法为原始狗剃毛。传递总是通过引用,并且引用是重复的,除非指定了ref
..
【讨论】:
这不是真的:它们总是“通过引用传递”, 不正确:它们总是“通过引用传递”,--如果将 m1 设置为 null,则不会通过引用传递;如果将其设置为“new MyClassType()”,则通过引用传递。我的主要问题是,为什么在调用该方法之前我将其设置为什么重要?它的工作方式使生活更加复杂。关于有方法return和MyClassType,我经常传很多参数;当多个更改时,将某个新类中的所有内容传回将其他类合并在一起是一件苦差事。 为什么在调用该方法之前我设置的值很重要 - 没关系。无论你为得出这个结论所做的任何实验都是不成立的,前提是错误的。 dotnetfiddle.net/xW9C2U 现在我们正处于关键点。我已经编写了演示代码,但我不确定如何在这里复制它。我对你的 dotnetfiddle 不熟悉 ---- 让我们看看那是什么。 您可以将其写入小提琴(或在其顶部),然后点击保存或共享,您将获得一个链接以粘贴回此处以上是关于c#将类实例传递为null而不是作为ref [重复]的主要内容,如果未能解决你的问题,请参考以下文章
c#传递引用对象作为参数的时候就没有必要用ref关键字,对吗