¿Por qué no puedo tener métodos estáticos abstractos en C #?

182

Últimamente he estado trabajando con proveedores , y me encontré con una situación interesante en la que quería tener una clase abstracta que tuviera un método estático abstracto. Leí algunas publicaciones sobre el tema, y ​​tenía sentido, pero ¿hay alguna explicación clara y agradable?

lomaxx
fuente
3
Déjelos abiertos para permitir futuras mejoras.
Mark Biek el
66
Creo que la pregunta es llegar al hecho de que C # necesita otra palabra clave, precisamente para este tipo de situación. Desea un método cuyo valor de retorno depende solo del tipo en el que se llama. No puede llamarlo "estático" si dicho tipo es desconocido. Pero una vez que se conoce el tipo, se volverá estático. La idea es "estática no resuelta": todavía no es estática, pero una vez que sepamos el tipo de recepción, lo será. Este es un concepto perfectamente bueno, por eso los programadores siguen pidiéndolo. Pero no encajaba en la forma en que los diseñadores pensaban sobre el lenguaje.
William Jockusch
@WilliamJockusch, ¿qué significa recibir tipo? Si llamo BaseClass.StaticMethod (), entonces BaseClass es el único tipo que puede usar para tomar la decisión. Pero a este nivel es abstracto, por lo que el método no se puede resolver. Si en su lugar llama a DerivedClass.StaticMethod bien, entonces la clase base es irrelevante.
Martin Capodici
En la clase base, el método no está resuelto y no puede usarlo. Necesita un tipo derivado o un objeto (que a su vez tendría un tipo derivado). Debería poder llamar a baseClassObject.Method () o DerivedClass.Method (). No puede llamar a BaseClass.Method () porque eso no le da el tipo.
William Jockusch
Posible duplicado de ¿Cómo implementar propiedades estáticas virtuales?
peterh - Restablece a Mónica el

Respuestas:

157

Los métodos estáticos no son instanciados como tales, solo están disponibles sin una referencia de objeto.

Una llamada a un método estático se realiza a través del nombre de la clase, no a través de una referencia de objeto, y el código del lenguaje intermedio (IL) para llamarlo llamará al método abstracto a través del nombre de la clase que lo definió, no necesariamente el nombre de la clase que usaste

Déjame mostrarte un ejemplo.

Con el siguiente código:

public class A
{
    public static void Test()
    {
    }
}

public class B : A
{
}

Si llamas a B.Test, así:

class Program
{
    static void Main(string[] args)
    {
        B.Test();
    }
}

Entonces el código real dentro del método Main es el siguiente:

.entrypoint
.maxstack 8
L0000: nop 
L0001: call void ConsoleApplication1.A::Test()
L0006: nop 
L0007: ret 

Como puede ver, la llamada se realiza a A.Test, porque fue la clase A la que lo definió, y no a B.Test, aunque puede escribir el código de esa manera.

Si tuviera tipos de clase , como en Delphi, donde puede hacer una variable que se refiera a un tipo y no a un objeto, tendría más uso para métodos estáticos virtuales y, por lo tanto, abstractos (y también constructores), pero no están disponibles y así, las llamadas estáticas no son virtuales en .NET.

Me doy cuenta de que los diseñadores de IL podrían permitir que el código se compile para llamar a B.Test y resolver la llamada en tiempo de ejecución, pero aún así no sería virtual, ya que aún tendría que escribir algún tipo de nombre de clase allí.

Los métodos virtuales, y por lo tanto los abstractos, solo son útiles cuando está utilizando una variable que, en tiempo de ejecución, puede contener muchos tipos diferentes de objetos, y por lo tanto desea llamar al método correcto para el objeto actual que tiene en la variable. Con los métodos estáticos, debe pasar por un nombre de clase de todos modos, por lo que el método exacto para llamar se conoce en tiempo de compilación porque no puede cambiar ni cambiará.

Por lo tanto, los métodos estáticos virtuales / abstractos no están disponibles en .NET.

