C# Language
Herencia
Buscar..
Sintaxis
- clase DerivedClass: BaseClass
- clase DerivedClass: BaseClass, IExampleInterface
- clase DerivedClass: BaseClass, IExampleInterface, IAnotherInterface
Observaciones
Las clases pueden heredar directamente de una sola clase, pero (en lugar o al mismo tiempo) pueden implementar una o más interfaces.
Structs puede implementar interfaces pero no puede heredar explícitamente de ningún tipo. System.ValueType
implícitamente de System.ValueType
, que a su vez hereda directamente de System.Object
.
Las clases estáticas no pueden implementar interfaces.
Heredando de una clase base
Para evitar la duplicación de código, defina métodos y atributos comunes en una clase general como base:
public class Animal
{
public string Name { get; set; }
// Methods and attributes common to all animals
public void Eat(Object dinner)
{
// ...
}
public void Stare()
{
// ...
}
public void Roll()
{
// ...
}
}
Ahora que tiene una clase que representa a Animal
en general, puede definir una clase que describa las peculiaridades de animales específicos:
public class Cat : Animal
{
public Cat()
{
Name = "Cat";
}
// Methods for scratching furniture and ignoring owner
public void Scratch(Object furniture)
{
// ...
}
}
La clase Cat tiene acceso no solo a los métodos descritos en su definición explícitamente, sino también a todos los métodos definidos en la clase base general de Animal
. Cualquier animal (sea o no un gato) podría comer, mirar fijamente o rodar. Sin embargo, un animal no podría rascarse, a menos que también fuera un gato. A continuación, podría definir otras clases que describen otros animales. (Como Gopher con un método para destruir jardines de flores y Sloth sin ningún método adicional).
Heredar de una clase e implementar una interfaz.
public class Animal
{
public string Name { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
//Note that in C#, the base class name must come before the interface names
public class Cat : Animal, INoiseMaker
{
public Cat()
{
Name = "Cat";
}
public string MakeNoise()
{
return "Nyan";
}
}
Heredando de una clase e implementando múltiples interfaces
public class LivingBeing
{
string Name { get; set; }
}
public interface IAnimal
{
bool HasHair { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
//Note that in C#, the base class name must come before the interface names
public class Cat : LivingBeing, IAnimal, INoiseMaker
{
public Cat()
{
Name = "Cat";
HasHair = true;
}
public bool HasHair { get; set; }
public string Name { get; set; }
public string MakeNoise()
{
return "Nyan";
}
}
Herencia de prueba y navegación
interface BaseInterface {}
class BaseClass : BaseInterface {}
interface DerivedInterface {}
class DerivedClass : BaseClass, DerivedInterface {}
var baseInterfaceType = typeof(BaseInterface);
var derivedInterfaceType = typeof(DerivedInterface);
var baseType = typeof(BaseClass);
var derivedType = typeof(DerivedClass);
var baseInstance = new BaseClass();
var derivedInstance = new DerivedClass();
Console.WriteLine(derivedInstance is DerivedClass); //True
Console.WriteLine(derivedInstance is DerivedInterface); //True
Console.WriteLine(derivedInstance is BaseClass); //True
Console.WriteLine(derivedInstance is BaseInterface); //True
Console.WriteLine(derivedInstance is object); //True
Console.WriteLine(derivedType.BaseType.Name); //BaseClass
Console.WriteLine(baseType.BaseType.Name); //Object
Console.WriteLine(typeof(object).BaseType); //null
Console.WriteLine(baseType.IsInstanceOfType(derivedInstance)); //True
Console.WriteLine(derivedType.IsInstanceOfType(baseInstance)); //False
Console.WriteLine(
string.Join(",",
derivedType.GetInterfaces().Select(t => t.Name).ToArray()));
//BaseInterface,DerivedInterface
Console.WriteLine(baseInterfaceType.IsAssignableFrom(derivedType)); //True
Console.WriteLine(derivedInterfaceType.IsAssignableFrom(derivedType)); //True
Console.WriteLine(derivedInterfaceType.IsAssignableFrom(baseType)); //False
Extendiendo una clase base abstracta
A diferencia de las interfaces, que se pueden describir como contratos de implementación, las clases abstractas actúan como contratos de extensión.
Una clase abstracta no puede ser instanciada, debe ser extendida y la clase resultante (o clase derivada) puede ser instanciada.
Las clases abstractas se utilizan para proporcionar implementaciones genéricas.
public abstract class Car
{
public void HonkHorn() {
// Implementation of horn being honked
}
}
public class Mustang : Car
{
// Simply by extending the abstract class Car, the Mustang can HonkHorn()
// If Car were an interface, the HonkHorn method would need to be included
// in every class that implemented it.
}
El ejemplo anterior muestra cómo cualquier clase de Automóviles que se extienden automáticamente recibirá el método HonkHorn con la implementación. Esto significa que cualquier desarrollador que cree un nuevo Coche no tendrá que preocuparse por cómo sonará su bocina.
Constructores en una subclase
Cuando creas una subclase de una clase base, puedes construir la clase base usando : base
después de los parámetros del constructor de la subclase.
class Instrument
{
string type;
bool clean;
public Instrument (string type, bool clean)
{
this.type = type;
this.clean = clean;
}
}
class Trumpet : Instrument
{
bool oiled;
public Trumpet(string type, bool clean, bool oiled) : base(type, clean)
{
this.oiled = oiled;
}
}
Herencia. Secuencia de llamadas de los constructores.
Considera que tenemos un Animal
clase que tiene un Dog
clase infantil
class Animal
{
public Animal()
{
Console.WriteLine("In Animal's constructor");
}
}
class Dog : Animal
{
public Dog()
{
Console.WriteLine("In Dog's constructor");
}
}
Por defecto, cada clase hereda implícitamente la clase Object
.
Esto es lo mismo que el código anterior.
class Animal : Object
{
public Animal()
{
Console.WriteLine("In Animal's constructor");
}
}
Al crear una instancia de la clase Dog
, se llamará al constructor predeterminado de las clases base (sin parámetros) si no hay una llamada explícita a otro constructor en la clase principal . En nuestro caso, primero se llamará constructor Object's
, luego constructor de Animal's
y al final de Dog's
.
public class Program
{
public static void Main()
{
Dog dog = new Dog();
}
}
La salida será
En constructor de animales.
En constructor de perros.
Llame al constructor de los padres explícitamente.
En los ejemplos anteriores, nuestro constructor de clase Dog
llama al constructor predeterminado de la clase Animal
. Si lo desea, puede especificar a qué constructor se debe llamar: es posible llamar a cualquier constructor que esté definido en la clase principal.
Consideremos que tenemos estas dos clases.
class Animal
{
protected string name;
public Animal()
{
Console.WriteLine("Animal's default constructor");
}
public Animal(string name)
{
this.name = name;
Console.WriteLine("Animal's constructor with 1 parameter");
Console.WriteLine(this.name);
}
}
class Dog : Animal
{
public Dog() : base()
{
Console.WriteLine("Dog's default constructor");
}
public Dog(string name) : base(name)
{
Console.WriteLine("Dog's constructor with 1 parameter");
Console.WriteLine(this.name);
}
}
¿Qué está pasando aquí?
Tenemos 2 constructores en cada clase.
¿Qué significa la base
?
base
es una referencia a la clase padre. En nuestro caso, cuando creamos una instancia de clase Dog
como esta.
Dog dog = new Dog();
El tiempo de ejecución primero llama al Dog()
, que es el constructor sin parámetros. Pero su cuerpo no funciona de inmediato. Después de los paréntesis del constructor tenemos una llamada de este tipo: base()
, lo que significa que cuando llamamos al constructor de Dog
predeterminado, a su vez llamará al constructor predeterminado del padre. Después de que se ejecute el constructor principal, este regresará y, finalmente, ejecutará el cuerpo del constructor Dog()
.
Así que la salida será así:
Constructor por defecto del animal
Constructor por defecto del perro
¿Y ahora si llamamos al constructor Dog's
con un parámetro?
Dog dog = new Dog("Rex");
Usted sabe que los miembros de la clase principal que no son privados son heredados por la clase secundaria, lo que significa que Dog
también tendrá el campo de name
.
En este caso pasamos un argumento a nuestro constructor. A su vez, pasa el argumento al constructor de la clase padre con un parámetro , que inicializa el campo de name
.
La salida será
Animal's constructor with 1 parameter
Rex
Dog's constructor with 1 parameter
Rex
Resumen:
Cada creación de objetos comienza a partir de la clase base. En la herencia, las clases que están en la jerarquía están encadenadas. Como todas las clases se derivan de Object
, el primer constructor al que se llama cuando se crea un objeto es el constructor de la clase Object
; Luego se llama al siguiente constructor en la cadena y solo después de que todos se llaman, se crea el objeto
palabra clave base
- La palabra clave base se utiliza para acceder a los miembros de la clase base desde una clase derivada:
- Llame a un método en la clase base que ha sido anulado por otro método. Especifique a qué constructor de clase base se debe llamar al crear instancias de la clase derivada.
Heredando metodos
Hay varias formas de heredar los métodos.
public abstract class Car
{
public void HonkHorn() {
// Implementation of horn being honked
}
// virtual methods CAN be overridden in derived classes
public virtual void ChangeGear() {
// Implementation of gears being changed
}
// abstract methods MUST be overridden in derived classes
public abstract void Accelerate();
}
public class Mustang : Car
{
// Before any code is added to the Mustang class, it already contains
// implementations of HonkHorn and ChangeGear.
// In order to compile, it must be given an implementation of Accelerate,
// this is done using the override keyword
public override void Accelerate() {
// Implementation of Mustang accelerating
}
// If the Mustang changes gears differently to the implementation in Car
// this can be overridden using the same override keyword as above
public override void ChangeGear() {
// Implementation of Mustang changing gears
}
}
Herencias Anti-patrones
Herencia impropia
Digamos que hay 2 clases de clase Foo
y Bar
. Foo
tiene dos características Do1
y Do2
. Bar
necesita usar Do1
de Foo
, pero no necesita Do2
o necesita una función que sea equivalente a Do2
pero hace algo completamente diferente.
Mala manera : hacer Do2()
en Foo
virtual, anularlo en Bar
o simplemente throw Exception
en Bar
para Do2()
public class Bar : Foo
{
public override void Do2()
{
//Does something completely different that you would expect Foo to do
//or simply throws new Exception
}
}
Buen camino
Saque Do1()
de Foo
y póngalo en la nueva clase Baz
luego herede tanto a Foo
como a Bar
de Baz
e implemente Do2()
separado
public class Baz
{
public void Do1()
{
// magic
}
}
public class Foo : Baz
{
public void Do2()
{
// foo way
}
}
public class Bar : Baz
{
public void Do2()
{
// bar way or not have Do2 at all
}
}
Ahora, por qué el primer ejemplo es malo y el segundo es bueno: cuando el desarrollador nr2 tiene que hacer un cambio en Foo
, es probable que rompa la implementación de Bar
porque ahora Bar
es inseparable de Foo
. Al hacerlo mediante el último ejemplo, Foo
y Bar
commonalty se han trasladado a Baz
y no se afectan entre sí (como no debería).
Clase base con especificación de tipo recursivo
Definición única de una clase base genérica con especificador de tipo recursivo. Cada nodo tiene un padre y varios hijos.
/// <summary>
/// Generic base class for a tree structure
/// </summary>
/// <typeparam name="T">The node type of the tree</typeparam>
public abstract class Tree<T> where T : Tree<T>
{
/// <summary>
/// Constructor sets the parent node and adds this node to the parent's child nodes
/// </summary>
/// <param name="parent">The parent node or null if a root</param>
protected Tree(T parent)
{
this.Parent=parent;
this.Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; private set; }
public List<T> Children { get; private set; }
public bool IsRoot { get { return Parent==null; } }
public bool IsLeaf { get { return Children.Count==0; } }
/// <summary>
/// Returns the number of hops to the root object
/// </summary>
public int Level { get { return IsRoot ? 0 : Parent.Level+1; } }
}
Lo anterior se puede reutilizar cada vez que se necesita definir una jerarquía de objetos de árbol. El objeto de nodo en el árbol tiene que heredar de la clase base con
public class MyNode : Tree<MyNode>
{
// stuff
}
cada clase de nodo sabe dónde se encuentra en la jerarquía, qué es el objeto principal y qué son los objetos secundarios. Varios tipos incorporados utilizan una estructura de árbol, como Control
o XmlElement
y el Tree<T>
se puede usar como una clase base de cualquier tipo en su código.
Por ejemplo, para crear una jerarquía de partes donde el peso total se calcula a partir del peso de todos los hijos, haga lo siguiente:
public class Part : Tree<Part>
{
public static readonly Part Empty = new Part(null) { Weight=0 };
public Part(Part parent) : base(parent) { }
public Part Add(float weight)
{
return new Part(this) { Weight=weight };
}
public float Weight { get; set; }
public float TotalWeight { get { return Weight+Children.Sum((part) => part.TotalWeight); } }
}
para ser utilizado como
// [Q:2.5] -- [P:4.2] -- [R:0.4]
// \
// - [Z:0.8]
var Q = Part.Empty.Add(2.5f);
var P = Q.Add(4.2f);
var R = P.Add(0.4f);
var Z = Q.Add(0.9f);
// 2.5+(4.2+0.4)+0.9 = 8.0
float weight = Q.TotalWeight;
Otro ejemplo sería en la definición de marcos de coordenadas relativas. En este caso, la posición verdadera del marco de coordenadas depende de las posiciones de todos los marcos de coordenadas principales.
public class RelativeCoordinate : Tree<RelativeCoordinate>
{
public static readonly RelativeCoordinate Start = new RelativeCoordinate(null, PointF.Empty) { };
public RelativeCoordinate(RelativeCoordinate parent, PointF local_position)
: base(parent)
{
this.LocalPosition=local_position;
}
public PointF LocalPosition { get; set; }
public PointF GlobalPosition
{
get
{
if(IsRoot) return LocalPosition;
var parent_pos = Parent.GlobalPosition;
return new PointF(parent_pos.X+LocalPosition.X, parent_pos.Y+LocalPosition.Y);
}
}
public float TotalDistance
{
get
{
float dist = (float)Math.Sqrt(LocalPosition.X*LocalPosition.X+LocalPosition.Y*LocalPosition.Y);
return IsRoot ? dist : Parent.TotalDistance+dist;
}
}
public RelativeCoordinate Add(PointF local_position)
{
return new RelativeCoordinate(this, local_position);
}
public RelativeCoordinate Add(float x, float y)
{
return Add(new PointF(x, y));
}
}
para ser utilizado como
// Define the following coordinate system hierarchy
//
// o--> [A1] --+--> [B1] -----> [C1]
// |
// +--> [B2] --+--> [C2]
// |
// +--> [C3]
var A1 = RelativeCoordinate.Start;
var B1 = A1.Add(100, 20);
var B2 = A1.Add(160, 10);
var C1 = B1.Add(120, -40);
var C2 = B2.Add(80, -20);
var C3 = B2.Add(60, -30);
double dist1 = C1.TotalDistance;