¿Por qué se implementaron métodos mágicos en C #?

16

En C #, comencé a ver aparecer todos estos métodos mágicos, sin una copia de seguridad de una interfaz. ¿Por qué fue elegido esto?

Dejame explicar.

Anteriormente en C #, si un objeto implementaba la IEnumerableinterfaz, sería iterable automáticamente por un foreachbucle. Eso tiene sentido para mí, ya que está respaldado por una interfaz, y si tuviera que repetir mi propia Iteratorfunción dentro de la clase, podría hacerlo sin preocuparme de que mágicamente significara otra cosa.

Ahora, aparentemente, (no estoy seguro de cuándo), estas interfaces ya no son necesarias. Solo necesita tener las conversiones de nombres correctas.

Otro ejemplo es hacer que cualquier objeto sea aceptable al tener un método llamado exactamente GetAwaiter que tiene algunas propiedades específicas.

¿Por qué no hacer una interfaz como lo hicieron con ellos IEnumerableo INotifyPropertyChangedpara respaldar esta "magia" estáticamente?

Más detalles sobre lo que quiero decir aquí:

http://blog.nem.ec/2014/01/01/magic-methods-c-sharp/

¿Cuáles son los pros y los contras de los métodos mágicos? ¿Existe algún sitio en línea donde pueda encontrar algo sobre por qué se tomaron estas decisiones?

Mathias Lykkegaard Lorenzen
fuente
44
Debes editar tu opinión personal de por qué "la magia es mala" si no quieres cerrarte.
DougM
2
Si quieres saber por qué Anders Hejlsberg hizo eso, tendrás que preguntarle. Solo puedo decirte por qué habría hecho eso, y es por compatibilidad directa. Los métodos de extensión le permiten "falsificar" métodos de adición a los tipos existentes, pero no hay interfaces de extensión. Si necesita una interfaz para, digamos, async/ await, eso solo funcionará con el código que se escribió después de que .NET 4.5 se extendió lo suficiente como para ser un objetivo viable ... que es básicamente ahora. Pero una traducción puramente sintáctica en llamadas a métodos me permite agregar awaitfuncionalidad a los tipos existentes después del hecho.
Jörg W Mittag
2
Tenga en cuenta que esto se aplica básicamente a la orientación a objetos: siempre que un objeto responda a los mensajes apropiados, se considera del tipo correcto.
Jörg W Mittag
77
Su "anterior" y "ahora" están al revés: el método mágico se implementó como la funcionalidad básica para un foreachbucle al principio. Hay que nunca ha sido un requisito para el objeto de implementar IEnumerablepara foreachel trabajo. Ha sido una convención hacerlo.
Jesse C. Slicer
2
@gbulmer aquí es donde recuerdo haber recibido la idea de: blogs.msdn.com/b/ericlippert/archive/2011/06/30/… (y en menor medida ericlippert.com/2013/07/22/… )
Jesse C. Slicer

Respuestas:

16

En general, los "métodos mágicos" se utilizan cuando no es posible crear una interfaz que funcione de la misma manera.

Cuando foreachse introdujo por primera vez en C # 1.0 (ese comportamiento ciertamente no es nada reciente), tuvo que usar métodos mágicos, porque no había genéricos. Las opciones básicamente fueron:

  1. Utilice el no genérico IEnumerabley IEnumeratorque funcione con objects, lo que significa tipos de valores de boxeo. Dado que iterar algo como una lista de ints debería ser muy rápido y ciertamente no debería crear muchos valores en caja de basura, esta no es una buena opción.

  2. Espera genéricos. Esto probablemente significaría retrasar .Net 1.0 (o al menos foreach) por más de 3 años.

  3. Usa métodos mágicos.

Por lo tanto, eligieron la opción n. ° 3 y se quedó con nosotros por razones de compatibilidad con versiones anteriores, aunque desde .Net 2.0, requerirlo IEnumerable<T>también habría funcionado.


Los inicializadores de colección pueden verse diferentes en cada tipo de colección. Compara List<T>:

public void Add(T item)

y Dictionary<TKey, TValue>:

public void Add(TKey key, TValue value)

No puede tener una única interfaz que admita solo el primer formulario List<T>y solo el segundo Dictionary<TKey, TValue>.


Los métodos LINQ generalmente se implementan como métodos de extensión (de modo que puede haber una sola implementación de, por ejemplo, LINQ to Object para todos los tipos que implementan IEnumerable<T>), lo que significa que no es posible usar una interfaz.


Para await, El GetResult()método puede devolver uno voido algún tipo T. Nuevamente, no puede tener una sola interfaz que pueda manejar ambas. Aunque awaitestá parcialmente basado en la interfaz: el camarero tiene que implementar INotifyCompletiony también puede implementar ICriticalNotifyCompletion.

svick
fuente
1
¿Está seguro de que "eligieron la opción # 3" para C # 1.0? Recuerdo que era la opción 1. Mi memoria es que C # afirmó una superioridad significativa sobre Java porque el compilador de C # hizo 'auto-boxing' y 'auto-unboxing'. Se afirmó que C # hizo que un código así funcionara mucho mejor que Java en parte debido a eso.
Gbulmer
1
La documentación foreachen C # 1.2 (no hay nada en MSDN para C # 1.0) dice que la expresión en foreach"Evalúa a un tipo que implementa IEnumerableo un tipo que declara un GetEnumeratormétodo". Y no estoy seguro de qué quiere decir con eso, unboxing en C # siempre es explícito.
svick
1
@gbulmer Y la especificación ECMA para C # de diciembre de 2001 (lo que significa que es para C # 1.0) también dice que (§15.8.4): “Se dice que un tipo C es un tipo de colección si implementa la interfaz System.IEnumerable o implementa el patrón de recolección cumpliendo con todos los siguientes criterios [...] ".
svick
1
@svick foreach(T x in sequence)aplica conversiones explícitas a Tlos elementos de la secuencia. Entonces, si la secuencia es simple IEnumerabley Tes un tipo de valor, se desempaquetará sin que se escriba ningún molde explícito en su código. Una de las partes más feas de C #.
CodesInChaos
@CodesInChaos "Auto-unboxing" suena bastante general, no me di cuenta de que hacía referencia a esta característica específica. (Creo que debería haberlo hecho, ya que estábamos hablando foreach).
svick