【问题标题】:Impossible to use ref and out for first ("this") parameter in Extension methods?无法在扩展方法中对第一个 (\"this\") 参数使用 ref 和 out?
【发布时间】:2023-01-11 11:54:35
【问题描述】:

为什么禁止使用ref修饰符调用Extension Method

这是可能的:

public static void Change(ref TestClass testClass, TestClass testClass2)
{
    testClass = testClass2;
}

而这个不是:

public static void ChangeWithExtensionMethod(this ref TestClass testClass, TestClass testClass2)
{
    testClass = testClass2;
}

但为什么?

【问题讨论】:

  • 您确定需要显式 ref 吗?我希望它是由this“自动生成”的——非引用扩展方法没有任何意义。
  • 但如果我没记错的话,它们是非参考的。
  • @MarcelJackwerth ref 参数与引用类型参数不同。 ref 参数传递调用者的引用(或指针)本身。使用 ref 您可以更新引用以指向其他某个对象。没有它(对于引用类型),您可以更新对象本身,但不能更新对它的引用。

标签: c# .net extension-methods


【解决方案1】:

您必须明确指定 refout。你会怎么做扩展方法?而且,你真的会到?

TestClass x = new TestClass();
(ref x).ChangeWithExtensionMethod(otherTestClass);
// And now x has changed?

或者您是否不想只为扩展方法中的第一个参数指定 ref 部分?

老实说,这对我来说听起来很奇怪,而且是不可读(或至少难以预测)代码的秘诀。

【讨论】:

  • “您必须明确指定……”是一个人为的编译器要求,可以在此处删除。但这会使它更加模糊。
  • @JonSkeet:我不喜欢这个模式foo = foo.WithSomeChange()。它不能成为线程安全的,而且如果 foo 不是简单变量,它会很尴尬(有时很危险)。将 foo 作为 ref 参数传递给 MakeSomeChange 方法使其成为线程安全的,明确表示该方法将更改 foo,并且会导致编译器禁止危险使用。我书中的所有重大胜利。如果能够使用突出显示正在执行的操作的语法而不是静态实用程序类的名称来保持这些优势,那就太好了。
  • @Jon Skeet - 对于我们这些从 VB 转向 C# 的人来说,这是一个非常有效的问题。仅仅因为您不同意并不意味着它不值得讨论。像这样驳斥别人的意见对我来说似乎有点精英主义。
  • @supercat - 我也不喜欢 foo = foo.WithSomeChanges 模式。在我看来,使用 ByRef 扩展方法会产生更优雅的代码,允许方法调用,而不仅仅是函数调用。我还使用它们能够将对 Nothing (null) 的检查推送到扩展方法中,因此不必在调用代码中重写它。我很高兴你提出这个问题,因为我刚刚遇到了同样的问题。我正在尝试转向 C#,但像这样的问题非常令人沮丧。
  • @Jon Skeet:我指的是“我认为不值得深入其中”。我确实觉得值得。虽然我没有提到它,但我发现“你是否出于兴趣浏览我的旧帖子寻找不同意的事情?”,无论措辞如何礼貌,都是不必要的。我不认识你,也不知道你可能有 supercat 的任何历史,但这并没有改变这个问题对来自 VB 的人具有真正价值的事实。 VB 是一种非常实用的语言,看到 C# 纯粹主义者争论的概念只会让代码更加教条而不是实用,这可能会令人震惊。
【解决方案2】:
【解决方案3】:

我同意 Jon Skeet 等人的回答。关于允许“ref this”扩展方法如何使代码更加晦涩。但是,如果您查看 .Net Framework 中的某些命名空间,通常会发现在结构上调用的方法会更改它。

以 System.Drawing 结构(Point、Rectangle 等)为例。其中每一个都有改变结构本身的方法(例如 Offset、Inflate 等)。我并不是说这是个好主意,事实上我个人觉得 Offset、Inflate 等改变结构本身而不是返回新结构非常烦人,而且我知道你们中的一些人反对可变结构的想法一般的。

我怀疑在任何情况下调用引用类型的方法都会更改引用(除非它与 String 类一起使用,我可以想象可能会有一些编译器魔术来切换引用以执行实习等)。因此,防止“this ref”与引用类型一起使用是有意义的,因为更改引用将是调用方法的完全非标准的副作用。

但是对于结构,允许“this ref”不会比 Rectangle.Inflate 等显着降低代码可读性,并且它将提供使用扩展函数“模拟”这种行为的唯一方法。

作为旁注,这是一个示例,其中“this ref”可能很有用,并且恕我直言仍然可读:

void SwapWith<T>(this ref T x, ref T y) {
   T tmp = x; x = y; y = tmp;
}

【讨论】:

  • 附带说明一下:我们现在可以很容易地交换:(x, y) =&gt; (y, x);
【解决方案4】:

我知道这是一个老问题。但事情发生了变化。万一有人在找这个。

从 C# 7.2 开始,您可以将 ref 修饰符添加到第一个 扩展方法的参数。添加 ref 修饰符意味着 第一个参数通过引用传递。这使您可以编写 更改被扩展结构的状态的扩展方法。

这仅适用于值类型 (struct),而不适用于引用类型 (classinterfacerecord)。

来源:Microsoft Docs, "Extension Methods (C# Programming Guide) — Extending Predefined Types"

public struct MyProperties
{
    public string MyValue { get; set; }
}

public static class MyExtensions
{
    public static void ChangeMyValue(this ref MyProperties myProperties)
    {
        myProperties.MyValue = "hello from MyExtensions";
    }
}

public class MyClass
{
    public MyClass()
    {
        MyProperties myProperties = new MyProperties();
        myProperties.MyValue = "hello world";
        myProperties.ChangeMyValue();
    }
}

【讨论】:

  • 有什么方法可以用字符串来做到这一点,因为它介于类和值类型对象之间?编译器似乎将其视为一个类并阻止它,但与大多数类不同,无法修改其内容,这更像是一个值类型。这将打开可能性,例如当且仅当原始字符串为 null 或空时设置默认值。如果不是这种情况,则根本不会完成任何分配。
【解决方案5】:

我同意它对结构有用

所以我想建议为结构制作扩展方法。 this 关键字应始终通过引用传递结构

无论如何,类总是通过引用传递。每当我们创建扩展方法时,我们都希望它表现得像一个真正的方法。所以类和结构的真实方法可以修改它的值。扩展方法应该也可以

【讨论】:

    【解决方案6】:

    这意味着调用myObject.ChangeWithExtentionMethod(otherObject) 实际上有可能改变myObject 的值。 IMO,当您可以通过使用带有 ref 的常规非扩展方法来实现所需的效果时,这不会产生非常可读的代码。

    编辑:我的观点是,方法调用应该要求您在通过引用传递某些内容时随时使用 ref 关键字。将 ref 与扩展方法的“this”参数一起使用会违反该行为。

    【讨论】:

      猜你喜欢
      • 2011-02-06
      • 1970-01-01
      • 2019-12-23
      • 2015-11-16
      • 2021-07-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多