sobrecarga sirve que programacion polimorfico para orientada operadores objetos metodos constructores c# generics overload-resolution

sirve - La resolución de sobrecarga del método C#no selecciona la anulación genérica concreta



sobrecarga de metodos en programacion orientada a objetos (3)

Este programa completo de C # ilustra el problema:

public abstract class Executor<T> { public abstract void Execute(T item); } class StringExecutor : Executor<string> { public void Execute(object item) { // why does this method call back into itself instead of binding // to the more specific "string" overload. this.Execute((string)item); } public override void Execute(string item) { } } class Program { static void Main(string[] args) { object item = "value"; new StringExecutor() // stack overflow .Execute(item); } }

Me encontré con una StackOverlowException que remonté a este patrón de llamada en el que estaba tratando de desviar las llamadas a una sobrecarga más específica. Para mi sorpresa, la invocación no estaba seleccionando la sobrecarga más específica sin embargo, sino llamando a sí misma. Claramente tiene algo que ver con que el tipo base sea genérico, pero no entiendo por qué no seleccionaría la sobrecarga Execute (cadena).

¿Alguien tiene alguna idea de esto?

El código anterior se simplificó para mostrar el patrón, la estructura real es un poco más complicada, pero el problema es el mismo.


Como otras respuestas han notado, esto es por diseño.

Consideremos un ejemplo menos complicado:

class Animal { public virtual void Eat(Apple a) { ... } } class Giraffe : Animal { public void Eat(Food f) { ... } public override void Eat(Apple a) { ... } }

La pregunta es por qué giraffe.Eat(apple) resuelve en Giraffe.Eat(Food) y no en el Animal.Eat(Apple) .

Esto es una consecuencia de dos reglas:

(1) El tipo de receptor es más importante que el tipo de cualquier argumento cuando se resuelven las sobrecargas.

Espero que esté claro por qué este debe ser el caso. La persona que escribe la clase derivada tiene estrictamente más conocimiento que la persona que escribe la clase base, porque la persona que escribe la clase derivada usa la clase base, y no al revés.

La persona que escribió Giraffe dijo "Tengo una manera para que una Giraffe comiera cualquier alimento " y eso requiere un conocimiento especial de la digestión interna de la jirafa. Esa información no está presente en la implementación de la clase base, que solo sabe cómo comer manzanas.

Por lo tanto, la resolución de sobrecarga siempre debe priorizar la elección de un método aplicable de una clase derivada sobre la elección de un método de una clase base, independientemente de la mejoría de las conversiones de tipo de argumento.

(2) Elegir anular o no anular un método virtual no es parte del área de superficie pública de una clase. Ese es un detalle de implementación privada. Por lo tanto, no se debe tomar ninguna decisión al hacer una resolución de sobrecarga que cambiaría dependiendo de si un método se reemplaza o no.

La resolución de sobrecarga nunca debe decir "Voy a elegir Animal.Eat(Apple) virtual Animal.Eat(Apple) porque fue anulado ".

Ahora, bien podría decir "OK, supongamos que estoy dentro de Giraffe cuando hago la llamada". El código dentro de Giraffe tiene todo el conocimiento de los detalles de implementación privados, ¿verdad? Por lo tanto, podría tomar la decisión de llamar a Animal.Eat(Apple) virtual Animal.Eat(Apple) lugar de Giraffe.Eat(Food) cuando se enfrenta con giraffe.Eat(apple) , ¿verdad? Porque sabe que hay una implementación que comprende las necesidades de las jirafas que comen manzanas.

Esa es una cura peor que la enfermedad. ¡Ahora tenemos una situación donde el código idéntico tiene un comportamiento diferente dependiendo de dónde se ejecuta! Puedes imaginar tener una llamada a la giraffe.Eat(apple) fuera de la clase, refactoriza para que esté dentro de la clase, ¡y de repente el comportamiento observable cambia!

O bien, puedes decir, hey, me doy cuenta de que mi lógica de Jirafa es en realidad lo suficientemente general para pasar a una clase base, pero no a Animal, así que voy a refactorizar mi código de Giraffe para:

class Mammal : Animal { public void Eat(Food f) { ... } public override void Eat(Apple a) { ... } } class Giraffe : Mammal { ... }

Y ahora todas las llamadas a la giraffe.Eat(apple) dentro de la Giraffe repente tiene un comportamiento de resolución de sobrecarga diferente después de la refactorización? Eso sería muy inesperado!

C # es un lenguaje de pozo de éxito; queremos asegurarnos de que las refactorizaciones simples, como cambiar el lugar donde se anula un método en una jerarquía, no provoquen cambios sutiles en el comportamiento.

Resumiendo:

  • La resolución de sobrecarga prioriza los receptores sobre otros argumentos porque llamar al código especializado que conoce las partes internas del receptor es mejor que llamar a un código más general que no lo hace.
  • No se tiene en cuenta si un método se reemplaza ni dónde se lo incluye durante la resolución de sobrecarga; todos los métodos se tratan como si nunca se anularan para fines de resolución de sobrecarga. Es un detalle de implementación, no parte de la superficie del tipo.
  • Se resuelven los problemas de resolución de sobrecarga, ¡módulo de accesibilidad, por supuesto! - De la misma manera, no importa dónde se produce el problema en el código. No tenemos un algoritmo para la resolución donde el receptor es del tipo del código que contiene, y otro para cuando la llamada está en una clase diferente.

Se pueden encontrar ideas adicionales sobre problemas relacionados aquí: https://ericlippert.com/2013/12/23/closer-is-better/ y aquí https://blogs.msdn.microsoft.com/ericlippert/2007/09/04/future-breaking-changes-part-three/


Como se menciona en el article Jon Skeet article , al invocar un método en una clase que también anula un método con el mismo nombre de una clase base, el compilador siempre tomará el método en clase en lugar de la anulación, independientemente de la "especificidad" "de tipo, siempre que la firma sea" compatible ".

Jon continúa señalando que este es un excelente argumento para evitar la sobrecarga a través de los límites de herencia, ya que este es exactamente el tipo de comportamiento inesperado que puede ocurrir.


Parece que esto se menciona en la especificación C # 5.0, 7.5.3 Resolución de sobrecarga:

La resolución de sobrecarga selecciona el miembro de función para invocar en los siguientes contextos distintos dentro de C #:

  • Invocación de un método nombrado en una invocación-expresión (§7.6.5.1).
  • Invocación de un constructor de instancia nombrado en una expresión de creación de objeto (§7.6.10.1).
  • Invocación de un descriptor de acceso mediante un elemento de acceso (§7.6.6).
  • Invocación de un operador predefinido o definido por el usuario al que se hace referencia en una expresión (§7.3.3 y §7.3.4).

Cada uno de estos contextos define el conjunto de miembros de la función candidata y la lista de argumentos de forma única, tal como se describe en detalle en las secciones enumeradas anteriormente. Por ejemplo, el conjunto de candidatos para una invocación de método no incluye los métodos marcados como anulación (§7.4), y los métodos en una clase base no son candidatos si se aplica cualquier método en una clase derivada (§7.6.5.1).

Cuando miramos 7.4:

Una búsqueda de miembro de un nombre N con parámetros de tipo K en un tipo T se procesa de la siguiente manera:

• Primero, se determina un conjunto de miembros accesibles llamado N:

  • Si T es un parámetro de tipo, entonces el conjunto es la unión de los conjuntos de
    miembros accesibles llamados N en cada uno de los tipos especificados como restricción primaria o secundaria (§10.1.5) para T, junto con el conjunto de miembros accesibles llamados N en el objeto.

  • De lo contrario, el conjunto consta de todos los miembros accesibles (§3.5) llamados N en T, incluidos los miembros heredados y el miembro accesible llamado N en el objeto. Si T es un tipo construido, el conjunto de miembros se obtiene sustituyendo los argumentos de tipo como se describe en §10.3.2. Los miembros que incluyen un modificador de anulación se excluyen del conjunto.

Si elimina la override el compilador elige la sobrecarga Execute(string) cuando lanza el elemento.