Por que não posso ter métodos estáticos abstratos em c #?

182

Ultimamente, tenho trabalhado com fornecedores bastante, e me deparei com uma situação interessante em que queria ter uma classe abstrata que tivesse um método estático abstrato. Li alguns posts sobre o assunto, e isso meio que fazia sentido, mas há uma boa explicação clara?

lomaxx
fonte
3
Deixe-os em aberto para permitir melhorias futuras.
Mark Biek 23/09/08
6
Penso que a questão está no fato de que o C # precisa de outra palavra-chave, exatamente para esse tipo de situação. Você deseja um método cujo valor de retorno dependa apenas do tipo no qual é chamado. Você não pode chamá-lo de "estático" se esse tipo for desconhecido. Mas uma vez que o tipo se torne conhecido, ele se tornará estático. "Estática não resolvida" é a idéia - ainda não é estática, mas assim que soubermos o tipo de recebimento, será. Esse é um conceito perfeitamente bom, e é por isso que os programadores continuam pedindo por ele. Mas não se encaixava na maneira como os designers pensavam sobre a linguagem.
William Jockusch 20/03/2015
@WilliamJockusch o que significa receber tipo? Se eu chamar BaseClass.StaticMethod (), então BaseClass é o único tipo que ele pode usar para tomar a decisão. Mas, nesse nível, é abstrato, portanto, o método não pode ser resolvido. Se você chamar DerivedClass.StaticMethod bem, a classe base será irrelevante.
Martin Capodici
Na classe base, o método não foi resolvido e você não pode usá-lo. Você precisa de um tipo derivado ou de um objeto (que, por sua vez, teria um tipo derivado). Você poderá chamar baseClassObject.Method () ou DerivedClass.Method (). Você não pode chamar BaseClass.Method () porque isso não fornece o tipo.
William Jockusch
Possível duplicata de Como implementar propriedades estáticas virtuais?
peterh - Restabelece Monica

Respostas:

157

Os métodos estáticos não são instanciados como tal, apenas estão disponíveis sem uma referência a objeto.

Uma chamada para um método estático é feita através do nome da classe, não através de uma referência a objeto, e o código da Linguagem Intermediária (IL) para chamá-lo chamará o método abstrato pelo nome da classe que a definiu, não necessariamente pelo nome de a classe que você usou.

Deixe-me mostrar um exemplo.

Com o seguinte código:

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

public class B : A
{
}

Se você chamar B.Test, assim:

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

Em seguida, o código real dentro do método Main é o seguinte:

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

Como você pode ver, a chamada é feita para A.Test, porque foi a classe A que a definiu, e não para B.Test, mesmo que você possa escrever o código dessa maneira.

Se você tivesse tipos de classe , como no Delphi, onde você pode criar uma variável referente a um tipo e não a um objeto, teria mais utilidade para métodos estáticos virtuais e, portanto, abstratos (e também construtores), mas eles não estão disponíveis e portanto, as chamadas estáticas não são virtuais no .NET.

Percebo que os designers de IL podem permitir que o código seja compilado para chamar B.Test e resolver a chamada em tempo de execução, mas ainda assim não seria virtual, pois você ainda teria que escrever algum tipo de nome de classe lá.

Métodos virtuais e, portanto, abstratos, são úteis apenas quando você está usando uma variável que, em tempo de execução, pode conter muitos tipos diferentes de objetos e, portanto, você deseja chamar o método correto para o objeto atual que você possui na variável. Com os métodos estáticos, você precisa passar pelo nome de uma classe de qualquer maneira; portanto, o método exato a ser chamado é conhecido no momento da compilação, porque não pode e não muda.

Portanto, métodos estáticos virtuais / abstratos não estão disponíveis no .NET.

Lasse V. Karlsen
fonte
4
Combinado com a maneira como a sobrecarga do operador é feita em C #, isso infelizmente elimina a possibilidade de exigir que as subclasses forneçam uma implementação para uma determinada sobrecarga do operador.
Chris Moschini
23
Não acho essa resposta muito útil, pois a definição de Test()é Amais do que abstrata e potencialmente definida em B. \
5
Parâmetros de tipo genérico se comportam efetivamente como variáveis ​​de "tipo" não persistentes e métodos estáticos virtuais podem ser úteis nesse contexto. Por exemplo, se alguém tivesse um Cartipo com um CreateFromDescriptionmétodo de fábrica estática virtual , o código que aceitasse um Cartipo genérico restrito Tpoderia chamar T.CreateFromDescriptionpara produzir um carro do tipo T. Essa construção poderia ser suportada muito bem no CLR se cada tipo que definisse esse método contivesse uma instância estática singleton de uma classe aninhada genérica que contivesse os métodos virtuais "estáticos".
supercat
45

Os métodos estáticos não podem ser herdados ou substituídos, e é por isso que eles não podem ser abstratos. Como os métodos estáticos são definidos no tipo, e não na instância, de uma classe, eles devem ser chamados explicitamente nesse tipo. Portanto, quando você quiser chamar um método em uma classe filho, precisará usar o nome para chamá-lo. Isso torna a herança irrelevante.

Suponha que você possa, por um momento, herdar métodos estáticos. Imagine este cenário:

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; }
}

Se você chamar Base.GetNumber (), qual método seria chamado? Qual valor retornou? É muito fácil perceber que, sem criar instâncias de objetos, a herança é bastante difícil. Métodos abstratos sem herança são apenas métodos que não têm um corpo, portanto não podem ser chamados.

