11

我继承了一些广泛且不必要地使用ref关键字的代码。最初的开发人员显然担心如果不使用ref ,对象会像原始类型一样被克隆,并且在编写 50k+ 行代码之前没有费心研究这个问题。

这与其他不良编码实践相结合,造成了一些表面上非常危险的情况。例如:


Customer person = NextInLine(); 
//person is Alice
person.DataBackend.ChangeAddress(ref person, newAddress);
//person could now be Bob, Eve, or null

你能想象走进一家商店改变你的地址,然后作为一个完全不同的人走出去吗?


可怕,但实际上在此应用程序中使用ref似乎是无害的多余。我很难证明清理它需要花费大量时间。为了帮助推销这个想法,我提出以下问题:

对 ref 的不必要使用还有什么破坏性?

我特别关心维护。带有示例的合理答案是首选。

也欢迎您争辩没有必要进行清理。

4

7 回答 7

8

我想说最大的危险是如果由于null某种原因将参数设置为函数内部:

public void MakeNull(ref Customer person)
{
    // random code
    person = null;
    return;
}

现在,你不只是一个不同的人,你已经完全从存在中消失了!

只要开发此应用程序的人了解:

默认情况下,对象引用是按值传递的。

和:

使用ref关键字,对象引用通过引用传递。

如果代码现在按预期工作并且您的开发人员了解其中的区别,那么将它们全部删除可能不值得。

于 2009-04-14T18:56:18.277 回答
4

我将添加我见过的最糟糕的 ref 关键字用法,该方法看起来像这样:

public bool DoAction(ref Exception exception) {...}

是的,您必须声明并传递一个 Exception 引用才能调用该方法,然后检查该方法的返回值以查看是否已捕获异常并通过 ref 异常返回。

于 2009-04-14T18:59:14.447 回答
2

你能弄清楚为什么代码的发起者认为他们需要将参数作为 ref 吗?是因为他们确实更新了它然后删除了功能,还是仅仅因为他们当时不了解 c#?

如果你认为清理工作是值得的,那就继续吧——特别是如果你现在有时间的话。当真正的问题出现时,您可能无法正确修复它,因为它很可能是紧急的错误修复,您将没有时间做正确的工作。

于 2009-04-14T18:59:55.710 回答
1

在 C# 中修改方法中参数的值是很常见的,因为它们通常是按值而不是按 ref。这适用于引用类型和值类型;例如,将引用设置为 null 会更改原始引用。当其他开发人员“照常”工作时,这可能会导致非常奇怪和痛苦的错误。使用 ref 参数创建递归方法是不行的。

除此之外,您对可以通过 ref 传递的内容有限制。例如,您不能传递常量值、只读字段、属性等,因此在使用 ref 参数调用方法时需要大量辅助变量。

最后但并非最不重要的一点是性能可能不太好,因为它需要更多的间接性(引用只是一个需要在每次访问时解析的引用)并且还可以使对象保持更长时间,因为引用不会消失范围尽可能快。

于 2009-04-14T19:00:49.047 回答
1

对我来说,这听起来像是一个 C++ 开发人员做出无根据的假设。

我会警惕对有效的东西进行大规模更改。(我假设它有效,因为您没有评论它被破坏,只是它很危险)。

您要做的最后一件事是打破一些微妙的事情,并且必须花费一周的时间来追踪问题。

我建议你边走边清理——一次一种方法。

  1. 在您确定不需要的地方找到使用 ref 的方法。
  2. 更改方法签名并修复调用。
  3. 测试。
  4. 重复。

虽然您遇到的具体问题可能比大多数情况更严重,但您的情况很常见 - 拥有不符合我们当前对“正确方式”做事的理解的大型代码库。

批发“升级”经常遇到困难。随手重构——在你处理它们时使事情符合规范——要安全得多。

这在建筑行业有先例。例如,旧建筑(比如 19 世纪)中的电线不需要接触,除非出现问题。但是,当出现问题,必须按照现代标准完成新工作。

于 2009-04-14T19:42:39.397 回答
0

我会尝试修复它。只需使用正则表达式执行解决方案范围的字符串替换,然后检查单元测试。我知道这可能会破坏代码。但是你多久使用一次 ref?几乎从来没有,对吧?鉴于开发人员不知道它是如何工作的,我认为它在某处(应该的方式)使用的机会更小。如果代码中断 - 好吧,回滚......

于 2009-04-14T19:30:43.763 回答
0

对 ref 的不必要使用还有什么破坏性?

其他答案涉及语义问题,这绝对是最重要的事情。好的代码是自我记录的,当我给出一个 ref 参数时,我认为它改变。如果不是,则 API 无法自我记录。

但是为了好玩,我们看看另一个方面——性能怎么样?

void ChangeAddress(ref Customer person, Address address)
{
    person.Address = address;
}

这里person是一个引用的引用,所以每当你访问它时都会引入一些间接性。让我们看一些可能转化为的程序集:

mov eax, [person]           ; load the reference to person.
mov [eax+Address], address  ; assign address to person.Address.

现在是非参考版本:

void ChangeAddress(Customer person, Address address)
{
    person.Address = address;
}

这里没有间接性,所以我们可以去掉一个读取:

mov [person+Address], address  ; assign address to person.Address.

在实践中,人们希望 .NET 缓存[person]ref版本中,将间接成本分摊到多次访问中。除了像这里这样的琐碎方法之外,它实际上可能不会减少 50% 的指令数。

于 2013-09-04T23:58:05.963 回答