Herencia en C# y .NETInheritance in C# and .NET

Este tutorial es una introducción a la herencia en C#.This tutorial introduces you to inheritance in C#. La herencia es una característica de los lenguajes de programación orientados a objetos que permite definir una clase base, que proporciona funcionalidad específica (datos y comportamiento), así como clases derivadas, que heredan o invalidan esa funcionalidad.Inheritance is a feature of object-oriented programming languages that allows you to define a base class that provides specific functionality (data and behavior) and to define derived classes that either inherit or override that functionality.

Requisitos previosPrerequisites

En este tutorial se supone que ha instalado el SDK de .NET Core.This tutorial assumes that you've installed the .NET Core SDK. Visite la página de descargas de .NET Core para descargarlo.Visit the .NET Core Downloads page to download it. También necesitará un editor de código.You also need a code editor. En este tutorial se usa Visual Studio Code, aunque puede elegir el que prefiera.This tutorial uses Visual Studio Code, although you can use any code editor of your choice.

Ejecución de los ejemplosRunning the examples

Para crear y ejecutar los ejemplos de este tutorial, use la utilidad dotnet desde la línea de comandos.To create and run the examples in this tutorial, you use the dotnet utility from the command line. Siga estos pasos para cada ejemplo:Follow these steps for each example:

  1. Cree un directorio para almacenar el ejemplo.Create a directory to store the example.
  2. Escriba el comando dotnet new console en el símbolo del sistema para crear un nuevo proyecto de .NET Core.Enter the dotnet new console command at a command prompt to create a new .NET Core project.
  3. Copie y pegue el código del ejemplo en el editor de código.Copy and paste the code from the example into your code editor.
  4. Escriba el comando dotnet restore desde la línea de comandos para cargar o restaurar las dependencias del proyecto.Enter the dotnet restore command from the command line to load or restore the project's dependencies.

Nota

A partir del SDK de .NET Core 2.0, no es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que requieren que se produzca una restauración, como dotnet new, dotnet build y dotnet run.Starting with .NET Core 2.0 SDK, you don't have to run dotnet restore because it's run implicitly by all commands that require a restore to occur, such as dotnet new, dotnet build and dotnet run. Sigue siendo un comando válido en algunos escenarios donde tiene sentido realizar una restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los sistemas de compilación que necesitan controlar explícitamente la hora a la que se produce la restauración.It's still a valid command in certain scenarios where doing an explicit restore makes sense, such as continuous integration builds in Azure DevOps Services or in build systems that need to explicitly control the time at which the restore occurs.

  1. Escriba el comando dotnet run para compilar y ejecutar el ejemplo.Enter the dotnet run command to compile and execute the example.

Información previa: ¿Qué es la herencia?Background: What is inheritance?

La herencia es uno de los atributos fundamentales de la programación orientada a objetos.Inheritance is one of the fundamental attributes of object-oriented programming. Permite definir una clase secundaria que reutiliza (hereda), amplía o modifica el comportamiento de una clase primaria.It allows you to define a child class that reuses (inherits), extends, or modifies the behavior of a parent class. La clase cuyos miembros son heredados se conoce como clase base.The class whose members are inherited is called the base class. La clase que hereda los miembros de la clase base se conoce como clase derivada.The class that inherits the members of the base class is called the derived class.

C# y .NET solo admiten herencia única.C# and .NET support single inheritance only. Es decir, una clase solo puede heredar de una clase única.That is, a class can only inherit from a single class. Sin embargo, la herencia es transitiva, lo que le permite definir una jerarquía de herencia para un conjunto de tipos.However, inheritance is transitive, which allows you to define an inheritance hierarchy for a set of types. En otras palabras, el tipo D puede heredar del tipo C, que hereda del tipo B, que hereda del tipo de clase base A.In other words, type D can inherit from type C, which inherits from type B, which inherits from the base class type A. Dado que la herencia es transitiva, los miembros de tipo A están disponibles para el tipo D.Because inheritance is transitive, the members of type A are available to type D.

No todos los miembros de una clase base los heredan las clases derivadas.Not all members of a base class are inherited by derived classes. Los siguientes miembros no se heredan:The following members are not inherited:

  • Constructores estáticos, que inicializan los datos estáticos de una clase.Static constructors, which initialize the static data of a class.

  • Constructores de instancias, a los que se llama para crear una nueva instancia de la clase.Instance constructors, which you call to create a new instance of the class. Cada clase debe definir sus propios constructores.Each class must define its own constructors.

  • Finalizadores, llamados por el recolector de elementos no utilizados en tiempo de ejecución para destruir instancias de una clase.Finalizers, which are called by the runtime's garbage collector to destroy instances of a class.

