¿Por qué se requiere la palabra clave 'this' para llamar a un método de extensión desde dentro de la clase extendida?

79

He creado un método de extensión para ASP.NET MVC ViewPage, por ejemplo:

public static class ViewExtensions
{
    public static string Method<T>(this ViewPage<T> page) where T : class
    {
        return "something";
    }
}

Al llamar a este método desde una Vista (derivada de ViewPage), aparece el error " CS0103: El nombre 'Método' no existe en el contexto actual " a menos que use la thispalabra clave para llamarlo:

<%: Method() %> <!-- gives error CS0103 -->
<%: this.Method() %> <!-- works -->

¿Por qué se thisrequiere la palabra clave? ¿O funciona sin él, pero me falta algo?

(Creo que debe haber un duplicado de esta pregunta, pero no pude encontrar uno)

Actualización :

Como dice Ben Robinson , la sintaxis para llamar a los métodos de extensión es simplemente azúcar de compilación. Entonces, ¿por qué el compilador no puede verificar automáticamente los métodos de extensión de los tipos base del tipo actual sin requerir la palabra clave this?

M4N
fuente
@ M4N: esto parece el posible duplicado que buscaba
djdd87
@GenericTypeTea: sí, es una pregunta similar. Pero no estoy preguntando cómo llamar al método de extensión ( que tengo que agregar la thispalabra clave), pero estoy preguntando por qué thisse requiere la palabra clave. Actualicé mi pregunta.
M4N
@ M4N - Buen punto, dicen que hay que hacerlo, pero las respuestas no dan una explicación suficientemente buena de por qué.
djdd87
En los métodos de instancia, 'this' se pasa implícitamente al método. Al llamar a Method () en lugar de this.Method () o Method (this), no le está diciendo al compilador qué pasar al método.
Alex Humphrey
@ Alex. Técnicamente, imagino que sería posible que el compilador infiera "esto" en el contexto de llamar a Method (), sin embargo, creo que esto habría sido una mala elección de diseño y haría que el código fuera menos legible.
Ben Robinson

Respuestas:

55

Un par de puntos:

En primer lugar, la función propuesta (implícita "esto" en una llamada al método de extensión) es innecesaria . Los métodos de extensión eran necesarios para que las comprensiones de consultas LINQ funcionaran como queríamos; el receptor siempre se indica en la consulta, por lo que no es necesario admitir esto implícitamente para que LINQ funcione.

En segundo lugar, la función funciona en contra del diseño más general de los métodos de extensión: es decir, que los métodos de extensión le permiten extender un tipo que no puede extender usted mismo , ya sea porque es una interfaz y no conoce la implementación, o porque la conoce. Conozca la implementación pero no tenga el código fuente.

Si usted está en el escenario en el que se utiliza un método de extensión para un tipo dentro de ese tipo , entonces no tiene acceso al código fuente. Entonces, ¿por qué estás usando un método de extensión? Puede escribir un método de instancia usted mismo si tiene acceso al código fuente del tipo extendido, ¡y entonces no tiene que usar ningún método de extensión! Entonces, su implementación puede aprovechar el acceso al estado privado del objeto, lo que los métodos de extensión no pueden.

Facilitar el uso de métodos de extensión desde dentro de un tipo al que tiene acceso es fomentar el uso de métodos de extensión sobre los métodos de instancia. Los métodos de extensión son geniales, pero generalmente es mejor usar un método de instancia si tiene uno.

Dados esos dos puntos, la carga ya no recae sobre el diseñador del lenguaje para explicar por qué no existe la función . Ahora le toca a usted explicar por qué debería hacerlo . Las características tienen enormes costos asociados con ellas. Esta característica no es necesaria y funciona en contra de los objetivos de diseño establecidos de los métodos de extensión; ¿Por qué deberíamos asumir el costo de implementarlo? Explique qué escenario importante y convincente permite esta función y consideraremos implementarla en el futuro. No veo ningún escenario importante y convincente que lo justifique, pero tal vez haya uno que me haya perdido.

