Подтвердить что ты не робот

Разве методы расширения С# не позволяют передавать параметры по ссылке?

Неужели невозможно создать метод расширения в С#, где экземпляр передан как ссылка?

Вот пример консольного приложения 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

Обратите внимание, что параметр значения передан ByRef.

И (почти) то же самое в С#:

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 не работает в С#, потому что я не могу использовать ключевое слово ref. Есть ли обходной путь для этого?

4b9b3361

Ответ 1

Нет. В С# вы не можете указывать любые модификаторы (например, 'out' или ref), отличные от this для первого параметра метода расширения - вы можете для других. Не знакомы с синтаксисом 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

Я думаю, что проблема, с которой вы здесь сталкиваетесь, связана с неизменяемыми типами значений. Если в будние дни был ссылочный тип, все будет хорошо. Для неизменяемых типов (structs) метод defacto должен возвращать новый экземпляр с требуемым значением. Например. См. Метод Add в структуре DateTime, он возвращает новый экземпляр DateTime, значение value = получателя DateTime значение + значение параметра.

public DateTime Add( TimeSpan value )

Ответ 2

Yikes - вы создаете изменяемую неизменяемую структуру. Это ломает то, что люди ожидают увидеть на С#, но если вам нужно, вы всегда можете напрямую вызвать метод:

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);

Ответ 3

Странно, что VB.NET позволяет это, а С# не...

Однако, хотя это может иметь смысл с технической точки зрения (поскольку метод расширения является только статическим методом), я думаю, что это не так, потому что методы расширения используются так, как если бы они были методами экземпляра, и методы экземпляра не могут изменять ссылку this.