Aide mémoire sur l’utilisation de “new” et “override”

Si vous connaissez déjà new et override dans les grandes lignes, aller directement au résumé des cas possibles pour vérifier si vous maîtrisez tous les cas de figures.

Même si les mots clés new et override n’ont, à priori, rien à voir, d’une façon générale ils modifient le comportement de membres d’une classe dans le cadre d’un héritage. Même si le cadre général d’utilisation de ces mots clés est connu, quelques cas particuliers peuvent mener à des comportements inattendus.

Override

override est le mot clé permettant de surcharger un membre public ou protected dans une classe dérivée.

Héritage simple

Pour effectuer une surcharge, il faut que le membre de la classe de base soit virtuel c’est-à-dire qu’il utilise le mot clé virtual. virtual autorise, ainsi, les surcharges éventuelles dans les classes dérivées.

Par exemple, si on définit la classe de base:

public class BaseClass 
{ 
    private int innerValue = 7; 
 
    public virtual int InnerValue  
    {  
        get { return this.innerValue; }  
    } 
 
    public int ComputeValue(int value) 
    { 
        return this.AddToInnerValue(value); 
    } 
 
    protected virtual int AddToInnerValue(int value) 
    { 
        return value + this.innerValue; 
    } 
}

La propriété InnerValue et la méthode AddToInnerValue() sont virtuelles et donc surchargeable dans une classe dérivée:

public class DerivedClass : BaseClass 
{ 
    public override int InnerValue 
    {  
        get { return 4; }  
    } 
 
    protected override int AddToInnerValue(int value) 
    { 
        return value + 9; 
    } 
}

override permet de modifier la définition d’un membre public ou protected dans la classe dérivée.

On peut faire appel à la classe de base en utilisant le mot clé base.

Par exemple, dans la classe DerivedClass, on peut appeler la méthode dans la classe BaseClass en faisant:

protected override int AddToInnerValue(int value) 
{ 

    return base.AddToInnerValue(value); 
}

Héritage d’une classe abstraite

override s’utilise aussi pour déclarer un membre public ou protected dans une classe dérivée si ces membres sont abstraits dans la classe de base. Pour indiquer qu’un membre est abstrait dans la classe de base, il doit utiliser le mot clé abstract.

Si une classe comporte au moins un membre abstrait, elle est déclarée abstraite et doit aussi être déclarée en utilisant le mot clé abstract.

Par exemple, si on déclare la classe abstraite:

public abstract class BaseClass 
{ 
    public abstract int InnerValue { get; } 
 
    public int ComputeValue(int value) 
    { 
        return this.AddToInnerValue(value); 
    } 
 
    protected abstract int AddToInnerValue(int value); 
}

On peut surcharger les membres abstraits en utilisant override:

public class DerivedClass : BaseClass 
{ 
    public override int InnerValue 
    {  
        get { return 4; }  
    } 
 
    protected override int AddToInnerValue(int value) 
    { 
        return value + 9; 
    } 
}

Dans la classe abstraite, les membres abstraits sont juste déclarés et n’ont pas d’implémentation. Si la classe dérivée n’est pas abstraite, il est obligatoire de définir tous les membres abstraits.

Si la classe dérivée est abstraite, il n’est pas obligatoire de définir une implémentation pour tous les membres abstraits.

New

new n’est pas un opérateur d’héritage. Il permet d’indiquer au compilateur qu’on souhaite appeler un membre dans une classe dérivée de la même façon qu’un autre membre dans la classe de base.

Par exemple, on définit la classe de base suivante:

public class BaseClass 
{ 
    public int InnerValue(int value) 
    { 
        return value + 5; 
    } 
}

Si on définit la classe dérivée avec le même nom de méthode:

public class DerivedClass : BaseClass 
{ 
    public int AddToInnerValue(int value) 
    { 
        return value + 9; 
    } 
}

A la compilation, on aura le message de “warning”:

Warning 'DerivedClass.AddToInnerValue' hides inherited member 'BaseClass.AddToInnerValue'. Use the new keyword if hiding was intended.

Pour supprimer ce message de “warning”, il suffit d’utiliser le mot clé new dans la méthode de la classe dérivée:

public class DerivedClass : BaseClass 
{ 
    public new int AddToInnerValue(int value) 
    { 
        return value + 9; 
    } 
}
“new” ne modifie pas le comportement lié à l’héritage

Le mot clé new ne modifie pas le comportement d’héritage des classes. La méthode AddToInnerValue() définit dans la classe de base BaseClass n’est pas déclarée “virtuelle” donc la méthode AddToInnerValue() dans la classe dérivée DerivedClass ne surcharge pas la méthode dans la classe de base. Il s’agit d’une nouvelle méthode qui n’a rien à voir avec celle définit dans la classe de base. Ainsi dans tous les cas d’utilisation, c’est la méthode de la classe de base qui sera exécutée sauf si on n’utilise pas le polymorphisme.