Eric Lippert
fuente
11
¡Gracias por esa respuesta! Dos puntos: 1: No estaba pidiendo que se implementara esa característica, solo para una explicación, por qué no está allí / por qué thisse requiere. 2: ¿Por qué llamo a un método de extensión desde el tipo extendido? En el ejemplo dado, estoy agregando algunos métodos de conveniencia a la clase base (ViewPage) y lo estoy llamando desde clases derivadas. Esto elimina la necesidad de crear una clase base común para todas mis vistas. Por cierto: estoy de acuerdo en que usar un método de extensión desde dentro del tipo extendido es incómodo, pero nuevamente vea el ejemplo con MVC ViewPage.
M4N
3
@ M4N: esa es exactamente mi situación también: quiero extender el método UserControl y tener esa extensión disponible para todos mis UserControls, sin el explícito 'this'. Sé que el desarrollo del compilador no es una democracia, pero considero que este es mi voto a favor de un 'esto' implícito :-)
Duncan Bayne
8
Hola Eric, reconozco que este no es un escenario principal. Sin embargo, el patrón general parece ser 1. No tiene acceso de edición de código fuente a una clase base. 2. desea agregar algunos métodos (protegidos) al tipo base, que desea invocar dentro de los cuerpos de las clases derivadas. ¿Existe algún otro mecanismo para lograrlo? this.MethodName no es tan difícil de escribir, pero se vuelve molesto después de un tiempo ...
Gishu
5
Creo que el escenario de agregar un método de extensión a una clase base (para el que no tiene la fuente) es válido. En ese caso, hubiera sido bueno poder llamar al método sin "this". Por cierto, esto puede resolverse en la próxima versión de c # con "importaciones estáticas".
ácido lisérgico
4
Una razón válida para utilizar métodos de extensión dentro de la clase que está ampliando es cuando tiene varios tipos que implementan una interfaz. Por ejemplo, tiene dos clases que implementan ICrossProductabley define un método de extensión en esa interfaz llamada GetProduct. Ejemplo muy simple, pero a menudo lo encuentro útil. Sin embargo, no es el mejor enfoque en todos los casos.
Ian Newson
9

Sin él, el compilador simplemente lo ve como un método estático en una clase estática que toma la página como su primer parámetro. es decir

// without 'this'
string s = ViewExtensions.Method(page);

vs.

// with 'this'
string s = page.Method();
Ferruccio
fuente
1
Creo que se supone que es ViewExtensions.Method (página).
Dave Van den Eynde
6

En los métodos de instancia, 'this' se pasa implícitamente a cada método de forma transparente, por lo que puede acceder a todos los miembros que proporciona.

Los métodos de extensión son estáticos. Al llamar en Method()lugar de this.Method()o Method(this), no le está diciendo al compilador qué pasar al método.

Podría decir '¿por qué no se da cuenta de cuál es el objeto de llamada y lo pasa como un parámetro?'

La respuesta es que los métodos de extensión son estáticos y se pueden llamar desde un contexto estático, donde no hay "esto".

Supongo que podrían comprobarlo durante la compilación, pero para ser honesto, probablemente sea mucho trabajo por muy poco beneficio. Y, para ser honesto, veo pocos beneficios en eliminar parte de la explícitaidad de las llamadas al método de extensión. El hecho de que se puedan confundir, por ejemplo, con métodos significa que a veces pueden ser bastante poco intuitivos (por ejemplo, NullReferenceExceptions no arroja). A veces pienso que deberían haber introducido un nuevo operador de estilo 'pipe-forward' para métodos de extensión.

Alex Humphrey
fuente
Entonces, parece que tomaron una decisión de diseño realmente mala al poder llamarlo desde un contexto estático, ya que no veo por qué nadie lo haría.
AustinWBryan
3

Es importante tener en cuenta que existen diferencias entre los métodos de extensión y los métodos regulares. Creo que te has cruzado con uno de ellos.