Lasse V. Karlsen
fuente
44
Combinado con la forma en que se realiza la sobrecarga del operador en C #, desafortunadamente esto elimina la posibilidad de requerir subclases para proporcionar una implementación para una sobrecarga del operador.
Chris Moschini
23
No encuentro esta respuesta terriblemente útil ya que la definición de Test()es en Alugar de ser abstracta y potencialmente definida en B. \
55
Los parámetros de tipo genérico se comportan efectivamente como variables de "tipo" no persistentes, y los métodos estáticos virtuales podrían ser útiles en dicho contexto. Por ejemplo, si uno tuviera un Cartipo con un CreateFromDescriptionmétodo de fábrica estática virtual , entonces el código que aceptaba un Cartipo genérico restringido Tpodría llamar T.CreateFromDescriptionpara producir un automóvil de tipo T. Tal construcción podría ser soportada bastante bien dentro del CLR si cada tipo que definiera dicho método tuviera una instancia única estática de una clase genérica anidada que contuviera los métodos "estáticos" virtuales.
supercat
45

Los métodos estáticos no pueden heredarse ni anularse, y es por eso que no pueden ser abstractos. Dado que los métodos estáticos se definen en el tipo, no en la instancia, de una clase, deben llamarse explícitamente en ese tipo. Entonces, cuando desee llamar a un método en una clase secundaria, debe usar su nombre para llamarlo. Esto hace que la herencia sea irrelevante.

Suponga que, por un momento, podría heredar métodos estáticos. Imagina este escenario:

public static class Base
{
    public static virtual int GetNumber() { return 5; }
}

public static class Child1 : Base
{
    public static override int GetNumber() { return 1; }
}

public static class Child2 : Base
{
    public static override int GetNumber() { return 2; }
}

Si llama a Base.GetNumber (), ¿qué método se llamaría? ¿Qué valor devuelto? Es bastante fácil ver que sin crear instancias de objetos, la herencia es bastante difícil. Los métodos abstractos sin herencia son solo métodos que no tienen cuerpo, por lo que no se pueden llamar.

David Wengier
fuente
33
Dado su escenario, diría que Base.GetNumber () devolvería 5; Child1.GetNumber () devuelve 1; Child2.GetNumber () devuelve 2; ¿Puedes demostrar que estoy equivocado para ayudarme a entender tu razonamiento? Gracias
Luis Filipe
La cara que crees que Base.GetNumber () devuelve 5, significa que ya entiendes lo que está sucediendo. Al devolver el valor base, no hay herencia en curso.
David Wengier
6060
¿Por qué en el mundo Base.GetNumber () devolvería algo más que 5? Es un método en la clase base: solo hay una opción allí.
Artem Russakovskii
44
@ArtemRussakovskii: Supongamos que uno lo hubiera hecho int DoSomething<T>() where T:Base {return T.GetNumber();}. Parecería útil siDoSomething<Base>() pudiera devolver cinco, mientras DoSomething<Child2>()que devolvería dos. Dicha habilidad no solo sería útil para ejemplos de juguetes, sino también para algo así class Car {public static virtual Car Build(PurchaseOrder PO);}, donde cada clase derivada Cartendría que definir un método que pudiera construir una instancia dada una orden de compra.
supercat
44
Hay exactamente el mismo "problema" con la herencia no estática.
Ark-kun
18

Otro encuestado (McDowell) dijo que el polimorfismo solo funciona para instancias de objetos. Eso debería estar calificado; hay lenguajes que tratan las clases como instancias de un tipo "Clase" o "Metaclase". Estos lenguajes admiten el polimorfismo tanto para los métodos de instancia como de clase (estáticos).

C #, como Java y C ++ antes, no es tal lenguaje; la staticpalabra clave se usa explícitamente para indicar que el método está vinculado estáticamente en lugar de dinámico / virtual.

Chris Hanson
fuente
9

Aquí hay una situación en la que definitivamente hay una necesidad de herencia para los campos y métodos estáticos:

abstract class Animal
{
  protected static string[] legs;

  static Animal() {
    legs=new string[0];
  }

  public static void printLegs()
  {
    foreach (string leg in legs) {
      print(leg);
    }
  }
}


