Polimorfismo
O polimorfismo é muitas vezes referido como o terceiro pilar da programação orientada a objetos, depois do encapsulamento e da herança. Polimorfismo é uma palavra grega que significa "multiforme" e tem dois aspetos distintos:
- Em tempo de execução, objetos de uma classe derivada podem ser tratados como objetos de uma classe base em locais como parâmetros de método e coleções ou matrizes. Quando esse polimorfismo ocorre, o tipo declarado do objeto não é mais idêntico ao seu tipo de tempo de execução.
- As classes base podem definir e implementar métodos virtuais, e as classes derivadas podem substituí-los, o que significa que fornecem sua própria definição e implementação. Em tempo de execução, quando o código do cliente chama o método, o CLR procura o tipo de tempo de execução do objeto e invoca essa substituição do método virtual. No código-fonte, você pode chamar um método em uma classe base e fazer com que a versão de uma classe derivada do método seja executada.
Os métodos virtuais permitem que você trabalhe com grupos de objetos relacionados de maneira uniforme. Por exemplo, suponha que você tenha um aplicativo de desenho que permite que um usuário crie vários tipos de formas em uma superfície de desenho. Você não sabe em tempo de compilação quais tipos específicos de formas o usuário criará. No entanto, o aplicativo tem que manter o controle de todos os vários tipos de formas que são criadas, e tem que atualizá-los em resposta às ações do mouse do usuário. Você pode usar o polimorfismo para resolver esse problema em duas etapas básicas:
- Crie uma hierarquia de classes na qual cada classe de forma específica deriva de uma classe base comum.
- Use um método virtual para invocar o método apropriado em qualquer classe derivada por meio de uma única chamada para o método de classe base.
Primeiro, crie uma classe base chamada Shape
, e classes derivadas como Rectangle
, Circle
e Triangle
. Dê à Shape
classe um método virtual chamado Draw
, e substitua-o em cada classe derivada para desenhar a forma específica que a classe representa. Crie um List<Shape>
objeto e adicione um Circle
, Triangle
e Rectangle
a ele.
public class Shape
{
// A few example members
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }
// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}
public class Circle : Shape
{
public override void Draw()
{
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
}
}
public class Rectangle : Shape
{
public override void Draw()
{
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
}
}
public class Triangle : Shape
{
public override void Draw()
{
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
}
}
Para atualizar a superfície de desenho, use um loop foreach para iterar pela lista e chamar o Draw
método em cada Shape
objeto na lista. Embora cada objeto na lista tenha um tipo declarado de , é o tipo de tempo de Shape
execução (a versão substituída do método em cada classe derivada) que será invocado.
// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used wherever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
new Rectangle(),
new Triangle(),
new Circle()
};
// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
shape.Draw();
}
/* Output:
Drawing a rectangle
Performing base class drawing tasks
Drawing a triangle
Performing base class drawing tasks
Drawing a circle
Performing base class drawing tasks
*/
Em C#, cada tipo é polimórfico porque todos os tipos, incluindo os tipos definidos pelo usuário, herdam de Object.
Visão geral do polimorfismo
Membros virtuais
Quando uma classe derivada herda de uma classe base, ela inclui todos os membros da classe base. Todo o comportamento declarado na classe base faz parte da classe derivada. Isso permite que os objetos da classe derivada sejam tratados como objetos da classe base. Os modificadores de acesso (public
, private
protected
, e assim por diante) determinam se esses membros são acessíveis a partir da implementação da classe derivada. Os métodos virtuais dão ao designer diferentes opções para o comportamento da classe derivada:
- A classe derivada pode substituir membros virtuais na classe base, definindo um novo comportamento.
- A classe derivada pode herdar o método de classe base mais próximo sem substituí-lo, preservando o comportamento existente, mas permitindo que outras classes derivadas substituam o método.
- A classe derivada pode definir uma nova implementação não virtual dos membros que ocultam as implementações da classe base.
Uma classe derivada pode substituir um membro da classe base somente se o membro da classe base for declarado como virtual ou abstrato. O membro derivado deve usar a palavra-chave override para indicar explicitamente que o método se destina a participar da chamada virtual. O código a seguir fornece um exemplo:
public class BaseClass
{
public virtual void DoWork() { }
public virtual int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public override void DoWork() { }
public override int WorkProperty
{
get { return 0; }
}
}
Os campos não podem ser virtuais; Somente métodos, propriedades, eventos e indexadores podem ser virtuais. Quando uma classe derivada substitui um membro virtual, esse membro é chamado mesmo quando uma instância dessa classe está sendo acessada como uma instância da classe base. O código a seguir fornece um exemplo:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = B;
A.DoWork(); // Also calls the new method.
Os métodos e propriedades virtuais permitem que as classes derivadas estendam uma classe base sem a necessidade de usar a implementação da classe base de um método. Para obter mais informações, consulte Controle de versão com a substituição e novas palavras-chave. Uma interface fornece outra maneira de definir um método ou conjunto de métodos cuja implementação é deixada para classes derivadas.
Ocultar membros da classe base com novos membros
Se você quiser que sua classe derivada tenha um membro com o mesmo nome de um membro em uma classe base, você pode usar a nova palavra-chave para ocultar o membro da classe base. A new
palavra-chave é colocada antes do tipo de retorno de um membro da classe que está sendo substituído. O código a seguir fornece um exemplo:
public class BaseClass
{
public void DoWork() { WorkField++; }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public new void DoWork() { WorkField++; }
public new int WorkField;
public new int WorkProperty
{
get { return 0; }
}
}
Os membros ocultos da classe base podem ser acessados a partir do código do cliente convertendo a instância da classe derivada para uma instância da classe base. Por exemplo:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
Impedir que classes derivadas substituam membros virtuais
Os membros virtuais permanecem virtuais, independentemente de quantas classes foram declaradas entre o membro virtual e a classe que o declarou originalmente. Se class A
declara um membro virtual, e class B
deriva de A
, e class C
deriva de B
, class C
herda o membro virtual e pode substituí-lo, independentemente de a classe B
ter declarado uma substituição para esse membro. O código a seguir fornece um exemplo:
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
Uma classe derivada pode interromper a herança virtual declarando uma substituição como selada. Parar a herança requer colocar a sealed
palavra-chave antes da override
palavra-chave na declaração de membro da classe. O código a seguir fornece um exemplo:
public class C : B
{
public sealed override void DoWork() { }
}
No exemplo anterior, o método DoWork
não é mais virtual para qualquer classe derivada de C
. Ainda é virtual para instâncias de , mesmo que elas sejam convertidas C
para digitar B
ou digitar A
. Os métodos selados podem ser substituídos por classes derivadas usando a palavra-chave new
, como mostra o exemplo a seguir:
public class D : C
{
public new void DoWork() { }
}
Neste caso, se DoWork
for chamado usando D
uma variável do tipo D
, o novo DoWork
é chamado. Se uma variável do tipo C
, B
, ou A
for usada para acessar uma instância de D
, uma chamada para DoWork
seguirá as regras de herança virtual, roteando essas chamadas para a implementação de DoWork
on class C
.
Acessar membros virtuais de classe base de classes derivadas
Uma classe derivada que substituiu ou substituiu um método ou propriedade ainda pode acessar o método ou propriedade na classe base usando a palavra-chave base
. O código a seguir fornece um exemplo:
public class Base
{
public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
public override void DoWork()
{
//Perform Derived's work here
//...
// Call DoWork on base class
base.DoWork();
}
}
Para obter mais informações, consulte base.
Nota
É recomendável que os membros virtuais usem base
para chamar a implementação de classe base desse membro em sua própria implementação. Permitir que o comportamento da classe base ocorra permite que a classe derivada se concentre na implementação do comportamento específico da classe derivada. Se a implementação da classe base não for chamada, caberá à classe derivada tornar seu comportamento compatível com o comportamento da classe base.