2009-08-11 30 views
20

¿Es realmente imposible crear un método de extensión en C# donde se pasa la instancia como referencia?¿Los métodos de extensión C# no permiten pasar parámetros por referencia?

Aquí hay una aplicación de consola VB.NET muestra:

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 

Nota el parámetro de valor se pasa ByRef.

y (casi) la misma en 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; 
    } 
    } 
} 

El método de extensión Add no funciona en C# porque no puedo utilizar la palabra clave ref. ¿Hay alguna solución para esto?

+0

Sólo por causa de finalización: la forma correcta de "Añadir" un valor de una enumeración bandera es 'Valor = valor o Arg1' a menos que desee que la adición de' Monday' dos veces se comporta como la adición de 'Tuesday'. La forma correcta de eliminar una bandera es 'Value = (Value Or Arg1) Xor Arg1'. – LWChris

Respuesta

12

No. En C#, se puede no especificar cualquier modificador (como 'fuera' o ref) que no sea this para el primer parámetro de un método de extensión - que pueda por los demás. No está familiarizado con la sintaxis de VB, pero parece estar usando un enfoque declarativo para marcar un método de extensión.

Cuando se llama a ella, No especificar el primer this parámetro. De ahí que marca el parámetro como hacia fuera o duerma ref tener sentido como no se puede especificar el modificador cuando se llame como me lo haces por métodos normales

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 

Creo que el problema que está teniendo aquí se relaciona con los tipos de valor son inmutables. Si Weekdays fue un tipo de referencia, funcionaría bien. Para tipos inmutables (estructuras), la forma defacto es devolver una nueva instancia con el valor requerido. P.ej. Vea el método Agregar en la estructura DateTime, devuelve una nueva instancia de DateTime cuyo valor = valor de param + valor de la instancia de DateTime del receptor.

public DateTime Add(TimeSpan value) 
+0

En vb, se puede especificar que un método de extensión tome el 'implicito' this '(' Me') como un parámetro 'ByRef'; desafortunadamente, el compilador luego permite que se le pasen estructuras de solo lectura. Una de las principales quejas acerca de los tipos de valores mutables es que, dado que no hay nada que indique qué métodos mutan 'this', permiten que dichos métodos se llamen inútiles en instancias de solo lectura. Me parece extraño que los implementadores de vb.net decidan permitir un método de extensión que tome un parámetro 'ByRef', prácticamente gritando" ¡MUTARÉ EL ARGUMENTO! "En un contexto de solo lectura. – supercat

11

Yikes: estás haciendo una estructura inmutable mutable. Se rompe lo que la gente espera ver en C#, pero si es necesario, entonces siempre se puede llamar directamente al método:

Ext.Add(ref value, arg1); 

Cualquier método de extensión es directamente exigible.

Además, una aclaración:

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

mutable inmutable struct? –

+1

sí, a Eric Lippert le encantaría eso;) –

+2

Los valores enum son inmutables, pero pasar uno por referencia como este parámetro a un método de extensión lo haría comportarse como un valor mutable. –

4

extraño que permite VB.NET y C# esto no lo hace ...

Sin embargo, a pesar de que podría tener sentido desde un punto de vista técnico (ya un método de extensión es solo un método estático), creo que no se siente bien, porque los métodos de extensión se usan como si fueran métodos de instancia, y los métodos de instancia no pueden modificar la referencia this.

+8

De hecho, los métodos de instancia * pueden * modificar "esto" en tipos de valores. Raro pero cierto –

+1

wow ... ¡Nunca me di cuenta de eso! –

+3

No hace falta decir que esto CHOZA mucho para C#. –

Cuestiones relacionadas