【问题标题】:Doesn't C# Extension Methods allow passing parameters by reference?C# 扩展方法不允许通过引用传递参数吗?
【发布时间】:2009-08-11 17:43:22
【问题描述】:

在C#中创建一个实例作为引用传递的扩展方法真的不可能吗?

这是一个示例 VB.NET 控制台应用程序:

Imports System.Runtime.CompilerServices

Module Module1
  Sub Main()
    Dim workDays As Weekdays

    workDays.Add(Weekdays.Monday)
    workDays.Add(Weekdays.Tuesday)

    Console.WriteLine("Tuesday is a workday: {0}", _ 
      CBool(workDays And Weekdays.Tuesday))
    Console.ReadKey()
  End Sub
End Module

<Flags()> _
Public Enum Weekdays
  Monday = 1
  Tuesday = 2
  Wednesday = 4
  Thursday = 8
  Friday = 16
  Saturday = 32
  Sunday = 64
End Enum

Module Ext
  <Extension()> _
  Public Sub Add(ByRef Value As Weekdays, ByVal Arg1 As Weekdays) 
    Value = Value + Arg1
  End Sub
End Module

注意 Value 参数是 ByRef 传递的。

在 C# 中(几乎)相同:

using System;

namespace CS.Temp
{
  class Program
  {
    public static void Main()
    {
      Weekdays workDays = 0;

      workDays.Add(Weekdays.Monday); // This won't work
      workDays.Add(Weekdays.Tuesday);

      // You have to use this syntax instead...
      // workDays = workDays | Weekdays.Monday;
      // workDays = workDays | Weekdays.Tuesday;

      Console.WriteLine("Tuesday is a workday: {0}", _ 
        System.Convert.ToBoolean(workDays & Weekdays.Tuesday));
      Console.ReadKey();
    }
  }

  [Flags()]
  public enum Weekdays : int
  {
    Monday = 1,
    Tuesday = 2,
    Wednesday = 4,
    Thursday = 8,
    Friday = 16,
    Saturday = 32,
    Sunday = 64
  }

  public static class Ext
  {
    // Value cannot be passed by reference? 
    public static void Add(this Weekdays Value, Weekdays Arg1) 
    {
      Value = Value | Arg1;
    }
  }
}

Add 扩展方法在 C# 中不起作用,因为我不能使用 ref 关键字。有什么解决方法吗?

【问题讨论】:

  • 只是为了完成:“添加”标志枚举值的正确方法是Value = Value Or Arg1,除非您希望将Monday 添加两次的行为类似于添加Tuesday。删除标志的正确方法是Value = (Value Or Arg1) Xor Arg1

标签: c# vb.net extension-methods byref


【解决方案1】:

没有。在 C# 中,您不能为扩展方法的第一个参数指定除 this 之外的任何修饰符(如 'out' 或 ref) - 您可以为其他参数指定任何修饰符。 不熟悉 VB 语法,但它似乎使用声明性方法来标记扩展方法。

调用它时,不要指定第一个 this 参数。因此,将参数标记为 out 或 ref 没有意义,因为您无法像在普通方法中那样调用它时指定修饰符

void MyInstanceMethod(ref SomeClass c, int data) { ... } // definition

obj.MyInstanceMethod(ref someClassObj, 10);              // call

void MyExtensionMethod(this SomeClass c, int data) {.... } // defn

c.MyExtensionMethod(10);                                 // call

我认为您在这里遇到的问题与不可变的值类型有关。如果 Weekdays 是一个引用类型,它会正常工作。对于不可变类型(结构),事实上的方法是返回具有所需值的新实例。例如。见 struct DateTime 上的 Add 方法,它返回一个新的 DateTime 实例,其值 = 接收者 DateTime 实例的值 + 参数值。

public DateTime Add( TimeSpan value )

【讨论】:

  • 在 vb 中,可以指定扩展方法将隐含的 this (Me) 作为 ByRef 参数;不幸的是,编译器随后继续允许将只读结构传递给它。关于可变值类型的主要抱怨之一是,由于没有指示哪些方法会发生变异this,因此它们允许在只读实例上无用地调用此类方法。我觉得奇怪的是,vb.net 的实现者会决定允许一个采用 ByRef 参数的扩展方法——实际上是在尖叫“我将改变参数!”——在只读上下文中。
【解决方案2】:

Yikes - 你正在制作一个可变的不可变结构。它打破了人们期望在 C# 中看到的内容,但如果必须,您始终可以直接调用该方法:

Ext.Add(ref value, arg1);

任何扩展方法都可以直接调用。

另外,澄清一下:

SomeReferenceType value = ...;
SomeReferenceType copy = value;
value.ExtensionMethodByRef(...);
// this failing is semantically ridiculous for reference types, which
// is why it makes no sense to pass a `this` parameter by ref.
object.ReferenceEquals(value, copy);

【讨论】:

  • 是的,Eric Lippert 会喜欢的 ;)
  • 枚举是不可变的值,但是通过引用将其作为 this 参数传递给扩展方法会使其表现得像一个可变值。
【解决方案3】:

奇怪的是 VB.NET 允许这样做而 C# 却不允许......

然而,尽管从技术角度来看它可能是有意义的(因为扩展方法只是一个静态方法),但我认为它感觉不对,因为扩展方法被用作实例方法,并且实例方法不能修改 this 引用。

【讨论】:

  • 事实上实例方法可以修改值类型中的“this”。奇怪但真实。
  • 毋庸置疑,这对 C# 来说很糟糕。
  • @TheoZographos 我相信这是为了“避免糟糕的代码”,就像 C# 中的许多其他东西一样,比 C++ 更具限制性,因此,代码是更易于阅读和维护。这就是我认为他们在设置限制时可能会想到的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-18
  • 1970-01-01
  • 2013-08-22
  • 1970-01-01
  • 2012-01-04
相关资源
最近更新 更多