¿Por qué las propiedades no se pueden convertir implícitamente en delegados?

9

Todos sabemos que las propiedades en C # se compilan con métodos antiguos simples y reales. Pero a diferencia del método (-group) s, no se pueden dar como argumentos a otros métodos que esperan un delegado como a Func<T>o Action<T>(getter y setter). ¿Hay algo especial que lo prohíba, o es solo una característica que no vino "gratis" cuando los grupos de métodos se convirtieron implícitamente en delegados?

Código de ejemplo:

public class MyClass
{
    public static int X { get; set; }
}

private static void Foo<T>(Func<T> f)
{
    Console.WriteLine(f());
}

private static void Bar<T>(Action<T> f)
{
    f(default(T));
}

private static void Test()
{
    // neither of these lines compile, even with an explicit type-cast.
    Foo(MyClass.X);
    Bar(MyClass.X);
}

Si tuviera que adivinar, diría que no valía la pena resolver el problema sintáctico de diferenciar entre invocaciones y referencias a la propiedad en sí, cuando simplemente podemos hacer la falta adicional de escritura () => MyClass.Xo x => MyClass.X = x.

sara
fuente
¿A cuál llamarían? Get or set
Ewan
Mi pensamiento sería que depende del tipo de delegado. setes un Actiony getes un Funcpor ejemplo
sara
1
FYI, sintaxis usando delegados en línea para pasar getter / setter . delegate {return SomeProperty}pasa getter; delegate(YourType value) {SomeProperty = value}pasa setter. Si solo necesita pasar uno o dos lugares, esta es una forma abreviada de los métodos ajustados que de otro modo crearía.
ToolmakerSteve
Y FWIW, si desea pasar tanto getter como setter, considere en su lugar definir un Interfacecon esa propiedad y cambiar la firma del método llamado para tomar esa interfaz como parámetro.
ToolmakerSteve

Respuestas:

7

El problema es que "MyClass.X" tiene un significado bien definido, que es llamar al captador en la propiedad "X". Lo importante a tener en cuenta es que, aunque se llama a un método, no se requieren paréntesis.

Por otro lado, cuando se llama a un método regular, como cuando se llama "Foo" en su código de ejemplo, se requieren corchetes para indicar que el método debe ejecutarse. Si desea pasar una referencia a un método, entonces excluiría los corchetes (vea el ejemplo a continuación).

Esto significa que no hay ambigüedad cuando se hace referencia a un método, ya sea que lo esté ejecutando inmediatamente (pasando cualquier argumento que requiera e incluyendo paréntesis) o que esté pasando el método como argumento (sin paréntesis).

Con un getter o setter de propiedad, sin paréntesis ya significa "ejecutar getter / setter inmediatamente". Si el getter / setter de propiedad también se puede pasar como un grupo de métodos, a veces "x.Name" significaría "ejecutar el getter de Name ahora" y a veces significaría "pasar el getter de Name como un grupo de métodos". Si bien es posible cambiar el compilador para desambiguar en función de si "x.Name" parece pasar a una función que espera una cadena (en cuyo caso se ejecutará el getter) o si parece que se pasa a una función que espera un Func <string> (en cuyo caso el captador se pasaría como un grupo de métodos), el equipo del lenguaje C # intenta evitar este tipo de escenarios potencialmente confusos.

using System;

namespace Example
{
    public static class Program
    {
        static void Main()
        {
            ValueWriter(1); // Execute ValueWriter (because brackets)
            Bar(ValueWriter); // Pass ValueWriter as an action (no brackets)
        }

        private static void Bar(Action<int> f)
        {
            f(2);
        }

        private static void ValueWriter(int value)
        {
            Console.WriteLine("Value is " + value);
        }
    }
}
Dan Roberts
fuente
5

No es posible debido a los fundamentos de cómo funciona la gramática y el compilador.

Las expresiones se evalúan "de abajo hacia arriba", lo que significa que la expresión MyClass.Xevalúa su valor int antes de que este resultado se alimente como argumento de la función. Su propuesta parece sugerir que el contexto, el tipo de parámetro, podría dirigir si la expresión debe interpretarse como una invocación getter o como una referencia al método getter en sí. Esto no es posible hacerlo de una manera no ambigua. ¿Qué pasa si el tipo de propiedad en sí es un Funco Action? ¿Y cómo se var x = MyClass.Xinterpretará? ¿Qué pasa si el método tiene sobrecargas con uno into Funco Actionparámetros?

Para resolver estas ambigüedades necesita una distinción a nivel de sintaxis, por ejemplo, un operador con soporte especial en la gramática como el typeofoperador, por ejemplo:

Foo(getterof(MyClass.X));

Pero esto será muy problemático para una función con un beneficio tan limitado.

JacquesB
fuente
Sí, la sutileza con la que uno podría trabajar, si es necesario, mientras que la indecidibilidad es un obstáculo para el espectáculo.
Deduplicador
2

Foo.Do()es decir, la invocación y Foo.Doser el delegado está bien.

Foo.Xser confuso es la propiedad, así como el delegado getter y el delegado setter dependiendo de las diferencias de contexto sutiles . Las buenas características que no son abrumadoramente buenas ni siquiera entran en C #, esto parece ser realmente malo. Si quieres escribir un código corto, prueba Perl.

Ya existe una manera de expresar claramente cuál necesita en C #, no veo el problema.

Nathan Cooper
fuente