24

Is it really impossible to create an extension method in C# where the instance is passed as a reference?

Here’s a sample VB.NET console app:

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

Note the Value parameter is passed ByRef.

And (almost) the same in 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;
    }
  }
}

The Add extension method doesn’t work in C# because I can’t use the ref keyword. Is there any workaround for this?

Jakob Gade
  • 12,319
  • 15
  • 70
  • 118
  • Just for completion's sake: the correct way to "Add" a value of a flag enum is `Value = Value Or Arg1` unless you want that adding `Monday` two times behaves like adding `Tuesday`. The correct way to remove a flag is `Value = (Value Or Arg1) Xor Arg1`. – LWChris Nov 09 '15 at 21:33

3 Answers3

13

No. In C#, you cannot specify any modifiers (like 'out' or ref) other than this for the first parameter of an extension method - you can for the others. Not familiar with the VB Syntax but it seems to be using a declarative approach to mark an extension method.

When you call it, you do not specify the first this parameter. Hence marking the parameter as out or ref doesnt make sense as You can't specify the modifier when you call it like you'd do for normal methods

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

I think the trouble you're having here is related to value types being immutable. If Weekdays was a reference type, it would work out alright. For immutable types (structs), the defacto way is to return a new instance with the required value. E.g. See the Add method on the struct DateTime, it returns a new DateTime instance whose value = receiver DateTime instance's value + param value.

public DateTime Add( TimeSpan value )
Gishu
  • 134,492
  • 47
  • 225
  • 308
  • In vb, one may specify that an extension method takes the implied `this` (`Me`) as a `ByRef` parameter; unfortunately, the compiler then proceeds to allow read-only structures to be passed to it. One of the major complaints about mutable value types is that since there's nothing to indicate which methods mutate `this`, they allow such methods to be called uselessly on read-only instances. I find it odd that the vb.net implementers would decide to allow an extension method that takes a `ByRef` parameter--practically screaming "I WILL MUTATE THE ARGUMENT!"--in a read-only context. – supercat Sep 27 '12 at 16:11
  • Since C# 7.2, you're actually allowed to use "ref this" for value types. See https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/readonly-ref.md#refin-extension-methods – Oliver Aug 17 '23 at 13:58
11

Yikes - you're making a mutable immutable struct. It breaks what people expect to see in C#, but if you must, then you can always directly call the method:

Ext.Add(ref value, arg1);

Any extension method is directly callable.

Also, a clarification:

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);
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
5

Strange that VB.NET allows this and C# doesn't...

However, although it might make sense from a technical point of view (since an extension method is just a static method), I think it doesn't feel right, because extension methods are used as if they were instance methods, and instance methods can't modify the this reference.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 8
    In fact instance methods *can* modify "this" in value types. Weird but true. – Jon Skeet Aug 11 '09 at 09:08
  • 4
    It's needless to say that this SUCKS big time for C#. – Theodore Zographos Jun 02 '10 at 13:14
  • 1
    @TheoZographos I believe this is the kind of thing that's here to "avoid bad code", as many other things in C# that are more restrictive than, say, C++, and because of them, code is easier to read and maintain. That's what I think they might have thought when they set the limitation in place. – Camilo Martin Feb 15 '13 at 06:48