Si bien las clases derivadas heredan todos los demás miembros de una clase base, que dichos miembros estén o no visibles depende de su accesibilidad.While all other members of a base class are inherited by derived classes, whether they are visible or not depends on their accessibility. La accesibilidad del miembro afecta a su visibilidad en las clases derivadas del modo siguiente:A member's accessibility affects its visibility for derived classes as follows:

  • Los miembros privados solo son visible en las clases derivadas que están anidadas en su clase base.Private members are visible only in derived classes that are nested in their base class. De lo contrario, no son visibles en las clases derivadas.Otherwise, they are not visible in derived classes. En el ejemplo siguiente, A.B es una clase anidada que se deriva de A, y C se deriva de A.In the following example, A.B is a nested class that derives from A, and C derives from A. El campo A.value privado es visible en A.B.The private A.value field is visible in A.B. Sin embargo, si quita los comentarios del método C.GetValue e intenta compilar el ejemplo, se produce el error del compilador CS0122: ""A.value" no es accesible debido a su nivel de protección".However, if you remove the comments from the C.GetValue method and attempt to compile the example, it produces compiler error CS0122: "'A.value' is inaccessible due to its protection level."

    using System;
    
    public class A 
    {
       private int value = 10;
    
       public class B : A
       {
           public int GetValue()
           {
               return this.value;
           }     
       }
    }
    
    public class C : A
    {
    //    public int GetValue()
    //    {
    //        return this.value;
    //    }
    }
    
    public class Example
    {
        public static void Main(string[] args)
        {
            var b = new A.B();
            Console.WriteLine(b.GetValue());
        }
    }
    // The example displays the following output:
    //       10
    
  • Los miembros protegidos solo son visibles en las clases derivadas.Protected members are visible only in derived classes.

  • Los miembros internos solo son visibles en las clases derivadas que se encuentran en el mismo ensamblado que la clase base.Internal members are visible only in derived classes that are located in the same assembly as the base class. No son visibles en las clases derivadas ubicadas en un ensamblado diferente al de la clase base.They are not visible in derived classes located in a different assembly from the base class.

  • Los miembros públicos son visibles en las clases derivadas y forman parte de la interfaz pública de dichas clases.Public members are visible in derived classes and are part of the derived class' public interface. Los miembros públicos heredados se pueden llamar como si se definieran en la clase derivada.Public inherited members can be called just as if they are defined in the derived class. En el ejemplo siguiente, la clase A define un método denominado Method1 y la clase B hereda de la clase A.In the following example, class A defines a method named Method1, and class B inherits from class A. El ejemplo llama a Method1 como si fuera un método de instancia en B.The example then calls Method1 as if it were an instance method on B.

public class A
{
    public void Method1()
    {
        // Method implementation.
    }
}

public class B : A
{ }


public class Example
{
    public static void Main()
    {
        B b = new B();
        b.Method1();
    }
}

Las clases derivadas pueden también invalidar los miembros heredados al proporcionar una implementación alternativa.Derived classes can also override inherited members by providing an alternate implementation. Para poder invalidar un miembro, el miembro de la clase base debe marcarse con la palabra clave virtual.In order to be able to override a member, the member in the base class must be marked with the virtual keyword. De forma predeterminada, los miembros de clase base no están marcados con virtual y no se pueden invalidar.By default, base class members are not marked as virtual and cannot be overridden. Al intentar invalidar un miembro no virtual, como en el ejemplo siguiente, se genera el error de compilador CS0506: "<miembro> no puede invalidar el miembro heredado <miembro> porque no está marcado como virtual, abstract ni override.Attempting to override a non-virtual member, as the following example does, generates compiler error CS0506: "<member> cannot override inherited member <member> because it is not marked virtual, abstract, or override.

public class A
{
    public void Method1()
    {
        // Do something.
    }
}

public class B : A
{
    public override void Method1() // Generates CS0506.
    {
        // Do something else.
    }
}

En algunos casos, una clase derivada debe invalidar la implementación de la clase base.In some cases, a derived class must override the base class implementation. Los miembros de clase base marcados con la palabra clave abstract requieren que las clases derivadas los invaliden.Base class members marked with the abstract keyword require that derived classes override them. Al intentar compilar el ejemplo siguiente, se genera el error de compilador CS0534, "<class> no implementa el miembro abstracto heredado <member>", porque la clase B no proporciona ninguna implementación para A.Method1.Attempting to compile the following example generates compiler error CS0534, "<class> does not implement inherited abstract member <member>", because class B provides no implementation for A.Method1.

public abstract class A
{
    public abstract void Method1();
}

public class B : A // Generates CS0534.
{
    public void Method3()
    {
        // Do something.
    }
}

La herencia solo se aplica a clases e interfaces.Inheritance applies only to classes and interfaces. Other type categories (structs, delegates, and enums) do not support inheritance.Other type categories (structs, delegates, and enums) do not support inheritance. Debido a estas reglas, al intentar compilar código como el siguiente se genera el error de compilador CS0527: "El tipo 'ValueType' de la lista de interfaces no es una interfaz".Because of these rules, attempting to compile code like the following example produces compiler error CS0527: "Type 'ValueType' in interface list is not an interface." El mensaje de error indica que, aunque se pueden definir las interfaces que implementa un struct, no se admite la herencia.The error message indicates that, although you can define the interfaces that a struct implements, inheritance is not supported.

using System;

public struct ValueStructure : ValueType // Generates CS0527.
{
}

Herencia implícitaImplicit inheritance

Aparte de los tipos de los que puedan heredar mediante herencia única, todos los tipos del sistema de tipos .NET heredan implícitamente de Object o de un tipo derivado de este.Besides any types that they may inherit from through single inheritance, all types in the .NET type system implicitly inherit from Object or a type derived from it. La funcionalidad común de Object está disponible para cualquier tipo.The common functionality of Object is available to any type.

Para ver lo que significa la herencia implícita, vamos a definir una nueva clase, SimpleClass, que es simplemente una definición de clase vacía:To see what implicit inheritance means, let's define a new class, SimpleClass, that is simply an empty class definition:

public class SimpleClass
{ }