Le daré un ejemplo de otra diferencia: es bastante fácil llamar a un método de extensión en una nullreferencia de objeto. Afortunadamente, esto es mucho más difícil de hacer con los métodos habituales. (Pero se puede hacer. IIRC, Jon Skeet demostró cómo hacer esto manipulando el código CIL).

static void ExtensionMethod(this object obj) { ... }

object nullObj = null;
nullObj.ExtensionMethod();  // will succeed without a NullReferenceException!

Dicho esto, estoy de acuerdo en que parece un poco poco lógico que thisse requiera llamar al método de extensión. Después de todo, un método de extensión idealmente debería "sentirse" y comportarse como uno normal.

Pero en realidad, los métodos de extensión son más como azúcar sintáctico agregado sobre el lenguaje existente que como una característica básica temprana que encaja perfectamente en el lenguaje en todos los aspectos.

stakx - ya no contribuye
fuente
en lenguaje Boo puede invocar el método de extensión o la propiedad directamente en null, es decir, null.Do ().
Sergey Mirvoda
1
@Sergey: Suena peligroso ... :) ¿Me da curiosidad saber cómo Boo conoce el tipo (implícito) de null? Podría ser casi cualquier tipo de referencia, ¿cómo sabe qué métodos están disponibles null?
stakx - ya no contribuye
1
Puede haber una buena razón para poder llamar a un método de extensión con una referencia nula.
Dave Van den Eynde
@DaveVandenEynde: En mi humilde opinión, fue un error para .net no definir un atributo para los métodos no virtuales que requerirían que los compiladores los invoquen con llamadas no virtuales. Algunos tipos de clases inmutables como Stringlógicamente se comportan como valores, y podrían tener un valor predeterminado sensible cuando son nulos (por ejemplo, .net podría considerar consistentemente una referencia nula de tipo estático stringcomo una cadena vacía, en lugar de hacerlo solo esporádicamente). Tener que usar métodos estáticos para cosas como esta if (!String.IsNullOrEmpty(stringVar))es simplemente espantoso.
supercat
1
@supercat Sí, horrible. También puede hacer string.IsNullOrEmpty (). Mucho más bonita.
Dave Van den Eynde
1

Porque el método de extensión no existe con la clase ViewPage. Necesita decirle al compilador en qué está llamando al método de extensión. Recuerde que this.Method()es solo azúcar de compilación para ViewExtensions.Method(this). Es de la misma manera que no puedes simplemente llamar a un método de extensión dentro de cualquier clase por el nombre del método.

Ben Robinson
fuente
1
Esto tiene sentido (de alguna manera). Pero si es azúcar de compilación, ¿por qué no puede el compilador buscar métodos de extensión sin la thispalabra clave?
M4N
Me parece un descuido. Como dijiste, el compilador podría ver que el método no existe y luego verifique la disponibilidad de las extensiones.
TomTom
Sí, podría decirse que esto podría funcionar. Sospecho que es una decisión de diseño específica para hacer que el código sea más legible. Si simplemente pudiera llamar a ExtensionMethod () dentro de una clase, podría ser un poco confuso para alguien que lea el código si esa clase no contiene ese método pero con this.ExtensionMethod () al menos es consistente con el uso de MyInstance.ExtentionMethod ()
Ben Robinson
Creo que this.Method () es más como azúcar sintáctico para ViewExtensions.Method (this).
Alex Humphrey
1

Estoy trabajando en una API fluida y encontré el mismo problema. Aunque tengo acceso a la clase que estoy ampliando, todavía quería que la lógica de cada uno de los métodos fluidos estuviera en sus propios archivos. La palabra clave "this" era muy poco intuitiva, los usuarios seguían pensando que faltaba el método. Lo que hice fue convertir mi clase en una clase parcial que implementó los métodos que necesitaba en lugar de usar métodos de extensión. No vi ninguna mención de parciales en las respuestas. Si tiene esta pregunta, los parciales podrían ser una mejor opción.

Reyn
fuente