Nem todo método pertence naturalmente a uma instância de classe, e podemos utilizar alguns métodos sem ter que instânciar as classes (sem criar objetos), isso é possível graças ao static. Ou seja, o método static acessa campos marcados como estáticos.
Crie esse método na classe Caixa:
public static int caixaLado2() {
int lat = 2;
int vol = lat * lat * lat;
return vol;
}
E no programa principal, podemos chamar o método estático sem ter que instanciar a classe onde ela está:
Console.WriteLine(Caixa.caixaLado2());
Veja um exemplo de uso de métodos e atributos estáticos numa classe denominada Lampada:
class Lampada {
private static float preco = 9.50f;
private static bool acesa = false;
public static void custo() {
Console.WriteLine("A lâmpada custa R$ {0:0.00}.", preco);
// Note que não usamos this para exibir atributos estáticos.
}
public static void acender() {
Console.WriteLine("A lâmpada está acesa!");
acesa = true;
}
public static void apagar() {
Console.WriteLine("A lâmpada está apagada!");
acesa = false;
}
}
E no código principal:
static void Main(string[] args) {
Lampada.custo(); // Método estático.
Lampada.acender();
Lampada.apagar();
}
PS: Não é recomendado chamar atributos e métodos estáticos através de objetos (tanto que algumas linguagens nem permitem isso), pois são atributos e métodos da classe, por isso devem ser chamados diretamente através dela.
Também podemos fazer novas atribuições em atributos estáticos no código principal, desde que sejam públicos, e toda instância feita por ele também é alterada, por exemplo:
Lampada.preco = 7.25f; // Atributo estático, deixe ele público
O static, teoricamente, significa que só uma alocação de memória é criada para esse atributo ou método, não tendo duas cópias na memória em simultâneo.
Em outras palavras, o static manipula os atributos e métodos na classe toda, não apenas em uma instância, e todo objeto criado com ela também terá essa alteração. São atributos e métodos globais.
PS: Métodos estáticos só podem trabalhar outros métodos e atributos quando estes também forem estáticos, e não podem ser sobrepostos. E atributos estáticos é recomendável eles serem inicializados.
Como visto, podemos criar métodos estáticos, que são compartilhadas com todos objetos instânciados pela mesma classe. Também podemos criar atributos estáticos.
Crie uma nova classe com o nome Acumula, e esse código aqui:
class Acumula {
public static int soma;
public Acumula() {
soma = 0;
}
public void incrementa() {
soma++;
}
public void incrementa(int valor) {
soma += valor;
}
}
Note que ele tem um atributo estático (que também pode ser chamado pela classe, sem criar um objeto). Também tem um polimorfismo de sobrecarga com incremento, onde escolhemos o incremento do número.
No programa principal, coloque isso:
Acumula[] obj = new Acumula[2];
obj[0] = new Acumula();
obj[1] = new Acumula();
obj[0].incrementa();
Console.WriteLine("O valor é {0}.", Acumula.soma.ToString());
obj[1].incrementa(8);
Console.WriteLine("O valor é {0}.", Acumula.soma.ToString());
Como o campo é compartilhado, ele altera o valor do atributo na classe, e todas as instâncias serão alteradas dessa mesma forma.
PS: Métodos getters e setters de atributos estáticos também devem ser estáticos, e não se utiliza o this dentro deles. Lembre-se também que qualquer método estático só pode trabalhar com atributos e métodos que também sejam estáticos. E atributos estáticos é recomendável os iniciar dentro da classe. Métodos estáticos não podem ser sobrepostos.
Também é melhor usarmos listas ao invés de arrays, quando estamos trabalhando com objetos, dessa forma:
List<Acumula> obj = new List<Acumula>();
obj.Add(new Acumula());
obj.Add(new Acumula());
Ou assim, mais simplificado:
List<Acumula> obj = new List<Acumula> {
new Acumula(),
new Acumula()
};
Herança é um conceito extremamente importante em orientação a objetos. Usamos a herança, por exemplo, para evitar repetição ao definirmos classes com características em comum e são relacionadas entre si. A herança é um relacionamento entre classes, que permite que uma classe adquira os membros de outra classe.
Um exemplo clássico de herança seria a classificação de mamíferos no reino animal. Homens, baleias e gatos são mamíferos, que cimpartilham muitas características entre si, mas claramente possuem atriutos que os diferem uns dos outros. Como podemos modelar baleias, gatos e humanos em um software? Podemos criar classes distintas para cada animal, mas essas classes teriam muitos comportamentos (métodos) em comum entre eles, como respirar, mamar e reproduzir-se, o que ocasionaria repetição desnecessária de código. Usamos então herança para resolver esse problema.
Podemos então criar uma classe chamada Mamifero que possua as funcionalidades comuns a todos os animais mamíferos, e então criar as classes Humano, Baleia e Gato, herando essas funcionalidades e também implementando as funcionalidades específicas de cada animal, como falar, nadar e arranhar.
Para declarar uma herança, coloque após o nome da classe, dois pontos e o nome da classe da qual ela herdará. A classe derivada herda da classe base, e os métodos da classe base se tornam parte da classe derivada. Em C# uma classe pode derivar no máximo de uma classe base. Mas uma classe pode derivar de uma classe já derivada de outra.
Veja a classe Mamifero abaixo:
class Mamifero {
public void respirar() {
Console.WriteLine("Eu Respiro!");
}
public void mamar() {
Console.WriteLine("Eu Mamo");
}
}
O da classe Gato, herdando de Mamifero:
class Gato : Mamifero {
public void arranhar() {
Console.WriteLine("Eu Arranho!");
}
}
E o da classe Humano, também herdando de Mamifero:
class Humano : Mamifero {
public void falar() {
Console.WriteLine("Eu Falo!");
}
}
E no programa principal, podemos declarar normalmente, assim:
Mamifero bicho = new Mamifero();
Humano homem = new Humano();
Gato bichano = new Gato();
homem.falar();
homem.respirar();
bichano.arranhar();
bichano.respirar();
PS: Não é possível herança múltipla, mas podemos fazer uma ou mais implementações, separadas por vírgula, seria algo tipo class NomeDaClasse2 : NomeDaClasse1, NomeDaInterface, sempre separados por vírgulas, a indicação da classe sempre vem antes das interfaces. E é possível usar herança em interfaces, no caso de uma interface herdar os métodos de outras (em interfaces podemos ter herança de mais de uma interface).
Também podemos chamar métodos da classe base nas classes filhas, usando a palavra-chave base, como por exemplo base.nomeDoMetodo().
Um método virtual é um método que pode ser sobreescrito em uma classe derivada. Isso significa que o método pode ser chamado e usado como foi originalmente escrito na classe base, mas também podemos escrever uma nova implementação dele que substitua o método original. Assim, os métodos são relacionados pois realizam tarefas similares, mas de forma específica em cada classe.
Marcamos um método como virtual usando a palavra-chave virtual. Veja um exemplo de método, que será colocado na classe Mamifero:
public virtual void lutar() { // Método virtual
Console.WriteLine("Mamíferos Lutam entre Si!");
}
Um método override (sobreescrever) é um método em uma classe derivada que pode declarar uma outra implementação de um método virtual da classe base. Lembrando que:
Podemos também tornar o método da classe mamífero abstrato, mas nesse caso a classe também terá a indicação abstract antes, o que não permitirá instanciar ela, apenas ser utilizada em heranças:
abstract class Mamifero {
public abstract void lutar();
public void respirar() {
Console.WriteLine("Eu Respiro!");
}
public void mamar() {
Console.WriteLine("Eu Mamo");
}
}
PS: Nem toda classe abstrata precisa ter métodos abstratos. E de certa forma, a interface é como uma classe puramente abstrata.
Na classe Humano, podemos colocar uma nova implementação do método lutar(), dessa forma:
public override void lutar() { // Método sobreescrito
base.lutar(); // Chamará o método da classe mãe, Mamifero, mas só pode ser usado caso não seja abstrato.
Console.WriteLine("Humanos Lutam com Armas!");
}
E na classe Gato, também teremos que implementar o método lutar, assim:
public override void lutar() {
base.lutar(); // Chamará o método da classe mãe, Mamifero, mas só pode ser usado caso não seja abstrato. Console.WriteLine("Gatos Lutam Arranhando!");
}
PS: Métodos abstratos também usam override, assim como os virtuais, mas métodos implementados de interfaces não se utiliza override. Mas qualquer um deles faz parte do polimorfismo.
Lembrando que podemos colocar a tipagem como uma interface ou classe (de preferência abstrata), mas a declaração do objeto com new deve ser de uma classe não-abstrata que implemente ou herde os métodos dela, por exemplo:
Mamifero bichano = new Gato();
Isso é útil para ganharmos em polimorfismo, onde podemos usar vários tipos de objetos em, por exemplo, parâmetros, desde que eles tenham a mesma classe pai em comum. Isso é o polimorfismo de inclusão.
E no programa principal, usamos assim:
Mamifero homem = new Humano();
Mamifero bichano = new Gato();
homem.lutar();
bichano.lutar();
Como podemos ver, o método da classe Gato não foi alterado, apenas o da classe Humano. Isso é chamado de polimorfismo de sobreposição.
Também podemos fazer que os construtores de classes pai sejam herdados pros construtores filhos, dessa forma:
public Humano() : base() {
// Código a ser inicializado aqui.
}
PS: Caso existissem parâmetros, eles só seriam especificados os tipos de todos nos primeiros, no segundo parenteses iriam só os parâmetros da classe pai sem declaração de tipo. Seria algo tipo public ClasseFilha(int n1, int n2) : base(n1).
E também podemos fazer sobreposição de destrutores normalmente.
PS: Caso deseje que uma classe seja final (ou seja, que não possa ser herdada), coloque antes da classe a palavra sealed, algo como sealed class NomeDaClasse. Para métodos, usamos da mesma forma, algo como sealed override void nomeDoMetodo() (apenas métodos que sobrepõe outros podem ser selados). No caso de atributos use const, sempre inicializada, como const int numero = 5.
Métodos e campos públicos em uma classe são acessíveis a todos. Já os privados são acessíveis apenas à classe em si. Porém, às vezes, é importante que uma classe base permita que as classes derivadas acessem alguns de seus membros, ao mesmo tempo em que esconde esses membros de outras classes fora da hierarquia de herança. Neste caso, usamos a palavra protected.
Se uma classe X é derivada de uma classe derivada Y, ela pode acessar os membros protegidos de Y. Já uma classe W, não derivada, não poderá acessar os membros protegidos da classe Y.
Crie a classe Veiculo, dessa forma:
class Veiculo { // Classe base
protected string placa; // Campo protegido
private string chassis;
public int portas;
public Veiculo() { // Construtor
this.placa = "AAA-0000";
this.chassis = "00000000000";
this.portas = 4;
}
public void mostraChassis() {
Console.WriteLine("O chassis é {0}", this.chassis); // this.chassis só é acessível dentro da própria classe
}
}
PS: O this vai ser substituído pelo nome do objeto. Ele verifica um atributo dentro de um método da própria classe e será substituído pela classe chamadora.
E crie a classe Automovel, que herda de veículo:
class Automovel : Veiculo {
public void dadosAuto() {
Console.WriteLine("A placa do auto é {0}", this.placa);
}
}
No caso acima, na classe Automovel não podemos usar o atributo chassis, mas podemos invocar pelo método que criamos.
No programa principal, podemos colocar isso:
Automovel meuCarro = new Automovel();
meuCarro.dadosAuto();
meuCarro.mostraChassis();
Console.WriteLine(meuCarro.portas.ToString());
Veiculo carro = new Veiculo();
carro.mostraChassis();
PS: Além de public, private e protected, um atributo ou método em C# pode ser declarado como internal (disponível para classes dentro do mesmo Assembly) e private protected (disponível para a mesma classe e derivadas dentro do mesmo Assembly).