class Human: Animal
{
  static Human() {
    legs=new string[] {"left leg", "right leg"};
  }
}


class Dog: Animal
{
  static Dog() {
    legs=new string[] {"left foreleg", "right foreleg", "left hindleg", "right hindleg"};
  }
}


public static void main() {
  Dog.printLegs();
  Human.printLegs();
}


//what is the output?
//does each subclass get its own copy of the array "legs"?
usuario275801
fuente
44
No, solo hay una instancia de la matriz 'patas'. La salida no es determinista ya que no sabe en qué orden se llamarán los constructores estáticos (en realidad no hay garantía de que se llame al constructor estático de la clase base). "Necesidad" es un término bastante absoluto donde "deseo" es probablemente más preciso.
Sam
legsdebería ser una propiedad abstracta estática.
Little Endian
8

Para agregar a las explicaciones anteriores, las llamadas a métodos estáticos están vinculadas a un método específico en tiempo de compilación , que más bien descarta el comportamiento polimórfico.

Rytmis
fuente
C # está estáticamente escrito; Las llamadas a métodos polimórficos también están vinculadas en tiempo de compilación, según tengo entendido, es decir, el CLR no tiene que resolver qué método llamar durante el tiempo de ejecución.
Adam Tolley
Entonces, ¿cómo cree exactamente que funciona el polimorfismo en el CLR? Su explicación simplemente descartó el envío de métodos virtuales.
Rytmis
Ese no es realmente un comentario tan útil como podría ser. Invité (con 'como lo entiendo') un discurso útil, creo que tal vez podrías proporcionar un poco más de contenido, ya que la gente viene aquí buscando respuestas y no insultos. Aunque, parece que puedo ser culpable de lo mismo, realmente quise decir el comentario anterior como una pregunta: ¿C # no evalúa estas cosas en tiempo de compilación?
Adam Tolley
Disculpas, no quise decir un insulto (aunque admito que respondí un poco rápido ;-). El punto de mi pregunta era, si tienes estas clases: class Base {public virtual void Method (); } clase derivada: Base {método de anulación de anulación pública (); } y escriba así: Base instancia = new Derived (); instancia.Method (); La información del tipo de tiempo de compilación en el sitio de la llamada es que tenemos una instancia de Base, cuando la instancia real es Derivada. Entonces el compilador no puede resolver el método exacto para llamar. En su lugar, emite una instrucción de "callvirt" IL que le indica al tiempo de ejecución para el envío ..
Rytmis
1
Gracias hombre, eso es informativo! Supongo que he estado posponiendo la inmersión en IL el tiempo suficiente, deséenme suerte.
Adam Tolley
5

De hecho, anulamos los métodos estáticos (en delphi), es un poco feo, pero funciona bien para nuestras necesidades.

Lo usamos para que las clases puedan tener una lista de sus objetos disponibles sin la instancia de clase, por ejemplo, tenemos un método que se ve así:

class function AvailableObjects: string; override;
begin
  Result := 'Object1, Object2';
end; 

Es feo pero necesario, de esta manera podemos crear instancias de lo que se necesita, en lugar de tener todas las clases instanciadas solo para buscar los objetos disponibles.

Este fue un ejemplo simple, pero la aplicación en sí misma es una aplicación cliente-servidor que tiene todas las clases disponibles en un solo servidor y múltiples clientes diferentes que pueden no necesitar todo lo que tiene el servidor y nunca necesitarán una instancia de objeto.

Esto es mucho más fácil de mantener que tener una aplicación de servidor diferente para cada cliente.

Espero que el ejemplo sea claro.

Fabio Gomes
fuente
0

Los métodos abstractos son implícitamente virtuales. Los métodos abstractos requieren una instancia, pero los métodos estáticos no tienen una instancia. Por lo tanto, puede tener un método estático en una clase abstracta, simplemente no puede ser un resumen estático (o un resumen estático).

AMing
fuente
1
-1 los métodos virtuales no necesitan una instancia, excepto por diseño. Y en realidad no aborda la pregunta, sino que la desvía.
FallenAvatar