2010-10-12 22 views
5

Que fait le code suivant?foreach (Derived obj dans la nouvelle liste <Base>())

class Base { } 
class Derived : Base { } 
class Test 
{ 
    void Foo(List<Base> list) 
    { 
     foreach (Derived obj in list) 
     { 
      // ... 
     } 
    } 
} 

Je ne m'attendais pas à le compiler, mais c'est le cas.

+0

Lisez sur le polymorphisme, voici un endroit pour commencer: http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming – Donnie

+4

Je sais ce qu'est le polymorphisme. –

Répondre

10

Le comportement que vous observez est conforme à la section 8.8.4 L'instruction foreach de la spécification de langage C#. Cette section définit la sémantique de l'instruction foreach comme suit:

[...] Les étapes ci-dessus, en cas de succès, produire sans ambiguïté un type de collection C, type recenseur E et type d'élément T. Une déclaration foreach du formulaire

foreach (V v in x) embedded-statement 

est ensuite étendu à:

{ 
    E e = ((C)(x)).GetEnumerator(); 
    try { 
     V v; 
     while (e.MoveNext()) { 
      // here the current item will be casted     
      v = (V)(T)e.Current; 
      embedded-statement 
     } 
    } 
    finally { 
     // Dispose e 
    } 
} 

La raison pour laquelle les inserts du compilateur cette distribution explicite est historique. C# 1.0 n'a pas eu les médicaments génériques ainsi afin de permettre à un code simple comme ce

ArrayList list = new ArrayList(); 
list.Add(1); 
foreach (int i in list) 
{ 
    ... 
} 

il a été décidé de laisser le compilateur introduire le casting.

7

Absolument rien, mais il le fait d'une manière très inefficace.

L'ordre des opérations est la suivante:

  1. instancier une nouvelle liste, avec zéro articles
  2. itérer sur cette liste nouvellement instancié, qui a zéro éléments
  3. pas jeté l'objet de base à un dérivé objet, car la liste a zéro élément. Si la liste contient des éléments, cette étape entraîne une exception d'exécution.
  4. N'exécutez pas le code dans le bloc foreach, car la liste contient zéro élément.

Modifier
En fonction de votre édition, vous courez le risque d'un InvalidCastException doit tout élément de la liste transmise à Foo pas réellement être un objet Derived.

Éditer2
Pourquoi compile-t-il? Parce que foreach implique une distribution implicite à Object pour chaque élément dans la liste, puis une autre transtypage explicite du type spécifié dans le bloc foreach, dans ce cas Derived

+2

Bien, merci. S'il vous plaît, changez votre réponse pour dire quelque chose comme "Foreach essaye de baisser silencieusement chaque objet à Dérivé, et jette quand c'est impossible." - Alors ça répondra exactement à ma question, et je serai capable d'accepter. –

+0

@Stefan Monov: vous battre à ce que je pense 3 secondes. – Randolpho

+1

Je ne pense pas que votre "Edit2" est correct. Il n'y a pas de distribution implicite à l'objet, il y a des conversions explicites impliquées. D'abord au type défini dans la collection (dans ce cas, Base), puis au type défini dans foreach. –

0

Je ne sais pas pourquoi ne pas attendre à ce que la compilation. Considérez ceci, qui est fonctionnellement équivalent:

{ 
    List<Base> list = new List<Base>(); // creates new, empty list of Base 
    foreach (Derived obj in list) 
    { 
    // ... 
    } 
} 

Est-ce que cela clarifie ce qui se passe dans votre code?

EDIT re version révisée.

Maintenant, il lancera InvalidCastException si votre liste contient tout ce qui n'est pas une instance de Derived. Derived 'est une base donc pas de problème avec la compilation de cela.

+1

Je ne m'attendrais pas à compiler car normalement vous ne pouvez pas assigner un objet Base à une variable Derived sans un downcast explicite - comme dans Derived d = new Base() ' –

+0

@Stefan - voir la réponse de @Jon Hanna –

0

Il est équivalent à:

Enumerator<Base> enumerator = new List<Base>().GetEnumerator(); 
while (enumerator.MoveNext()) 
{ 
    Derived obj = (Derived) enumerator.Current; 
    // ... 
} 

En tant que votre liste est vide, le bloc interne n'exécute pas.

Si elle contenait des éléments, alors il y aurait un risque InvalidCastException si l'un des éléments n'était pas de type Derived.

+1

Si vous voulez être plus précis, il y a en fait une double distribution. 'Derived obj = (Derived) (Base) enumerator.Current;' –

+1

Je veux vous remettre en question, mais je ne peux pas. foreach n'utilise pas d'énumérateurs génériques. – Randolpho

+0

@Randolpho, C# spec 8.8.4: "S'il existe exactement un type T tel qu'il y ait une conversion implicite de X à l'interface System.Collections.Generic.IEnumerable , alors le type de collection est cette interface, le type de l'énumérateur est l'interface System.Collections.Generic.IEnumerator , et le type d'élément est T. " –

0

Techniquement, Randolpho est correct: votre code ne fait rien. Mais je pense que vous arrivez à un point différent. La diffusion de vos éléments de liste à Derived vous donnera accès aux propriétés définies dans la classe Derived en plus des propriétés définies dans la classe Base. Toutefois, vous obtiendrez des erreurs si vous avez des éléments de type Derived dans la liste lorsque vous accédez à ces propriétés.

À moins d'un autre besoin, il est préférable d'avoir la liste définie comme List<Derived>.

3

foreach inclut un plâtre. Considérons qu'il peut être utilisé avec des énumérations d'objets non basées sur un modèle. La diffusion depuis Base vers Derived est valide, donc le code est valide, mais pourrait générer une exception lors de l'exécution.

+0

Ah, bon, maintenant j'ai appris autre chose - j'ai toujours pensé que lorsque j'utilise une collection non générique, j'ai besoin d'utiliser une variable "System.Object", comme ceci: 'foreach (objet a dans la liste)' . Maintenant je sais que cela me permet de mettre n'importe quel type à la place de 'object', là. –

+0

Il est bon de rappeler que les génériques n'existaient pas dans .NET avant la version 2.0. En tant que tel, alors qu'il serait peut-être logique aujourd'hui d'avoir «prédire» pas impliquer une distribution pour une frappe plus forte, cela aurait été une nuisance dans les jours pré-génériques quand le fait que l'on devait utiliser des collections d'objets dans les savait que le type de tous les éléments aurait abouti à beaucoup de casting explicite. –

0

le même que

list.Cast<Derived>(); 

il suffit de taper la coulée. dans certains cas, vous pourriez avoir une erreur