Exemples d’utilisation de “new” et “override”

Utilisation de “override”

Si on définit les objets A, B et I de la façon suivante:

public interface I 
{  
    void Method(); 
} 
 
public class A : I 
{ 
    public virtual void Method()  
    { 
        Console.WriteLine("Exécuté dans A"); 
    } 
} 
 
public class B : A 
{ 
    public override void Method() 
    { 
        Console.WriteLine("Exécuté dans B"); 
    } 
}

Différents cas d’utilisation des classes avec “override”

A instance1 = new A(); 
instance1.Method(); // Exécuté dans A 
 
A instance2 = new B(); 
instance2.Method(); // Exécuté dans B 
 
B instance3 = new B(); 
instance3.Method(); // Exécuté dans B 
 
I instance4 = new A(); 
instance4.Method(); // Exécuté dans A 
 
I instance5 = new B(); 
instance5.Method(); // Exécuté dans B

Dans le cas d’un héritage classique, override permet de définir un comportement différent de la méthode dans le classe dérivée.

Si l’objet est du type de la classe dérivée, la méthode exécutée sera toujours celle de la classe dérivée quelque soit le type de la variable référençant l’objet.

Utilisation de “new”

Si on définit les objets A, B et I de la façon suivante:

public interface I 
{  
    void Method(); 
} 
 
public class A : I 
{ 
    public void Method()  
    { 
        Console.WriteLine("Exécuté dans A"); 
    } 
} 
 
public class B : A 
{ 
    public new void Method() 
    { 
        Console.WriteLine("Exécuté dans B"); 
    } 
}

Différents cas d’utilisation des classes avec “new”

A instance1 = new A(); 
instance1.Method(); // Exécuté dans A 
 
A instance2 = new B(); 
instance2.Method(); // Exécuté dans A 
 
B instance3 = new B(); 
instance3.Method(); // Exécuté dans B 
 
I instance4 = new A(); 
instance4.Method(); // Exécuté dans A 
 
I instance5 = new B(); 
instance5.Method(); // Exécuté dans A

new ne permet pas de substituer le lien d’héritage entre les classes: mis à part le cas où on instancie la classe dérivée et qu’on l’utilise dans son type d’origine, dans tous les cas, c’est la méthode dans la classe de base qui sera exécutée.

La méthode exécutée sera toujours celle de la classe de base sauf si l’objet et la variable sont explicitement du type de la classe dérivée.

Résumé des cas possibles

Dans le tableau suivant, on peut voir tous les comportements possibles dans le cas d’utilisation des mots clés new et override:

Définition et instanciation des objets Variable de la classe dérivée
Instanciation de la classe dérivée
Variable de la classe de base
Instanciation de la classe dérivée
Variable avec le type de la classe de base
Instanciation de la classe de base
Variable avec le type d’une interface
Instanciation de la classe de base
Variable avec le type d’une interface
Instanciation de la classe dérivée
B obj
= new B();
obj.M();
A obj
= new B();
obj.M();
A obj
= new A();
obj.M();
I obj
= new A();
obj.M();
I obj
= new B();
obj.M();
Pas de surchage:

public interface I
{
  void M();
}

public class A : I
{
  public void M(){}
}

public class B : A {}
Exécution dans la classe de base (A)
Pas d’utilisation d’opérateur:

public interface I
{
  void M();
}

public class A : I
{
  public void M(){}
}

public class B : A
{
  public void M(){}
}
Exécution dans la classe dérivée (B)
Compilation avec “warning”
Exécution dans la classe de base (A)
Compilation avec “warning”
virtual dans la classe base:

public interface I
{
  void M();
}

public class A : I
{ 
  public virtual void M(){}
}

public class B : A
{
  public void M(){}
}
Exécution dans la classe dérivée (B)
Compilation avec “warning”
Exécution dans la classe de base (A)
Compilation avec “warning”
new dans la classe dérivée:

public interface I
{
  void M();
}

public class A : I
{
  public void M(){}
}

public class B : A
{
  public new void M(){}
}
Exécution dans la classe dérivée (B) Exécution dans la classe de base (A)
  • virtual dans la classe de base
  • new dans la classe dérivée
public interface I
{
  void M();
}

public class A : I
{
  public virtual void M(){}
}

public class B : A
{
  public new void M(){}
}
Exécution dans la classe dérivée (B) Exécution dans la classe de base (A)
Surcharge classique:

  • virtual dans la classe de base
  • override dans la classe dérivée
public interface I
{
  void M();
}

public class A : I
{
  public virtual void M(){}
}

public class B : A
{
  public override void M(){}
}
Exécution dans la classe dérivée (B) Exécution dans la classe dérivée (B) Exécution dans la classe de base (A) Exécution dans la classe de base (A) Exécution dans la classe dérivée (B)

Leave a Reply