Después, se puede usar la reflexión (que permite inspeccionar los metadatos de un tipo para obtener información sobre ese tipo) con el fin de obtener una lista de los miembros que pertenecen al tipo SimpleClass.You can then use reflection (which lets you inspect a type's metadata to get information about that type) to get a list of the members that belong to the SimpleClass type. Aunque no se ha definido ningún miembro en la clase SimpleClass, la salida del ejemplo indica que en realidad tiene nueve miembros.Although you haven't defined any members in your SimpleClass class, output from the example indicates that it actually has nine members. Uno de ellos es un constructor sin parámetros (o predeterminado) que el compilador de C# proporciona de manera automática para el tipo SimpleClass.One of these members is a parameterless (or default) constructor that is automatically supplied for the SimpleClass type by the C# compiler. Los ocho restantes son miembros de Object, el tipo del que heredan implícitamente a la larga todas las clases e interfaces del sistema de tipo .NET.The remaining eight are members of Object, the type from which all classes and interfaces in the .NET type system ultimately implicitly inherit.

using System;
using System.Reflection;

public class Example
{
    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 (var 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";
            }
            var 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

La herencia implícita desde la clase Object permite que estos métodos estén disponibles para la clase SimpleClass:Implicit inheritance from the Object class makes these methods available to the SimpleClass class:

  • El método público ToString, que convierte un objeto SimpleClass en su representación de cadena, devuelve el nombre de tipo completo.The public ToString method, which converts a SimpleClass object to its string representation, returns the fully qualified type name. En este caso, el método ToString devuelve la cadena "SimpleClass".In this case, the ToString method returns the string "SimpleClass".

  • Tres métodos de prueba de igualdad de dos objetos: el método de instancia pública Equals(Object), el método público estático Equals(Object, Object) y el método público estático ReferenceEquals(Object, Object).Three methods that test for equality of two objects: the public instance Equals(Object) method, the public static Equals(Object, Object) method, and the public static ReferenceEquals(Object, Object) method. De forma predeterminada, estos métodos prueban la igualdad de referencia; es decir, para que sean iguales, dos variables de objeto deben hacer referencia al mismo objeto.By default, these methods test for reference equality; that is, to be equal, two object variables must refer to the same object.

  • El método público GetHashCode, que calcula un valor que permite que una instancia del tipo se use en colecciones con hash.The public GetHashCode method, which computes a value that allows an instance of the type to be used in hashed collections.

  • El método público GetType, que devuelve un objeto Type que representa el tipo SimpleClass.The public GetType method, which returns a Type object that represents the SimpleClass type.

  • El método protegido Finalize, que está diseñado para liberar recursos no administrados antes de que el recolector de elementos no utilizados reclame la memoria de un objeto.The protected Finalize method, which is designed to release unmanaged resources before an object's memory is reclaimed by the garbage collector.

  • El método protegido MemberwiseClone, que crea un clon superficial del objeto actual.The protected MemberwiseClone method, which creates a shallow clone of the current object.

Debido a la herencia implícita, se puede llamar a cualquier miembro heredado de un objeto SimpleClass como si realmente fuera un miembro definido en la clase SimpleClass.Because of implicit inheritance, you can call any inherited member from a SimpleClass object just as if it was actually a member defined in the SimpleClass class. Así, en el ejemplo siguiente se llama al método SimpleClass.ToString, que SimpleClass hereda de Object.For instance, the following example calls the SimpleClass.ToString method, which SimpleClass inherits from Object.

using System;

public class SimpleClass
{}

public class Example
{
    public static void Main()
    {
        SimpleClass sc = new SimpleClass();
        Console.WriteLine(sc.ToString());
    }
}
// The example displays the following output:
//        SimpleClass

En la tabla siguiente se enumeran las categorías de tipos que se pueden crear en C# y los tipos de los que heredan implícitamente.The following table lists the categories of types that you can create in C# and the types from which they implicitly inherit. Cada tipo base constituye un conjunto diferente de miembros disponible mediante herencia para los tipos derivados de forma implícita.Each base type makes a different set of members available through inheritance to implicitly derived types.

Categoría de tipoType category Hereda implícitamente deImplicitly inherits from
claseclass Object
structstruct ValueType, ObjectValueType, Object
enumenum Enum, ValueType, ObjectEnum, ValueType, Object
delegadodelegate MulticastDelegate, Delegate, ObjectMulticastDelegate, Delegate, Object

Herencia y una relación "is a"Inheritance and an "is a" relationship

Normalmente, la herencia se usa para expresar una relación "is a" entre una clase base y una o varias clases derivadas, donde las clases derivadas son versiones especializadas de la clase base; la clase derivada es un tipo de la clase base.Ordinarily, inheritance is used to express an "is a" relationship between a base class and one or more derived classes, where the derived classes are specialized versions of the base class; the derived class is a type of the base class. Por ejemplo, la clase Publication representa una publicación de cualquier tipo y las clases Book y Magazine representan tipos específicos de publicaciones.For example, the Publication class represents a publication of any kind, and the Book and Magazine classes represent specific types of publications.

Nota

Una clase o struct puede implementar una o varias interfaces.A class or struct can implement one or more interfaces. Aunque a menudo la implementación se presenta como una solución alternativa para la herencia única o como una forma de usar la herencia con structs, su finalidad es expresar una relación diferente (una relación "can do") entre una interfaz y su tipo de implementación que la herencia.While interface implementation is often presented as a workaround for single inheritance or as a way of using inheritance with structs, it is intended to express a different relationship (a "can do" relationship) between an interface and its implementing type than inheritance. Una interfaz define un subconjunto de funcionalidad (por ejemplo, la posibilidad de probar la igualdad, comparar u ordenar objetos o de admitir análisis y formato con referencia cultural) que la interfaz pone a disposición de sus tipos de implementación.An interface defines a subset of functionality (such as the ability to test for equality, to compare or sort objects, or to support culture-sensitive parsing and formatting) that the interface makes available to its implementing types.

Tenga en cuenta que "is a" también expresa la relación entre un tipo y una instancia específica de ese tipo.Note that "is a" also expresses the relationship between a type and a specific instantiation of that type. En el ejemplo siguiente, Automobile es una clase que tiene tres propiedades de solo lectura exclusivas: Make, el fabricante del automóvil; Model, el tipo de automóvil; y Year, el año de fabricación.In the following example, Automobile is a class that has three unique read-only properties: Make, the manufacturer of the automobile; Model, the kind of automobile; and Year, its year of manufacture. La clase Automobile también tiene un constructor cuyos argumentos se asignan a los valores de propiedad, y reemplaza al método Object.ToString para crear una cadena que identifica de forma única la instancia de Automobile en lugar de la clase Automobile.Your Automobile class also has a constructor whose arguments are assigned to the property values, and it overrides the Object.ToString method to produce a string that uniquely identifies the Automobile instance rather than the Automobile class.

using System;

public class Automobile
{
    public Automobile(string make, string model, int year)
    {
        if (make == null)
           throw new ArgumentNullException("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("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}";
}

En este caso, no se debe basar en la herencia para representar marcas y modelos de coche específicos.In this case, you shouldn't rely on inheritance to represent specific car makes and models. Por ejemplo, no es necesario definir un tipo Packard para representar los automóviles fabricados por la empresa de automóviles Packard Motor.For example, you don't need to define a Packard type to represent automobiles manufactured by the Packard Motor Car Company. En su lugar, se pueden representar mediante la creación de un objeto Automobile con los valores adecuados que se pasan a su constructor de clase, como en el ejemplo siguiente.Instead, you can represent them by creating an Automobile object with the appropriate values passed to its class constructor, as the following example does.

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

Una relación "is a" basada en la herencia se aplica mejor a una clase base y a clases derivadas que agregan miembros adicionales a la clase base o que requieren funcionalidad adicional que no está presente en la clase base.An is-a relationship based on inheritance is best applied to a base class and to derived classes that add additional members to the base class or that require additional functionality not present in the base class.

Diseño de la clase base y las clases derivadasDesigning the base class and derived classes

Veamos el proceso de diseño de una clase base y sus clases derivadas.Let's look at the process of designing a base class and its derived classes. En esta sección, se definirá una clase base, Publication, que representa una publicación de cualquier tipo, como un libro, una revista, un periódico, un diario, un artículo, etc. También se definirá una clase Book que se deriva de Publication.In this section, you'll define a base class, Publication, which represents a publication of any kind, such as a book, a magazine, a newspaper, a journal, an article, etc. You'll also define a Book class that derives from Publication. El ejemplo se podría ampliar fácilmente para definir otras clases derivadas, como Magazine, Journal, Newspaper y Article.You could easily extend the example to define other derived classes, such as Magazine, Journal, Newspaper, and Article.

Clase base PublicationThe base Publication class

A la hora de diseñar la clase Publication, se deben tomar varias decisiones de diseño:In designing your Publication class, you need to make several design decisions:

  • Qué miembros se van a incluir en la clase Publication base, y si los miembros de Publication proporcionan implementaciones de método, o bien si Publication es una clase base abstracta que funciona como plantilla para sus clases derivadas.What members to include in your base Publication class, and whether the Publication members provide method implementations or whether Publication is an abstract base class that serves as a template for its derived classes.

    En este caso, la clase Publication proporcionará implementaciones de método.In this case, the Publication class will provide method implementations. La sección Diseño de clases base abstractas y sus clases derivadas contiene un ejemplo en el que se usa una clase base abstracta para definir los métodos que deben invalidar las clases derivadas.The Designing abstract base classes and their derived classes section contains an example that uses an abstract base class to define the methods that derived classes must override. Las clases derivadas pueden proporcionar cualquier implementación que sea adecuada para el tipo derivado.Derived classes are free to provide any implementation that is suitable for the derived type.

    La posibilidad de reutilizar el código (es decir, varias clases derivadas comparten la declaración y la implementación de los métodos de clase base y no tienen que invalidarlos) es una ventaja de las clases base no abstractas.The ability to reuse code (that is, multiple derived classes share the declaration and implementation of base class methods and do not need to override them) is an advantage of non-abstract base classes. Por tanto, se deben agregar miembros a Publication si es probable que algunos o la mayoría de los tipos Publication especializados compartan su código.Therefore, you should add members to Publication if their code is likely to be shared by some or most specialized Publication types. Si no puede proporcionar implementaciones de clase base de forma eficaz, acabará por tener que proporcionar implementaciones de miembros prácticamente idénticas en las clases derivadas en lugar de una única implementación en la clase base.If you fail to provide base class implementations efficiently, you'll end up having to provide largely identical member implementations in derived classes rather a single implementation in the base class. La necesidad de mantener código duplicado en varias ubicaciones es un origen potencial de errores.The need to maintain duplicated code in multiple locations is a potential source of bugs.

    Para maximizar la reutilización del código y crear una jerarquía de herencia lógica e intuitiva, asegúrese de incluir en la clase Publication solo los datos y la funcionalidad común a todas o a la mayoría de las publicaciones.Both to maximize code reuse and to create a logical and intuitive inheritance hierarchy, you want to be sure that you include in the Publication class only the data and functionality that is common to all or to most publications. Así, las clases derivadas implementan miembros que son únicos para una clase determinada de publicación que representan.Derived classes then implement members that are unique to the particular kinds of publication that they represent.

  • Hasta qué punto extender la jerarquía de clases.How far to extend your class hierarchy. ¿Quiere desarrollar una jerarquía de tres o más clases, en lugar de simplemente una clase base y una o más clases derivadas?Do you want to develop a hierarchy of three or more classes, rather than simply a base class and one or more derived classes? Por ejemplo, Publication podría ser una clase base de Periodical, que, a su vez, es una clase base de Magazine, Journal y Newspaper.For example, Publication could be a base class of Periodical, which in turn is a base class of Magazine, Journal and Newspaper.

    En el ejemplo, se usará la jerarquía pequeña de una clase Publication y una sola clase derivada, Book.For your example, you'll use the small hierarchy of a Publication class and a single derived class, Book. El ejemplo se podría extender fácilmente para crear una serie de clases adicionales que se derivan de Publication, como Magazine y Article.You could easily extend the example to create a number of additional classes that derive from Publication, such as Magazine and Article.

  • Si tiene sentido crear instancias de la clase base.Whether it makes sense to instantiate the base class. Si no, se debe aplicar la palabra clave abstract a la clase.If it does not, you should apply the abstract keyword to the class. De lo contrario, se puede crear una instancia de la clase Publication mediante una llamada a su constructor de clase.Otherwise, your Publication class can be instantiated by calling its class constructor. Si se intenta crear una instancia de una clase marcada con la palabra clave abstract mediante una llamada directa a su constructor de clase, el compilador de C# genera el error CS0144: "No se puede crear una instancia de la interfaz o clase abstracta".If an attempt is made to instantiate a class marked with the abstract keyword by a direct call to its class constructor, the C# compiler generates error CS0144, "Cannot create an instance of the abstract class or interface." Si se intenta crear una instancia de la clase mediante reflexión, el método de reflexión produce una excepción MemberAccessException.If an attempt is made to instantiate the class by using reflection, the reflection method throws a MemberAccessException.

    De forma predeterminada, se puede crear una instancia de una clase base mediante una llamada a su constructor de clase.By default, a base class can be instantiated by calling its class constructor. No es necesario definir un constructor de clase de forma explícita.You do not have to explicitly define a class constructor. Si uno no está presente en el código fuente de la clase base, el compilador de C# proporciona automáticamente un constructor (sin parámetros) de forma predeterminada.If one is not present in the base class' source code, the C# compiler automatically provides a default (parameterless) constructor.

    En el ejemplo, la clase Publication se marcará como abstract para que no se puedan crear instancias de ella.For your example, you'll mark the Publication class as abstract so that it cannot be instantiated. Una clase abstract sin ningún método abstract indica que representa un concepto abstracto que se comparte entre varias clases concretas (como un Book, Journal).An abstract class without any abstract methods indicates that this class represents an abstract concept that is shared among several concrete classes (like a Book, Journal).

  • Si las clases derivadas deben heredar la implementación de la clase base de determinados miembros, si tienen la opción de invalidar la implementación de la clase base, o bien si deben proporcionar una implementación.Whether derived classes must inherit the base class implementation of particular members, whether they have the option to override the base class implementation, or whether they must provide an implementation. La palabra clave abstract se usa para forzar que las clases derivadas proporcionen una implementación.You use the abstract keyword to force derived classes to provide an implementation. La palabra clave virtual se usa para permitir que las clases derivadas invaliden un método de clase base.You use the virtual keyword to allow derived classes to override a base class method. De forma predeterminada, no se pueden invalidar los métodos definidos en la clase base.By default, methods defined in the base class are not overridable.

La clase Publication no tiene ningún método abstract, pero la propia clase es abstract.The Publication class does not have any abstract methods, but the class itself is abstract.

  • Si una clase derivada representa la clase final en la jerarquía de herencia y no se puede usar ella misma como clase base para clases derivadas adicionales.Whether a derived class represents the final class in the inheritance hierarchy and cannot itself be used as a base class for additional derived classes. De forma predeterminada, cualquier clase puede servir como clase base.By default, any class can serve as a base class. Se puede aplicar la palabra clave sealed para indicar que una clase no puede servir como clase base para las clases adicionales.You can apply the sealed keyword to indicate that a class cannot serve as a base class for any additional classes. Al intentar derivar de una clase sellada, se genera el error de compilador CS0509: "No puede derivar del tipo sellado <nombreDeTipo>".Attempting to derive from a sealed class generated compiler error CS0509, "cannot derive from sealed type <typeName>".

    Para el ejemplo, la clase derivada se marcará como sealed.For your example, you'll mark your derived class as sealed.

En el ejemplo siguiente se muestra el código fuente para la clase Publication, así como una enumeración PublicationType que devuelve la propiedad Publication.PublicationType.The following example shows the source code for the Publication class, as well as a PublicationType enumeration that is returned by the Publication.PublicationType property. Además de los miembros que hereda de Object, la clase Publication define los siguientes miembros únicos e invalidaciones de miembros:In addition to the members that it inherits from Object, the Publication class defines the following unique members and member overrides:

using System;

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 (publisher == null)
         throw new ArgumentNullException("The publisher cannot be null.");
      else if (String.IsNullOrWhiteSpace(publisher))
         throw new ArgumentException("The publisher cannot consist only of white space.");
      Publisher = publisher;
  
      if (title == null)
         throw new ArgumentNullException("The title cannot be null.");
      else if (String.IsNullOrWhiteSpace(title))
         throw new ArgumentException("The title cannot consist only of white space.");
      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("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;
      this.datePublished = datePublished;
   }

   public void Copyright(string copyrightName, int copyrightDate)
   {
      if (copyrightName == null)
         throw new ArgumentNullException("The name of the copyright holder cannot be null.");
      else if (String.IsNullOrWhiteSpace(copyrightName))
         throw new ArgumentException("The name of the copyright holder cannot consist only of white space.");
      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;
}
  • Un constructorA constructor

    Dado que la clase Publication es abstract, no se puede crear una instancia de ella directamente desde código similar al del ejemplo siguiente:Because the Publication class is abstract, it cannot be instantiated directly from code like the following example:

    var publication = new Publication("Tiddlywinks for Experts", "Fun and Games",
                                      PublicationType.Book);
    

    Sin embargo, su constructor de instancia se puede llamar directamente desde los constructores de clases derivadas, como muestra el código fuente de la clase Book.However, its instance constructor can be called directly from derived class constructors, as the source code for the Book class shows.

  • Dos propiedades relacionadas con la publicaciónTwo publication-related properties

    Title es una propiedad String de solo lectura cuyo valor se suministra mediante la llamada al constructor Publication.Title is a read-only String property whose value is supplied by calling the Publication constructor.

    Pages es una propiedad Int32 de solo lectura que indica cuántas páginas en total tiene la publicación.Pages is a read-write Int32 property that indicates how many total pages the publication has. El valor se almacena en un campo privado denominado totalPages.The value is stored in a private field named totalPages. Debe ser un número positivo o se inicia una excepción ArgumentOutOfRangeException.It must be a positive number or an ArgumentOutOfRangeException is thrown.

  • Miembros relacionados con el publicadorPublisher-related members

    Dos propiedades de solo lectura, Publisher y Type.Two read-only properties, Publisher and Type. Los valores se proporcionan originalmente mediante la llamada al constructor de clase Publication.The values are originally supplied by the call to the Publication class constructor.

  • Miembros relacionados con la publicaciónPublishing-related members

    Dos métodos, Publish y GetPublicationDate, establecen y devuelven la fecha de publicación.Two methods, Publish and GetPublicationDate, set and return the publication date. El método Publish establece una marca published privada en true cuando se llama y asigna la fecha pasada a él como argumento al campo datePublished privado.The Publish method sets a private published flag to true when it is called and assigns the date passed to it as an argument to the private datePublished field. El método GetPublicationDate devuelve la cadena "NYP" si la marca published es false, y el valor del campo datePublished si es true.The GetPublicationDate method returns the string "NYP" if the published flag is false, and the value of the datePublished field if it is true.

  • Miembros relacionados con copyrightCopyright-related members

    El método Copyright toma como argumentos el nombre del propietario del copyright y el año del copyright, y los asigna a las propiedades CopyrightName y CopyrightDate.The Copyright method takes the name of the copyright holder and the year of the copyright as arguments and assigns them to the CopyrightName and CopyrightDate properties.

  • Una invalidación del método ToStringAn override of the ToString method

    Si un tipo no invalida al método Object.ToString, devuelve el nombre completo del tipo, que es de poca utilidad a la hora de diferenciar una instancia de otra.If a type does not override the Object.ToString method, it returns the fully qualified name of the type, which is of little use in differentiating one instance from another. La clase Publication invalida Object.ToString para devolver el valor de la propiedad Title.The Publication class overrides Object.ToString to return the value of the Title property.

En la ilustración siguiente se muestra la relación entre la clase base Publication y su clase Object heredada de forma implícita.The following figure illustrates the relationship between your base Publication class and its implicitly inherited Object class.

Las clases de objeto y publicación

La clase Book.The Book class

La clase Book representa un libro como un tipo especializado de publicación.The Book class represents a book as a specialized type of publication. En el ejemplo siguiente se muestra el código fuente de la clase Book.The following example shows the source code for the Book class.

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.");
        ulong nISBN = 0;
        if (! UInt64.TryParse(isbn, out nISBN))
            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("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)
   {
      Book book = obj as Book;
      if (book == null)
         return false;
      else
         return ISBN == book.ISBN;   
   }

   public override int GetHashCode() => ISBN.GetHashCode();

   public override string ToString() => $"{(String.IsNullOrEmpty(Author) ? "" : Author + ", ")}{Title}"; 
}

Además de los miembros que hereda de Publication, la clase Book define los siguientes miembros únicos e invalidaciones de miembros:In addition to the members that it inherits from Publication, the Book class defines the following unique members and member overrides:

  • Dos constructoresTwo constructors

    Los dos constructores Book comparten tres parámetros comunes.The two Book constructors share three common parameters. Dos, title y publisher, corresponden a los parámetros del constructor Publication.Two, title and publisher, correspond to parameters of the Publication constructor. La tercera es author, que se almacena para una propiedad Author pública inmutable.The third is author, which is stored to a public immutable Author property. Un constructor incluye un parámetro isbn, que se almacena en la propiedad automática ISBN.One constructor includes an isbn parameter, which is stored in the ISBN auto-property.

    El primer constructor usa esta palabra clave para llamar al otro constructor.The first constructor uses the this keyword to call the other constructor. El encadenamiento de constructores es un patrón común en la definición de constructores.Constructor chaining is a common pattern in defining constructors. Los constructores con menos parámetros proporcionan valores predeterminados al llamar al constructor con el mayor número de parámetros.Constructors with fewer parameters provide default values when calling the constructor with the greatest number of parameters.

    El segundo constructor usa la palabra clave base para pasar el título y el nombre del editor al constructor de clase base.The second constructor uses the base keyword to pass the title and publisher name to the base class constructor. Si no realiza una llamada explícita a un constructor de clase base en el código fuente, el compilador de C# proporciona automáticamente una llamada al constructor sin parámetros o predeterminado de la clase base.If you don't make an explicit call to a base class constructor in your source code, the C# compiler automatically supplies a call to the base class' default or parameterless constructor.

  • Una propiedad ISBN de solo lectura, que devuelve el ISBN (International Standard Book Number) del objeto Book, un número exclusivo de 10 y 13 caracteres.A read-only ISBN property, which returns the Book object's International Standard Book Number, a unique 10- or 13-digit number. El ISBN se proporciona como argumento para uno de los constructores Book.The ISBN is supplied as an argument to one of the Book constructors. El ISBN se almacena en un campo de respaldo privado, generado automáticamente por el compilador.The ISBN is stored in a private backing field, which is auto-generated by the compiler.

  • Una propiedad Author de solo lectura.A read-only Author property. El nombre del autor se proporciona como argumento para ambos constructores Book y se almacena en la propiedad.The author name is supplied as an argument to both Book constructors and is stored in the property.

  • Dos propiedades relacionadas con el precio de solo lectura, Price y Currency.Two read-only price-related properties, Price and Currency. Sus valores se proporcionan como argumentos en una llamada al método SetPrice.Their values are provided as arguments in a SetPrice method call. La propiedad Currency es el símbolo de moneda ISO de tres dígitos (por ejemplo, USD para el dólar estadounidense).The Currency property is the three-digit ISO currency symbol (for example, USD for the U.S. dollar). Los símbolos de moneda ISO se pueden recuperar de la propiedad ISOCurrencySymbol.ISO currency symbols can be retrieved from the ISOCurrencySymbol property. Ambas propiedades son de solo lectura desde ubicaciones externas, pero se pueden establecer mediante código en la clase Book.Both of these properties are externally read-only, but both can be set by code in the Book class.

  • Un método SetPrice, que establece los valores de las propiedades Price y Currency.A SetPrice method, which sets the values of the Price and Currency properties. Esos son los valores devueltos por dichas propiedades.Those values are returned by those same properties.

  • Invalida el método ToString (heredado de Publication) y los métodos Object.Equals(Object) y GetHashCode (heredados de Object).Overrides to the ToString method (inherited from Publication) and the Object.Equals(Object) and GetHashCode methods (inherited from Object).

    A menos que se invalide, el método Object.Equals(Object) prueba la igualdad de referencia.Unless it is overridden, the Object.Equals(Object) method tests for reference equality. Es decir, dos variables de objeto se consideran iguales si hacen referencia al mismo objeto.That is, two object variables are considered to be equal if they refer to the same object. Por otro lado, en la clase Book, dos objetos Book deben ser iguales si tienen el mismo ISBN.In the Book class, on the other hand, two Book objects should be equal if they have the same ISBN.

    Cuando invalide el método Object.Equals(Object), también debe invalidar el método GetHashCode, que devuelve un valor que se usa en el entorno de ejecución para almacenar elementos en colecciones con hash para una recuperación eficiente.When you override the Object.Equals(Object) method, you must also override the GetHashCode method, which returns a value that the runtime uses to store items in hashed collections for efficient retrieval. El código hash debe devolver un valor que sea coherente con la prueba de igualdad.The hash code should return a value that's consistent with the test for equality. Puesto que se ha invalidado Object.Equals(Object) para devolver true, si las propiedades de ISBN de dos objetos Book son iguales, se devuelve el código hash calculado mediante la llamada al método GetHashCode de la cadena devuelta por la propiedad ISBN.Since you've overridden Object.Equals(Object) to return true if the ISBN properties of two Book objects are equal, you return the hash code computed by calling the GetHashCode method of the string returned by the ISBN property.

En la siguiente ilustración se muestra la relación entre la clase Book y Publication, su clase base.The following figure illustrates the relationship between the Book class and Publication, its base class.

Clases de libro y publicación

Ahora se puede crear una instancia de un objeto Book, invocar sus miembros únicos y heredados, y pasarla como argumento a un método que espera un parámetro de tipo Publication o de tipo Book, como se muestra en el ejemplo siguiente.You can now instantiate a Book object, invoke both its unique and inherited members, and pass it as an argument to a method that expects a parameter of type Publication or of type Book, as the following example shows.

using System;
using static System.Console;

public class Example
{
   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");
      Write($"{book.Title} and {book2.Title} are the same publication: " +
            $"{((Publication) book).Equals(book2)}");
   }

   public static void ShowPublicationInfo(Publication pub)
   {
       string pubDate = pub.GetPublicationDate();
       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

Diseño de clases base abstractas y sus clases derivadasDesigning abstract base classes and their derived classes

En el ejemplo anterior, se define una clase base que proporciona una implementación para una serie de métodos con el fin de permitir que las clases derivadas compartan código.In the previous example, you defined a base class that provided an implementation for a number of methods to allow derived classes to share code. En muchos casos, sin embargo, no se espera que la clase base proporcione una implementación.In many cases, however, the base class is not expected to provide an implementation. En su lugar, la clase base es una clase abstracta que declara métodos abstractos; sirve como una plantilla que define los miembros que debe implementar cada clase derivada.Instead, the base class is an abstract class that declares abstract methods; it serves as a template that defines the members that each derived class must implement. Normalmente, en una clase base abstracta, la implementación de cada tipo derivado es exclusiva de ese tipo.Typically in an abstract base class, the implementation of each derived type is unique to that type. La clase se ha marcado con la palabra clave abstract porque no tenía mucho sentido crear instancias de un objeto Publication, aunque la clase proporcionara las implementaciones de funcionalidad común a las publicaciones.You marked the class with the abstract keyword because it made no sense to instantiate a Publication object, although the class did provide implementations of functionality common to publications.

Por ejemplo, cada forma geométrica bidimensional cerrada incluye dos propiedades: área, la extensión interna de la forma; y perímetro, o la distancia a lo largo de los bordes de la forma.For example, each closed two-dimensional geometric shape includes two properties: area, the inner extent of the shape; and perimeter, or the distance along the edges of the shape. La manera en que se calculan estas propiedades, sin embargo, depende completamente de la forma específica.The way in which these properties are calculated, however, depends completely on the specific shape. La fórmula para calcular el perímetro (o la circunferencia) de un círculo, por ejemplo, es diferente a la de un triángulo.The formula for calculating the perimeter (or circumference) of a circle, for example, is different from that of a triangle. La clase Shape es una clase abstract con métodos abstract.The Shape class is an abstract class with abstract methods. Eso indica que las clases derivadas comparten la misma funcionalidad, pero que la implementan de otra manera.That indicates derived classes share the same functionality, but those derived classes implement that functionality differently.

En el ejemplo siguiente se define una clase base abstracta denominada Shape que define dos propiedades: Area y Perimeter.The following example defines an abstract base class named Shape that defines two properties: Area and Perimeter. Además de marcar la clase con la palabra clave abstract, cada miembro de instancia también se marca con la palabra clave abstract.In addition to marking the class with the abstract keyword, each instance member is also marked with the abstract keyword. En este caso, Shape también invalida el método Object.ToString para devolver el nombre del tipo, en lugar de su nombre completo.In this case, Shape also overrides the Object.ToString method to return the name of the type, rather than its fully qualified name. Y define dos miembros estáticos, GetArea y GetPerimeter, que permiten que los llamadores recuperen fácilmente el área y el perímetro de una instancia de cualquier clase derivada.And it defines two static members, GetArea and GetPerimeter, that allow callers to easily retrieve the area and perimeter of an instance of any derived class. Cuando se pasa una instancia de una clase derivada a cualquiera de estos métodos, el runtime llama a la invalidación del método de la clase derivada.When you pass an instance of a derived class to either of these methods, the runtime calls the method override of the derived class.

using System;

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

Después, se pueden derivar algunas clases de Shape que representan formas concretas.You can then derive some classes from Shape that represent specific shapes. El ejemplo siguiente define tres clases Triangle, Rectangle y Circle.The following example defines three classes, Triangle, Rectangle, and Circle. Cada una usa una fórmula única para esa forma en particular para calcular el área y el perímetro.Each uses a formula unique for that particular shape to compute the area and perimeter. Algunas de las clases derivadas también definen propiedades, como Rectangle.Diagonal y Circle.Diameter, que son únicas para la forma que representan.Some of the derived classes also define properties, such as Rectangle.Diagonal and Circle.Diameter, that are unique to the shape that they represent.

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

En el ejemplo siguiente se usan objetos derivados de Shape.The following example uses objects derived from Shape. Se crea una instancia de una matriz de objetos derivados de Shape y se llama a los métodos estáticos de la clase Shape, que ajusta los valores de propiedad Shape devueltos.It instantiates an array of objects derived from Shape and calls the static methods of the Shape class, which wraps return Shape property values. El runtime recupera los valores de las propiedades invalidadas de los tipos derivados.The runtime retrieves values from the overridden properties of the derived types. En el ejemplo también se convierte cada objeto Shape de la matriz a su tipo derivado y, si la conversión se realiza correctamente, recupera las propiedades de esa subclase específica de Shape.The example also casts each Shape object in the array to its derived type and, if the cast succeeds, retrieves properties of that particular subclass of Shape.

using System;

public class Example
{
   public static void Main()
   {
      Shape[] shapes = { new Rectangle(10, 12), new Square(5),
                        new Circle(3) };
      foreach (var shape in shapes) {
         Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
                           $"perimeter, {Shape.GetPerimeter(shape)}");
         var rect = shape as Rectangle;
         if (rect != null) {
            Console.WriteLine($"   Is Square: {rect.IsSquare()}, Diagonal: {rect.Diagonal}");
            continue;
         }
         var sq = shape as Square;
         if (sq != null) {
            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

Vea tambiénSee also