Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este tutorial introduz você à 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 mais recente SDK do .NET
- Editor de código do Visual Studio
- O Kit de Desenvolvimento C#
Instruções de instalação
No Windows, este arquivo de configuração 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 receber um aviso de Controle de Conta de Utilizador (UAC) intermitente 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 .NET SDK e clique duas vezes para executá-lo. A página de download deteta sua plataforma e recomenda o instalador mais recente para sua plataforma.
- Baixe o instalador mais recente da página inicial do Visual Studio Code e clique duas vezes para executá-lo. Essa página também deteta a sua plataforma e o link deve estar correto para o seu sistema.
- Clique no botão "Instalar" na página de extensão C# DevKit. Isso abre o código do Visual Studio e pergunta se você deseja instalar ou habilitar a extensão. Selecione "instalar".
Executando os exemplos
Para criar e executar os exemplos neste tutorial, use o utilitário dotnet da 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 .NET Core.
Copie e cole o código do exemplo no editor de códigos.
Digite o comando dotnet restore na linha de comando para carregar ou restaurar as dependências do projeto.
Não é necessário executá
dotnet restore
porque ele é executado implicitamente por todos os comandos que exigem uma restauração para ocorrer, comodotnet new
,dotnet build
,dotnet run
,dotnet test
,dotnet publish
edotnet pack
. Para desativar a restauração implícita, use a opção--no-restore
.O comando
dotnet restore
ainda é útil em determinados cenários em que a restauração explícita faz sentido, como compilações de integração contínua no de Serviços de DevOps do Azure ou em sistemas de compilação que precisam controlar explicitamente quando a restauração ocorre.Para obter informações sobre como gerenciar feeds NuGet, consulte a documentação
dotnet restore
.Digite o comando dotnet run para compilar e executar o exemplo.
Antecedentes: O que é herança?
Herança é um dos atributos fundamentais da programação orientada a objetos. Permite definir uma classe filha que reutiliza (herda), estende ou modifica o comportamento de uma classe mãe. A classe cujos membros são herdados é chamada de classe base . A classe que herda os membros da classe base é chamada de classe derivada .
C# e .NET suportam 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 o tipo 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 os seus próprios construtores.
Finalizadores, que são chamados pelo coletor de lixo do tempo de execução para destruir as instâncias de uma classe.
Enquanto todos os outros membros de uma classe base são herdados por classes derivadas, se eles são visíveis ou não depende de sua acessibilidade. A acessibilidade de um membro afeta sua visibilidade para classes derivadas da seguinte maneira:
membros do Private são visíveis apenas em classes derivadas aninhadas em sua classe base. Caso contrário, eles não são visíveis em classes derivadas. No exemplo a seguir,
A.B
é uma classe aninhada que deriva deA
eC
deriva deA
. O campoA._value
privado é visível em A.B. No entanto, se você remover os comentários do métodoC.GetValue
e tentar compilar o exemplo, ele produzirá o erro de 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
Membros protegidos são visíveis apenas em classes derivadas.
Membros internos são visíveis apenas em classes derivadas que estão no mesmo assembly que a classe base. Eles não são visíveis em classes derivadas localizadas em um assembly diferente da classe base.
Membros do Public são visíveis em classes derivadas e fazem parte da interface pública da classe derivada. Os membros públicos herdados podem ser chamados como se estivessem definidos na classe derivada. No exemplo a seguir, a classe
A
define um método chamadoMethod1
e a classeB
herda da classeA
. O exemplo então 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(); } }
As classes derivadas também podem substituir membros herdados fornecendo uma implementação alternativa. Para poder substituir um membro, o membro da 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 sobrepor um membro não virtual, como no exemplo a seguir, gera o erro CS0506 do compilador: "<membro> não pode sobrepor o membro herdado <membro> porque não está marcado como virtual, abstract ou override."
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 os substituam. Tentar compilar o exemplo a seguir gera erro de compilador CS0534, "<classe> não implementa membro abstrato herdado <membro>", porque a classe B
não fornece nenhuma 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 aplica-se apenas a classes e interfaces. Outras categorias de tipo (structs, delegates e enums) não suportam herança. Devido a essas regras, tentar compilar código como o exemplo a seguir produz erro de compilador CS0527: "Tipo 'ValueType' na lista de interface não é uma interface." A mensagem de erro indica que, embora você possa definir as interfaces que um struct implementa, a herança não é suportada.
public struct ValueStructure : ValueType // Generates CS0527.
{
}
Herança implícita
Além de quaisquer tipos que eles possam herdar através de herança única, todos os tipos no sistema de tipos .NET herdam implicitamente de Object ou um tipo derivado dele. A funcionalidade comum do 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 a 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 tipo SimpleClass
. Embora você não tenha definido nenhum membro em sua classe SimpleClass
, a saída do exemplo indica que, na verdade, ela tem nove membros. Um desses membros é um construtor sem parâmetros (ou padrão) que é fornecido automaticamente para o tipo SimpleClass
pelo compilador C#. Os oito restantes são membros de Object, o tipo do qual, em última instância, todas as classes e interfaces no 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 classe Object torna esses métodos disponíveis para a classe SimpleClass
:
O método
ToString
público, que converte um objetoSimpleClass
em sua representação de cadeia de caracteres, retorna o nome de tipo totalmente qualificado. Nesse caso, o métodoToString
retorna a cadeia de caracteres "SimpleClass".Três métodos que testam a igualdade de dois objetos: o método público de instância
Equals(Object)
, o método público estáticoEquals(Object, Object)
e o método público estáticoReferenceEquals(Object, Object)
. Por padrão, esses métodos testam a igualdade de referência; ou seja, para serem iguais, duas variáveis de objeto devem referir-se ao mesmo objeto.O método
GetHashCode
público, que calcula um valor que permite que uma instância do tipo seja usada em coleções com hash.O método public
GetType
, que retorna um objeto Type que representa o tipoSimpleClass
.O método Finalize protegido, que é projetado para liberar recursos não gerenciados antes que a memória de um objeto seja recuperada pelo coletor de lixo.
O método MemberwiseClone, protegido, que cria um clone superficial do objeto atual.
Devido à herança implícita, você pode chamar qualquer membro herdado de um objeto SimpleClass
como se fosse realmente um membro definido na classe SimpleClass
. Por exemplo, o exemplo a seguir chama o método SimpleClass.ToString
, 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 de base torna disponível um conjunto variado de membros por meio de herança para tipos que são implicitamente derivados.
Categoria de tipo | Herda implicitamente de |
---|---|
classe | Object |
estrutura | ValueType, Object |
enum | Enum, ValueType, Object |
delegado | MulticastDelegate, Delegate, Object |
Herança e uma relação "é um"
Normalmente, a herança é usada para expressar uma relação "é um" entre uma classe base e uma ou mais classes derivadas, onde as classes derivadas são versões especializadas da classe base; a classe derivada é um tipo da classe base. Por exemplo, a classe Publication
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 de interface seja frequentemente apresentada como uma solução alternativa para herança única ou como uma forma de usar herança com estruturas, destina-se a expressar uma relação diferente (uma relação "pode fazer") entre uma interface e o tipo que a implementa, em vez de uma relação de herança. Uma interface define um subconjunto de funcionalidades (como a capacidade de testar a igualdade, comparar ou classificar objetos ou oferecer suporte à análise e formatação sensíveis à cultura) que a interface disponibiliza para seus tipos de implementação.
Observe que "is a" também expressa a relação entre um tipo e uma instanciação específica desse tipo. No exemplo a seguir, Automobile
é uma classe que tem três propriedades somente leitura exclusivas: Make
, o fabricante do automóvel; Model
, o tipo de automóvel; e Year
, o seu ano de fabrico. Sua classe Automobile
também tem um construtor cujos argumentos são atribuídos aos valores de propriedade e substitui o método Object.ToString para produzir uma cadeia de caracteres que identifica exclusivamente a instância Automobile
em vez da classe Automobile
.
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}";
}
Neste caso, você não deve confiar na herança para representar marcas e modelos de carros específicos. Por exemplo, você não precisa definir um tipo de Packard
para representar automóveis fabricados pela Packard Motor Car Company. Em vez disso, você pode representá-los criando um objeto Automobile
com os valores apropriados passados para seu construtor de classe, como faz o exemplo a seguir.
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
Uma relação is-a baseada em herança é melhor aplicada a uma classe base e a classes derivadas que adicionam membros adicionais à classe base ou que exigem funcionalidade adicional não presente na classe base.
Projetando a classe base e as classes derivadas
Vejamos 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, uma revista, um artigo, etc. Você também definirá uma classe Book
que deriva de Publication
. Você pode facilmente estender o exemplo para definir outras classes derivadas, como Magazine
, Journal
, Newspaper
e Article
.
A classe base Publicação
Ao projetar sua classe Publication
, você precisa tomar várias decisões de design:
Quais membros incluir em sua classe base
Publication
e se os membrosPublication
fornecem implementações de método ou sePublication
é uma classe base abstrata que serve como modelo para suas classes derivadas.Nesse caso, a classe
Publication
fornecerá implementações de método. A seção Designing abstract base classes and their derived classes contém um exemplo que usa uma classe base abstrata para definir os métodos que as classes derivadas devem substituir. As classes derivadas são livres para fornecer qualquer implementação que seja 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, deve-se adicionar membros a
Publication
se for provável que o seu código seja partilhado por alguns ou a maioria dos tipos dePublication
especializados. Se você não conseguir fornecer implementações de classe base de forma eficiente, acabará tendo que fornecer implementações de membro em grande parte 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 fonte potencial de bugs.Para maximizar a reutilização de código e criar uma hierarquia de herança lógica e intuitiva, você quer ter certeza de incluir na classe
Publication
apenas os dados e a funcionalidade comuns a todas ou à maioria das publicações. Em seguida, as classes derivadas implementam membros que são exclusivos para os tipos específicos de publicação que representam.Até onde estender sua hierarquia de classe. Você 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
poderia ser uma classe base dePeriodical
, que por sua vez é uma classe base deMagazine
,Journal
eNewspaper
.Para seu exemplo, você usará a pequena hierarquia de uma classe
Publication
e uma única classe derivada,Book
. Você pode 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ê deve aplicar a palavra-chave abstrato à classe. Caso contrário, sua classe
Publication
pode ser instanciada chamando seu construtor de classe. Se for feita uma tentativa de instanciar uma classe marcada com a palavra-chaveabstract
por uma chamada direta para seu construtor de classe, o compilador C# gerará o erro CS0144, "Não é possível criar uma instância da classe abstrata ou interface." Se for feita uma tentativa de instanciar a classe usando reflexão, o método de reflexão lançará um MemberAccessException.Por padrão, uma classe base pode ser instanciada chamando seu construtor de classe. Não é necessário definir explicitamente um construtor de classe. Se um não estiver presente no código-fonte da classe base, o compilador C# fornece automaticamente um construtor padrão (sem parâmetros).
Para o seu exemplo, você marcará a classe
Publication
como abstrato para que ela não possa ser instanciada. Uma classeabstract
sem nenhum métodoabstract
indica que essa classe representa um conceito abstrato que é compartilhado entre várias classes concretas (como umBook
,Journal
).Se as classes derivadas devem herdar a implementação da classe base de membros específicos, se eles têm a opção de substituir a implementação da classe base ou se devem fornecer uma implementação. Você usa a palavra-chave abstrato para obrigar classes derivadas a fornecerem 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 classe
Publication
não tem nenhum métodoabstract
, 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 ser usada como classe base para outras classes. Tentando derivar de uma classe selada gerou erro de compilador CS0509, "não pode derivar de tipo selado <typeName>."
Para o seu exemplo, você marcará sua classe derivada como
sealed
.
O exemplo a seguir mostra o código-fonte para a classe Publication
, bem como uma enumeração PublicationType
que é retornada pela propriedade Publication.PublicationType
. Além dos membros que herda de Object, a classe Publication
define os seguintes membros únicos e substituições de membros:
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 classe
Publication
é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 classe derivados, como mostra o código-fonte da classe
Book
.Duas propriedades relacionadas à publicação
Title
é uma propriedade String de somente leitura e cujo valor é determinado ao chamar o construtorPublication
.Pages
é uma propriedade de leitura-escrita Int32 que indica a quantidade total de páginas que tem a publicação. O valor é armazenado em um campo privado chamadototalPages
. Deve ser um número positivo ou um ArgumentOutOfRangeException é lançado.Membros relacionados com o editor
Duas propriedades somente leitura,
Publisher
eType
. Os valores são originalmente fornecidos através da chamada para o construtor da classePublication
.Membros relacionados com a publicação
Dois métodos,
Publish
eGetPublicationDate
, definem e retornam a data de publicação. O métodoPublish
define um sinalizador privadopublished
paratrue
quando é chamado e atribui a data passada como argumento ao campo privadodatePublished
. O métodoGetPublicationDate
retorna a cadeia de caracteres "NYP" se o sinalizadorpublished
forfalse
, e o valor do campodatePublished
se fortrue
.Membros relacionados com direitos de autor
O método
Copyright
toma o nome do detentor dos direitos autorais e o ano dos direitos autorais como argumentos e os atribui às propriedadesCopyrightName
eCopyrightDate
.Uma substituição do método
ToString
Se um tipo não substituir o método Object.ToString, ele retornará o nome totalmente qualificado do tipo, o que é de pouca utilidade para diferenciar uma instância de outra. A classe
Publication
substitui Object.ToString para retornar o valor da propriedadeTitle
.
A figura a seguir ilustra a relação entre sua classe base Publication
e sua classe Object implicitamente herdada.
A classe Book
A classe Book
representa um livro como um tipo especializado de publicação. O exemplo a seguir mostra o código-fonte da classe Book
.
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 que herda de Publication
, a classe Book
define os seguintes membros únicos e substituições de membros:
Dois construtores
Os dois construtores
Book
compartilham três parâmetros comuns. Dois, título e editor, correspondem a parâmetros do construtorPublication
. O terceiro é autor, que é armazenado em uma propriedade pública imutávelAuthor
. Um construtor inclui um parâmetro isbn, que é armazenado naISBN
auto-propriedade.O primeiro construtor usa a palavra-chave this para chamar o outro construtor. Encadeamento de construtores é 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 da classe base. Se você não fizer uma chamada explícita para um construtor de classe base em seu código-fonte, o compilador C# fornecerá automaticamente uma chamada para o construtor padrão ou sem parâmetros da classe base.
Uma propriedade
ISBN
de leitura apenas, que retorna o Número Internacional Normalizado para Livros do objetoBook
, um número único de 10 ou 13 dígitos. O ISBN é fornecido como um argumento para um dos construtoresBook
. O ISBN é armazenado em um campo de suporte privado, que é gerado automaticamente pelo compilador.Propriedade somente leitura
Author
. O nome do autor é fornecido como um argumento tanto para ambos os construtoresBook
como é armazenado no atributo.Duas propriedades de leitura apenas relacionadas a preços,
Price
eCurrency
. Seus valores são fornecidos como argumentos em uma chamada de métodoSetPrice
. A propriedadeCurrency
é 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 de leitura apenas externamente, mas podem ser definidas por código na classeBook
.Um método
SetPrice
, 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 método Object.Equals(Object) 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
devem ser iguais se tiverem o mesmo ISBN.Ao substituir o método Object.Equals(Object), você também deve substituir o método GetHashCode, que retorna um valor que o tempo de execução usa para armazenar itens em coleções com 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 ISBN de dois objetosBook
forem iguais, você retorna o código hash calculado ao chamar o método GetHashCode da cadeia de caracteres retornada pela propriedadeISBN
.
A figura a seguir ilustra a relação entre a classe Book
e Publication
, sua classe base.
Agora você pode instanciar um objeto Book
, invocar seus membros exclusivos e herdados e passá-lo como um argumento para um método que espera um parâmetro do tipo Publication
ou do 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
Projetando 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 compartilhem 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 abstract porque não fazia sentido instanciar um objeto Publication
, embora a classe fornecesse implementações de funcionalidade comuns a 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 forma 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 classe Shape
é uma classe abstract
com métodos abstract
. 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 chamada Shape
que define duas propriedades: Area
e Perimeter
. Além de marcar a classe com a palavra-chave abstrato, cada membro da instância também é marcado com a palavra-chave abstrato. Nesse caso, Shape
também substitui o método Object.ToString 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 qualquer um desses métodos, o tempo de execução chama a substituição de 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;
}
Em seguida, você pode 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 para a 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 instancia uma matriz de objetos derivados de Shape
e chama os métodos estáticos da classe Shape
, que encapsulam os valores das propriedades de retorno Shape
. O tempo de execução recupera valores das propriedades sobrepostas dos tipos derivados. O exemplo também converte cada objeto Shape
na matriz para seu tipo derivado e, se a conversão for bem-sucedida, recupera 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