Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este tutorial apresenta a herança em C#. A herança é um recurso de linguagens de programação orientadas a objetos que permite definir uma classe base que fornece funcionalidade específica (dados e comportamento) e definir classes derivadas que herdam ou substituem essa funcionalidade.
Pré-requisitos
- O .NET SDK mais recente
- Editor do Visual Studio Code
- O DevKit C#
Instruções de instalação
No Windows, esse Arquivo de configuração do WinGet para instalar todos os pré-requisitos. Se você já tiver algo instalado, o WinGet ignorará essa etapa.
- Baixe o arquivo e clique duas vezes para executá-lo.
- Leia o contrato de licença, digite ye selecione Enter quando solicitado a aceitar.
- Se você receber um prompt de UAC (Controle de Conta de Usuário) piscando na barra de tarefas, permita que a instalação continue.
Em outras plataformas, você precisa instalar cada um desses componentes separadamente.
- Baixe o instalador recomendado na página de download do SDK do .NET e clique duas vezes para executá-lo. A página de download detecta sua plataforma e recomenda o instalador mais recente para sua plataforma.
- Baixe o instalador mais recente do Visual Studio Code home page e clique duas vezes para executá-lo. Essa página também detecta sua plataforma e o link deve estar correto para o sistema.
- Clique no botão "Instalar" na página de extensão do DevKit em C#. Isso abre o código do Visual Studio e pergunta se você deseja instalar ou habilitar a extensão. Selecione "instalar".
Como executar os exemplos
Para criar e executar os exemplos neste tutorial, use o utilitário dotnet na linha de comando. Siga estas etapas para cada exemplo:
Crie um diretório para armazenar o exemplo.
Digite o comando dotnet new console em um prompt de comando para criar um novo projeto do .NET Core.
Copie e cole o código do exemplo no editor de código.
Digite o comando dotnet restore na linha de comando para carregar ou restaurar as dependências do projeto.
Não é necessário executar
dotnet restore
, pois ele é executado implicitamente por todos os comandos que exigem uma restauração, comodotnet new
,dotnet build
,dotnet run
,dotnet test
,dotnet publish
edotnet pack
. Para desabilitar a restauração implícita, use a opção--no-restore
.O comando
dotnet restore
ainda é útil em determinados cenários em que realizar uma restauração explícita faz sentido, como compilações de integração contínua no Azure DevOps Services ou em sistemas de compilação que precisam controlar explicitamente quando a restauração ocorrerá.Para obter informações sobre como gerenciar feeds do NuGet, confira a documentação do
dotnet restore
.Insira o comando executar dotnet para compilar e executar o exemplo.
Plano de fundo: O que é herança?
A herança é um dos atributos fundamentais da programação orientada a objetos. Ele permite que você defina uma classe filha que reutiliza (herda), estende ou modifica o comportamento de uma classe base. A classe cujos membros são herdados é chamada de classe base. A classe que herda os membros da classe base é chamada de classe derivada.
O C# e o .NET dão suporte apenas à herança única . Ou seja, uma classe só pode herdar de uma única classe. No entanto, a herança é transitiva, o que permite definir uma hierarquia de herança para um conjunto de tipos. Em outras palavras, o tipo D
pode herdar do tipo C
, que herda do tipo B
, que herda do tipo de classe base A
. Como a herança é transitiva, os membros do tipo A
estão disponíveis para digitar D
.
Nem todos os membros de uma classe base são herdados por classes derivadas. Os seguintes membros não são herdados:
Construtores estáticos, que inicializam os dados estáticos de uma classe.
Construtores de instância, que você chama para criar uma nova instância da classe. Cada classe deve definir seus próprios construtores.
Finalizadores, que são chamados pelo coletor de lixo do runtime para destruir instâncias de uma classe.
Embora todos os outros membros de uma classe base sejam herdados por classes derivadas, se eles são visíveis ou não dependem de sua acessibilidade. A acessibilidade de um membro afeta sua visibilidade para classes derivadas da seguinte maneira:
Membros Privados são visíveis apenas em classes derivadas que estão aninhadas em sua classe base. Caso contrário, elas não estarão visíveis em classes derivadas. No exemplo a seguir,
A.B
é uma classe aninhada daA
qual deriva eC
deriva deA
. O campo privadoA._value
é visível no A.B. No entanto, se você remover os comentários doC.GetValue
método e tentar compilar o exemplo, ele produzirá o erro do compilador CS0122: "'A._value' está inacessível devido ao seu nível de proteção".public class A { private int _value = 10; public class B : A { public int GetValue() { return _value; } } } public class C : A { // public int GetValue() // { // return _value; // } } public class AccessExample { public static void Main(string[] args) { var b = new A.B(); Console.WriteLine(b.GetValue()); } } // The example displays the following output: // 10
Os membros protegidos são visíveis apenas em classes derivadas.
Membros internos são visíveis apenas em classes derivadas localizadas no mesmo assembly que a classe base. Elas não são visíveis em classes derivadas localizadas em um assembly diferente da classe base.
Os membros públicos são visíveis em classes derivadas e fazem parte da interface pública da classe derivada. Membros herdados públicos podem ser chamados exatamente como se fossem definidos na classe derivada. No exemplo a seguir, a classe
A
define um método chamadoMethod1
e a classeB
herda da classeA
. Em seguida, o exemplo chamaMethod1
como se fosse um método de instância emB
.public class A { public void Method1() { // Method implementation. } } public class B : A { } public class Example { public static void Main() { B b = new (); b.Method1(); } }
Classes derivadas também podem substituir membros herdados fornecendo uma implementação alternativa. Para poder substituir um membro, o membro na classe base deve ser marcado com a palavra-chave virtual . Por padrão, os membros da classe base não são marcados como virtual
e não podem ser substituídos. A tentativa de substituir um membro não virtual, como o seguinte exemplo faz, gera o erro do compilador CS0506: "O <membro> não pode substituir o membro herdado <membro>, pois não está marcado como virtual, abstrato ou de substituição."
public class A
{
public void Method1()
{
// Do something.
}
}
public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}
Em alguns casos, uma classe derivada deve substituir a implementação da classe base. Os membros da classe base marcados com a palavra-chave abstrata exigem que as classes derivadas as substituam. A tentativa de compilar o exemplo a seguir gera o erro do compilador CS0534, "<classe> não implementa o membro abstrato herdado <membro>", porque a classe B
não fornece implementação para A.Method1
.
public abstract class A
{
public abstract void Method1();
}
public class B : A // Generates CS0534.
{
public void Method3()
{
// Do something.
}
}
A herança se aplica apenas a classes e interfaces. Outras categorias de tipo (structs, delegados e enumerações) não dão suporte à herança. Devido a essas regras, a tentativa de compilar código como o exemplo a seguir produz um erro do compilador CS0527: "Tipo 'ValueType' na lista de interfaces não é uma interface". A mensagem de erro indica que, embora você possa definir as interfaces que um struct implementa, não há suporte para a herança.
public struct ValueStructure : ValueType // Generates CS0527.
{
}
Herança implícita
Além de qualquer tipo que eles possam herdar através de herança única, todos os tipos no sistema de tipos .NET herdam implicitamente de Object ou de um tipo derivado dele. A funcionalidade comum de Object está disponível para qualquer tipo.
Para ver o que significa herança implícita, vamos definir uma nova classe, SimpleClass
que é simplesmente uma definição de classe vazia:
public class SimpleClass
{ }
Em seguida, você pode usar reflexão (que permite inspecionar os metadados de um tipo para obter informações sobre esse tipo) para obter uma lista dos membros que pertencem ao SimpleClass
tipo. Embora você não tenha definido nenhum membro em sua SimpleClass
classe, a saída do exemplo indica que ele realmente tem nove membros. Um desses membros é um construtor sem parâmetros (ou padrão) que é fornecido automaticamente para o SimpleClass
tipo pelo compilador C#. Os oito restantes são membros do tipo Object, do qual todas as classes e interfaces do sistema de tipos .NET herdam implicitamente.
using System.Reflection;
public class SimpleClassExample
{
public static void Main()
{
Type t = typeof(SimpleClass);
BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
MemberInfo[] members = t.GetMembers(flags);
Console.WriteLine($"Type {t.Name} has {members.Length} members: ");
foreach (MemberInfo member in members)
{
string access = "";
string stat = "";
var method = member as MethodBase;
if (method != null)
{
if (method.IsPublic)
access = " Public";
else if (method.IsPrivate)
access = " Private";
else if (method.IsFamily)
access = " Protected";
else if (method.IsAssembly)
access = " Internal";
else if (method.IsFamilyOrAssembly)
access = " Protected Internal ";
if (method.IsStatic)
stat = " Static";
}
string output = $"{member.Name} ({member.MemberType}): {access}{stat}, Declared by {member.DeclaringType}";
Console.WriteLine(output);
}
}
}
// The example displays the following output:
// Type SimpleClass has 9 members:
// ToString (Method): Public, Declared by System.Object
// Equals (Method): Public, Declared by System.Object
// Equals (Method): Public Static, Declared by System.Object
// ReferenceEquals (Method): Public Static, Declared by System.Object
// GetHashCode (Method): Public, Declared by System.Object
// GetType (Method): Public, Declared by System.Object
// Finalize (Method): Internal, Declared by System.Object
// MemberwiseClone (Method): Internal, Declared by System.Object
// .ctor (Constructor): Public, Declared by SimpleClass
A herança implícita da Object classe disponibiliza esses métodos para a SimpleClass
classe:
O método público
ToString
, que converte umSimpleClass
objeto em sua representação de cadeia de caracteres, retorna o nome de tipo totalmente qualificado. Nesse caso, oToString
método retorna a cadeia de caracteres "SimpleClass".Três métodos que testam a igualdade de dois objetos: o método de instância
Equals(Object)
pública, o método estáticoEquals(Object, Object)
público e o método estáticoReferenceEquals(Object, Object)
público. Por padrão, esses métodos testam a igualdade de referência; ou seja, para ser igual, duas variáveis de objeto devem se referir ao mesmo objeto.O método público
GetHashCode
, que calcula um valor que permite que uma instância do tipo seja usada em coleções de hash.O método público
GetType
, que retorna um Type objeto que representa oSimpleClass
tipo.O método protegido Finalize , que foi projetado para liberar recursos não gerenciados antes que a memória de um objeto seja recuperada pelo coletor de lixo.
O método protegido MemberwiseClone , que cria um clone superficial do objeto atual.
Por causa da herança implícita, você pode chamar qualquer membro herdado de um SimpleClass
objeto como se fosse realmente um membro definido na SimpleClass
classe. Por exemplo, o exemplo a seguir chama o SimpleClass.ToString
método, que SimpleClass
herda de Object.
public class EmptyClass
{ }
public class ClassNameExample
{
public static void Main()
{
EmptyClass sc = new();
Console.WriteLine(sc.ToString());
}
}
// The example displays the following output:
// EmptyClass
A tabela a seguir lista as categorias de tipos que você pode criar em C# e os tipos dos quais eles herdam implicitamente. Cada tipo base disponibiliza um conjunto diferente de membros por meio de herança para tipos derivados implicitamente.
Categoria de tipo | Herda implicitamente de |
---|---|
classe | Object |
Struct | ValueType, Object |
enumeração | Enum ValueType Object |
delegar | MulticastDelegate Delegate Object |
Herança e um relacionamento "é um(a)"
Normalmente, a herança é usada para expressar uma relação "é um" entre uma classe base e uma ou mais classes derivadas, em que as classes derivadas são versões especializadas da classe base; a classe derivada é um tipo da classe base. Por exemplo, a Publication
classe representa uma publicação de qualquer tipo, e as classes Book
e Magazine
representam tipos específicos de publicações.
Observação
Uma classe ou struct pode implementar uma ou mais interfaces. Embora a implementação da interface seja apresentada geralmente como uma alternativa para herança única, ou como uma forma de usar a herança com structs, ela tem como objetivo expressar um relacionamento diferente (um relacionamento "pode fazer") entre uma interface e seu tipo de implementação em comparação com a herança. Uma interface define um subconjunto de funcionalidade (como a capacidade de testar a igualdade, comparar ou classificar objetos ou dar suporte à análise e formatação sensíveis à cultura) que a interface disponibiliza para seus tipos de implementação.
Observe que "é um(a)" também expressa o relacionamento entre um tipo e uma instanciação específica desse tipo. No exemplo a seguir, Automobile
é uma classe que tem três propriedades exclusivas somente leitura: Make
, o fabricante do automóvel; Model
, o tipo de automóvel; e Year
, seu ano de fabricação. Sua Automobile
classe também tem um construtor cujos argumentos são atribuídos aos valores de propriedade e substitui o Object.ToString método para produzir uma cadeia de caracteres que identifica exclusivamente a Automobile
instância em vez da Automobile
classe.
public class Automobile
{
public Automobile(string make, string model, int year)
{
if (make == null)
throw new ArgumentNullException(nameof(make), "The make cannot be null.");
else if (string.IsNullOrWhiteSpace(make))
throw new ArgumentException("make cannot be an empty string or have space characters only.");
Make = make;
if (model == null)
throw new ArgumentNullException(nameof(model), "The model cannot be null.");
else if (string.IsNullOrWhiteSpace(model))
throw new ArgumentException("model cannot be an empty string or have space characters only.");
Model = model;
if (year < 1857 || year > DateTime.Now.Year + 2)
throw new ArgumentException("The year is out of range.");
Year = year;
}
public string Make { get; }
public string Model { get; }
public int Year { get; }
public override string ToString() => $"{Year} {Make} {Model}";
}
Nesse caso, você não deve depender da herança para representar marcas e modelos de carros específicos. Por exemplo, você não precisa definir um Packard
tipo para representar automóveis fabricados pela Packard Motor Car Company. Em vez disso, você pode representá-los criando um Automobile
objeto com os valores apropriados passados para seu construtor de classe, como o exemplo a seguir faz.
using System;
public class Example
{
public static void Main()
{
var packard = new Automobile("Packard", "Custom Eight", 1948);
Console.WriteLine(packard);
}
}
// The example displays the following output:
// 1948 Packard Custom Eight
Um relacionamento é-um(a) baseado na herança é mais bem aplicado a uma classe base e em classes derivadas que adicionam outros membros à classe base, ou que exigem funcionalidades adicionais não incluídas na classe base.
Projetando a classe base e as classes derivadas
Vamos examinar o processo de criação de uma classe base e suas classes derivadas. Nesta seção, você definirá uma classe base, Publication
que representa uma publicação de qualquer tipo, como um livro, uma revista, um jornal, um diário, um artigo etc. Você também definirá uma Book
classe que deriva de Publication
. Você poderia estender facilmente o exemplo para definir outras classes derivadas, como Magazine
, , Journal
Newspaper
e Article
.
A classe base de Publicação
Ao projetar sua Publication
classe, você precisa tomar várias decisões de design:
Quais membros incluir em sua classe base
Publication
e se osPublication
membros fornecem implementações de método ou sePublication
é uma classe base abstrata que serve como um modelo para suas classes derivadas.Nesse caso, a
Publication
classe fornecerá implementações de método. A seção Criar classes base abstratas e suas classes derivadas contém um exemplo que usa uma classe base abstrata para definir os métodos que as classes derivadas devem substituir. Classes derivadas são gratuitas para fornecer qualquer implementação adequada para o tipo derivado.A capacidade de reutilizar código (ou seja, várias classes derivadas compartilham a declaração e a implementação de métodos de classe base e não precisam substituí-los) é uma vantagem das classes base não abstratas. Portanto, você deverá adicionar membros à
Publication
se o código precisar ser compartilhado por um ou mais tiposPublication
especializados. Se você não fornecer implementações de classe base com eficiência, você acabará tendo que fornecer implementações de membro praticamente idênticas em classes derivadas, em vez de uma única implementação na classe base. A necessidade de manter o código duplicado em vários locais é uma possível fonte de bugs.Para maximizar a reutilização de código e criar uma hierarquia de herança lógica e intuitiva, você deseja ter certeza de que inclui na
Publication
classe apenas os dados e a funcionalidade que são comuns a todas ou à maioria das publicações. Em seguida, as classes derivadas implementam membros exclusivos para os tipos específicos de publicação que eles representam.Até onde estender sua hierarquia de classes. Deseja desenvolver uma hierarquia de três ou mais classes, em vez de simplesmente uma classe base e uma ou mais classes derivadas? Por exemplo,
Publication
pode ser uma classe base dePeriodical
, que, por sua vez, é uma classe base deMagazine
,Journal
eNewspaper
.Para seu exemplo, você usará a hierarquia pequena de uma
Publication
classe e uma única classe derivada.Book
Você poderia facilmente estender o exemplo para criar várias classes adicionais que derivam dePublication
, comoMagazine
eArticle
.Se faz sentido instanciar a classe base. Se isso não acontecer, você deverá aplicar a palavra-chave abstrata à classe. Caso contrário, sua
Publication
classe poderá ser instanciada chamando seu construtor de classe. Se for feita uma tentativa de instanciar uma classe marcada com aabstract
palavra-chave por uma chamada direta ao construtor de classe, o compilador C# gerará o erro CS0144, "Não é possível criar uma instância da classe ou interface abstrata". Se for feita uma tentativa de instanciar a classe usando reflexão, o método de reflexão gerará um MemberAccessException.Por padrão, uma classe base pode ser instanciada chamando seu construtor de classe. Você não precisa definir explicitamente um construtor de classe. Se um não estiver presente no código-fonte da classe base, o compilador C# fornecerá automaticamente um construtor padrão (sem parâmetros).
Para seu exemplo, você marcará a
Publication
classe como abstrata para que ela não possa ser instanciada. Umaabstract
classe sem métodosabstract
indica que essa classe representa um conceito abstrato que é compartilhado entre várias classes concretas (como umaBook
,Journal
).Se as classes derivadas devem herdar a implementação de classe base de membros específicos, se elas têm a opção de substituir a implementação da classe base ou se devem fornecer uma implementação. Use a palavra-chave abstrata para forçar classes derivadas a fornecer uma implementação. Use a palavra-chave virtual para permitir que classes derivadas substituam um método de classe base. Por padrão, os métodos definidos na classe base não são substituíveis.
A
Publication
classe não tem métodosabstract
, mas a classe em si éabstract
.Se uma classe derivada representa a classe final na hierarquia de herança e não pode ser usada como uma classe base para classes derivadas adicionais. Por padrão, qualquer classe pode servir como uma classe base. Você pode aplicar a palavra-chave selada para indicar que uma classe não pode servir como uma classe base para classes adicionais. Tentativa de derivar de uma classe lacrada gerou o erro do compilador CS0509: "não é possível derivar do tipo lacrado <typeName>."
Para seu exemplo, você marcará sua classe derivada como
sealed
.
O exemplo a seguir mostra o código-fonte da Publication
classe, bem como uma PublicationType
enumeração retornada pela Publication.PublicationType
propriedade. Além dos membros herdados de Object, a classe Publication
define os seguintes membros exclusivos e substituições de membro:
public enum PublicationType { Misc, Book, Magazine, Article };
public abstract class Publication
{
private bool _published = false;
private DateTime _datePublished;
private int _totalPages;
public Publication(string title, string publisher, PublicationType type)
{
if (string.IsNullOrWhiteSpace(publisher))
throw new ArgumentException("The publisher is required.");
Publisher = publisher;
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("The title is required.");
Title = title;
Type = type;
}
public string Publisher { get; }
public string Title { get; }
public PublicationType Type { get; }
public string? CopyrightName { get; private set; }
public int CopyrightDate { get; private set; }
public int Pages
{
get { return _totalPages; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The number of pages cannot be zero or negative.");
_totalPages = value;
}
}
public string GetPublicationDate()
{
if (!_published)
return "NYP";
else
return _datePublished.ToString("d");
}
public void Publish(DateTime datePublished)
{
_published = true;
_datePublished = datePublished;
}
public void Copyright(string copyrightName, int copyrightDate)
{
if (string.IsNullOrWhiteSpace(copyrightName))
throw new ArgumentException("The name of the copyright holder is required.");
CopyrightName = copyrightName;
int currentYear = DateTime.Now.Year;
if (copyrightDate < currentYear - 10 || copyrightDate > currentYear + 2)
throw new ArgumentOutOfRangeException($"The copyright year must be between {currentYear - 10} and {currentYear + 1}");
CopyrightDate = copyrightDate;
}
public override string ToString() => Title;
}
Um construtor
Como a
Publication
classe éabstract
, ela não pode ser instanciada diretamente do código, como o exemplo a seguir:var publication = new Publication("Tiddlywinks for Experts", "Fun and Games", PublicationType.Book);
No entanto, seu construtor de instância pode ser chamado diretamente de construtores de classes derivadas, como mostra o código-fonte da classe
Book
.Duas propriedades relacionadas à publicação
Title
é uma propriedade somente de leitura String cujo valor é fornecido pela chamada do construtorPublication
.Pages
é uma propriedade de leitura-gravação Int32 que indica quantas páginas totais a publicação tem. O valor é armazenado em um campo privado chamadototalPages
. O lançamento deve ser de um número positivo ou de um ArgumentOutOfRangeException.Membros relacionados ao publicador
Duas propriedades somente leitura,
Publisher
eType
. Os valores são fornecidos originalmente pela chamada ao construtor da classePublication
.Membros relacionados à publicação
Dois métodos
Publish
eGetPublicationDate
, definir e retornar a data de publicação. OPublish
método define um sinalizador privadopublished
paratrue
quando ele é chamado e atribui a data passada a ele como um argumento para o campo privadodatePublished
. O métodoGetPublicationDate
retorna a cadeia de caracteres "NYP" se o sinalizadorpublished
forfalse
, e o valor do campodatePublished
fortrue
.Membros relacionados a direitos autorais
O método
Copyright
usa o nome do titular dos direitos autorais e o ano dos direitos como argumentos e os atribui às propriedadesCopyrightName
eCopyrightDate
.Uma substituição do método
ToString
Se um tipo não sobrescrever o método Object.ToString, ele retornará o nome totalmente qualificado do tipo, o que é pouco útil para diferenciar uma instância de outra. A
Publication
classe substitui Object.ToString para retornar o valor daTitle
propriedade.
A figura a seguir ilustra a relação entre sua classe base Publication
e sua classe implicitamente herdada Object .
A classe Book
A Book
classe representa um livro como um tipo especializado de publicação. O exemplo a seguir mostra o código-fonte da Book
classe.
using System;
public sealed class Book : Publication
{
public Book(string title, string author, string publisher) :
this(title, string.Empty, author, publisher)
{ }
public Book(string title, string isbn, string author, string publisher) : base(title, publisher, PublicationType.Book)
{
// isbn argument must be a 10- or 13-character numeric string without "-" characters.
// We could also determine whether the ISBN is valid by comparing its checksum digit
// with a computed checksum.
//
if (!string.IsNullOrEmpty(isbn))
{
// Determine if ISBN length is correct.
if (!(isbn.Length == 10 | isbn.Length == 13))
throw new ArgumentException("The ISBN must be a 10- or 13-character numeric string.");
if (!ulong.TryParse(isbn, out _))
throw new ArgumentException("The ISBN can consist of numeric characters only.");
}
ISBN = isbn;
Author = author;
}
public string ISBN { get; }
public string Author { get; }
public decimal Price { get; private set; }
// A three-digit ISO currency symbol.
public string? Currency { get; private set; }
// Returns the old price, and sets a new price.
public decimal SetPrice(decimal price, string currency)
{
if (price < 0)
throw new ArgumentOutOfRangeException(nameof(price), "The price cannot be negative.");
decimal oldValue = Price;
Price = price;
if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-character string.");
Currency = currency;
return oldValue;
}
public override bool Equals(object? obj)
{
if (obj is not Book book)
return false;
else
return ISBN == book.ISBN;
}
public override int GetHashCode() => ISBN.GetHashCode();
public override string ToString() => $"{(string.IsNullOrEmpty(Author) ? "" : Author + ", ")}{Title}";
}
Além dos membros herdados de Publication
, a classe Book
define os seguintes membros exclusivos e substituições de membro:
Dois construtores
Os dois
Book
construtores compartilham três parâmetros comuns. Dois, título e editor, correspondem aos parâmetros doPublication
construtor. O terceiro é author, que é armazenado em uma propriedade públicaAuthor
imutável. Um construtor inclui um parâmetro isbn, que é armazenado na propriedade automáticaISBN
.O primeiro construtor usa essa palavra-chave para chamar o outro construtor. O encadeamento do construtor é um padrão comum na definição de construtores. Construtores com menos parâmetros fornecem valores padrão ao chamar o construtor com o maior número de parâmetros.
O segundo construtor usa a palavra-chave base para passar o título e o nome do editor para o construtor de classe base. Se você não fizer uma chamada explícita para um construtor de classe base no código-fonte, o compilador C# fornecerá automaticamente uma chamada para o construtor padrão ou sem parâmetros da classe base.
Uma propriedade de leitura somente
ISBN
, que retorna o ISBN do objetoBook
, um número exclusivo de 10 ou 13 dígitos. O ISBN é fornecido como um argumento para um dosBook
construtores. O ISBN é armazenado em um campo de backup privado, que é gerado automaticamente pelo compilador.Uma propriedade
Author
somente leitura. O nome do autor é fornecido como um argumento para os dois construtoresBook
e é armazenado na propriedade.Duas propriedades somente leitura relacionadas ao preço,
Price
eCurrency
. Seus valores são fornecidos como argumentos em umaSetPrice
chamada de método. ACurrency
propriedade é o símbolo de moeda ISO de três dígitos (por exemplo, USD para o dólar americano). Os símbolos de moeda ISO podem ser recuperados da propriedade ISOCurrencySymbol. Ambas as propriedades são somente leitura externa, mas ambas podem ser definidas por código naBook
classe.Um
SetPrice
método, que define os valores das propriedadesPrice
eCurrency
. Esses valores são retornados por essas mesmas propriedades.Substitui o método
ToString
(herdado dePublication
) e os métodos Object.Equals(Object) e GetHashCode (herdados de Object).A menos que seja substituído, o Object.Equals(Object) método testa a igualdade de referência. Ou seja, duas variáveis de objeto são consideradas iguais se se referirem ao mesmo objeto. Na classe
Book
, por outro lado, dois objetosBook
deverão ser considerados iguais se possuírem o mesmo ISBN.Ao substituir o Object.Equals(Object) método, você também deve substituir o GetHashCode método, que retorna um valor que o runtime usa para armazenar itens em coleções de hash para recuperação eficiente. O código hash deve retornar um valor consistente com o teste de igualdade. Como você substituiu Object.Equals(Object) para retornar
true
, se as propriedades de ISBN de dois objetosBook
forem iguais, retorne o código hash computado chamando o método GetHashCode da cadeia de caracteres retornada pela propriedadeISBN
.
A figura a seguir ilustra a relação entre a Book
classe e Publication
sua classe base.
Agora você pode instanciar um Book
objeto, invocar seus membros exclusivos e herdados e passá-lo como um argumento para um método que espera um parâmetro de tipo Publication
ou de tipo Book
, como mostra o exemplo a seguir.
public class ClassExample
{
public static void Main()
{
var book = new Book("The Tempest", "0971655819", "Shakespeare, William",
"Public Domain Press");
ShowPublicationInfo(book);
book.Publish(new DateTime(2016, 8, 18));
ShowPublicationInfo(book);
var book2 = new Book("The Tempest", "Classic Works Press", "Shakespeare, William");
Console.Write($"{book.Title} and {book2.Title} are the same publication: " +
$"{((Publication)book).Equals(book2)}");
}
public static void ShowPublicationInfo(Publication pub)
{
string pubDate = pub.GetPublicationDate();
Console.WriteLine($"{pub.Title}, " +
$"{(pubDate == "NYP" ? "Not Yet Published" : "published on " + pubDate):d} by {pub.Publisher}");
}
}
// The example displays the following output:
// The Tempest, Not Yet Published by Public Domain Press
// The Tempest, published on 8/18/2016 by Public Domain Press
// The Tempest and The Tempest are the same publication: False
Criando classes base abstratas e suas classes derivadas
No exemplo anterior, você definiu uma classe base que forneceu uma implementação para vários métodos para permitir que classes derivadas compartilhassem código. Em muitos casos, no entanto, não se espera que a classe base forneça uma implementação. Em vez disso, a classe base é uma classe abstrata que declara métodos abstratos; ele serve como um modelo que define os membros que cada classe derivada deve implementar. Normalmente, em uma classe base abstrata, a implementação de cada tipo derivado é exclusiva para esse tipo. Você marcou a classe com a palavra-chave abstrata porque não fazia sentido instanciar um Publication
objeto, embora a classe tenha fornecido implementações de funcionalidade comuns às publicações.
Por exemplo, cada forma geométrica bidimensional fechada inclui duas propriedades: área, a extensão interna da forma; e perímetro, ou a distância ao longo das bordas da forma. A maneira como essas propriedades são calculadas, no entanto, depende completamente da forma específica. A fórmula para calcular o perímetro (ou circunferência) de um círculo, por exemplo, é diferente da de um quadrado. A Shape
classe é uma abstract
classe com abstract
métodos. Isso indica que as classes derivadas compartilham a mesma funcionalidade, mas essas classes derivadas implementam essa funcionalidade de forma diferente.
O exemplo a seguir define uma classe base abstrata nomeada Shape
que define duas propriedades: Area
e Perimeter
. Além de marcar a classe com a palavra-chave abstrata , cada membro da instância também é marcado com a palavra-chave abstrata . Nesse caso, Shape
também substitui o Object.ToString método para retornar o nome do tipo, em vez de seu nome totalmente qualificado. E define dois membros estáticos, GetArea
e GetPerimeter
, que permitem que os chamadores recuperem facilmente a área e o perímetro de uma instância de qualquer classe derivada. Quando você passa uma instância de uma classe derivada para um desses métodos, o runtime chama a substituição do método da classe derivada.
public abstract class Shape
{
public abstract double Area { get; }
public abstract double Perimeter { get; }
public override string ToString() => GetType().Name;
public static double GetArea(Shape shape) => shape.Area;
public static double GetPerimeter(Shape shape) => shape.Perimeter;
}
Você pode então derivar algumas classes de Shape
que representam formas específicas. O exemplo a seguir define três classes, Square
Rectangle
e Circle
. Cada um usa uma fórmula exclusiva para essa forma específica para calcular a área e o perímetro. Algumas das classes derivadas também definem propriedades, como Rectangle.Diagonal
e Circle.Diameter
, que são exclusivas da forma que representam.
using System;
public class Square : Shape
{
public Square(double length)
{
Side = length;
}
public double Side { get; }
public override double Area => Math.Pow(Side, 2);
public override double Perimeter => Side * 4;
public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);
}
public class Rectangle : Shape
{
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}
public double Length { get; }
public double Width { get; }
public override double Area => Length * Width;
public override double Perimeter => 2 * Length + 2 * Width;
public bool IsSquare() => Length == Width;
public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) + Math.Pow(Width, 2)), 2);
}
public class Circle : Shape
{
public Circle(double radius)
{
Radius = radius;
}
public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2), 2);
public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2);
// Define a circumference, since it's the more familiar term.
public double Circumference => Perimeter;
public double Radius { get; }
public double Diameter => Radius * 2;
}
O exemplo a seguir usa objetos derivados de Shape
. Ele cria uma matriz de objetos derivados de Shape
e chama os métodos estáticos da classe Shape
, que retorna valores de propriedade Shape
. O runtime recupera os valores das propriedades substituídas dos tipos derivados. O exemplo também converte cada Shape
objeto na matriz em seu tipo derivado e, se a conversão for bem-sucedida, recuperará as propriedades dessa subclasse específica de Shape
.
using System;
public class Example
{
public static void Main()
{
Shape[] shapes = { new Rectangle(10, 12), new Square(5),
new Circle(3) };
foreach (Shape shape in shapes)
{
Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
$"perimeter, {Shape.GetPerimeter(shape)}");
if (shape is Rectangle rect)
{
Console.WriteLine($" Is Square: {rect.IsSquare()}, Diagonal: {rect.Diagonal}");
continue;
}
if (shape is Square sq)
{
Console.WriteLine($" Diagonal: {sq.Diagonal}");
continue;
}
}
}
}
// The example displays the following output:
// Rectangle: area, 120; perimeter, 44
// Is Square: False, Diagonal: 15.62
// Square: area, 25; perimeter, 20
// Diagonal: 7.07
// Circle: area, 28.27; perimeter, 18.85