David Wengier
fonte
33
Dado o seu cenário, eu diria que Base.GetNumber () retornaria 5; Child1.GetNumber () retorna 1; Child2.GetNumber () retorna 2; Você pode me provar que estou errado, para me ajudar a entender seu raciocínio? Obrigado
Luis Filipe
O rosto que você acha que Base.GetNumber () retorna 5, significa que você já entende o que está acontecendo. Ao retornar o valor base, não há herança em andamento.
David Wengier 04/10/08
60
Por que no mundo Base.GetNumber () retornaria algo além de 5? É um método na classe base - há apenas uma opção lá.
Artem Russakovskii
4
@ArtemRussakovskii: Suponha que alguém tivesse int DoSomething<T>() where T:Base {return T.GetNumber();}. Seria útil se DoSomething<Base>()pudesse retornar cinco, enquanto DoSomething<Child2>()retornaria dois. Essa capacidade seria útil não apenas para exemplos de brinquedos, mas também para algo como class Car {public static virtual Car Build(PurchaseOrder PO);}, onde todas as classes derivadas Carteriam que definir um método que pudesse criar uma instância para um pedido de compra.
supercat
4
Há exatamente o mesmo "problema" com herança não estática.
Arca-kun
18

Outro entrevistado (McDowell) disse que o polimorfismo funciona apenas para instâncias de objetos. Isso deve ser qualificado; existem idiomas que tratam as classes como instâncias do tipo "Classe" ou "Metaclasse". Essas linguagens suportam polimorfismo para os métodos de instância e classe (estático).

C #, como Java e C ++ antes, não é uma linguagem; a staticpalavra-chave é usada explicitamente para indicar que o método está vinculado estaticamente em vez de dinâmico / virtual.

Chris Hanson
fonte
9

Aqui está uma situação em que há definitivamente uma necessidade de herança para campos e 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"?
user275801
fonte
4
Não, há apenas uma instância da matriz 'pernas'. A saída é não determinística, pois você não sabe em que ordem os construtores estáticos serão chamados (na verdade não há garantia de que o construtor estático da classe base seria chamado). 'Necessidade' é um termo bastante absoluto em que 'desejo' é provavelmente mais preciso.
Sam
legsdeve ser uma propriedade abstrata estática.
Little Endian
8

Para adicionar às explicações anteriores, as chamadas de método estático são vinculadas a um método específico em tempo de compilação , que exclui o comportamento polimórfico.

Rytmis
fonte
C # é digitado estaticamente; chamadas para métodos polimórficos também são vinculadas em tempo de compilação, como eu a entendo - ou seja, o CLR não resta para resolver qual método chamar durante o tempo de execução.
Adam Tolley
Então, como exatamente você acha que o polimorfismo funciona no CLR? Sua explicação descartou o envio de métodos virtuais.
Rytmis
Esse não é um comentário tão útil quanto poderia ser. Convidei (com 'como eu o entendo') discurso útil, acho que talvez você possa fornecer um pouco mais de conteúdo - visto que as pessoas vêm aqui procurando respostas e não insultos. Embora, parece que posso ser culpado da mesma coisa - eu realmente quis dizer o comentário acima como uma pergunta: O C # não avalia essas coisas em tempo de compilação?
Adam Tolley
Desculpas, eu não quis dizer um insulto (embora eu admita responder um pouco rapidamente ;-). O ponto da minha pergunta era, se você tem essas classes: class Base {public virtual void Method (); } classe Derivada: Base {public override void Method (); } e escreva assim: Instância Base = new Derived (); instance.Method (); as informações do tipo em tempo de compilação no site de chamada é que temos uma instância do Base, quando a instância real é Derivada. Portanto, o compilador não pode resolver o método exato a ser chamado. Em vez disso, emite um "callvirt" instrução IL que informa o tempo de execução para expedição ..
Rytmis
1
Obrigado cara, isso é informativo! Acho que estou adiando a IL há tempo suficiente, me deseje sorte.
precisa
5

Na verdade, substituímos os métodos estáticos (no delphi), é um pouco feio, mas funciona perfeitamente para as nossas necessidades.

Nós o usamos para que as classes possam ter uma lista de seus objetos disponíveis sem a instância da classe; por exemplo, temos um método parecido com este:

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

É feio, mas necessário; dessa maneira, podemos instanciar exatamente o que é necessário, em vez de ter todas as classes instanciadas apenas para procurar os objetos disponíveis.

Este foi um exemplo simples, mas o aplicativo em si é um aplicativo cliente-servidor que possui todas as classes disponíveis em apenas um servidor e vários clientes diferentes que podem não precisar de tudo o que o servidor possui e nunca precisarão de uma instância de objeto.

Portanto, isso é muito mais fácil de manter do que ter um aplicativo de servidor diferente para cada cliente.

Espero que o exemplo esteja claro.

Fabio Gomes
fonte
0

Os métodos abstratos são implicitamente virtuais. Métodos abstratos requerem uma instância, mas métodos estáticos não têm uma instância. Portanto, você pode ter um método estático em uma classe abstrata, apenas não pode ser abstrato estático (ou abstrato estático).

AMing
fonte
1
-1 métodos virtuais não precisam de uma instância, exceto por design. E você realmente não aborda a questão, mas a desvia.
usar o seguinte comando