ToList () - crée-t-il une nouvelle liste?

176

Disons que j'ai une classe

public class MyObject
{
   public int SimpleInt{get;set;}
}

Et j'ai un List<MyObject>, et je ToList()le change , puis je change l'un des SimpleInt, ma modification sera-t-elle propagée à la liste d'origine. En d'autres termes, quel serait le résultat de la méthode suivante?

public void RunChangeList()
{
  var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}};
  var whatInt = ChangeToList(objs );
}
public int ChangeToList(List<MyObject> objects)
{
  var objectList = objects.ToList();
  objectList[0].SimpleInt=5;
  return objects[0].SimpleInt;

}

Pourquoi?

P / S: Je suis désolé s'il semble évident de le découvrir. Mais je n'ai pas de compilateur avec moi maintenant ...

Graviton
la source
Une façon de le formuler est de faire .ToList()une copie superficielle . Les références sont copiées, mais les nouvelles références pointent toujours vers les mêmes instances que celles vers lesquelles pointent les références d'origine. Quand on y pense, ToListon ne peut pas en créer new MyObject()quand MyObjectest un classtype.
Jeppe Stig Nielsen

Réponses:

217

Oui, ToListcréera une nouvelle liste, mais comme dans ce cas MyObjectest un type de référence, la nouvelle liste contiendra des références aux mêmes objets que la liste d'origine.

La mise à jour de la SimpleIntpropriété d'un objet référencé dans la nouvelle liste affectera également l'objet équivalent dans la liste d'origine.

(Si MyObjectétait déclaré comme un structplutôt que comme un, classalors la nouvelle liste contiendrait des copies des éléments de la liste d'origine, et la mise à jour d'une propriété d'un élément dans la nouvelle liste n'affecterait pas l'élément équivalent dans la liste d'origine.)

LukeH
la source
1
Notez également qu'avec un Listof structs, une affectation comme celle- objectList[0].SimpleInt=5ci ne serait pas autorisée (erreur de compilation C #). C'est parce que la valeur de retour de l' getaccesseur de l'indexeur de liste n'est pas une variable (c'est une copie retournée d'une valeur struct), et donc la définition de son membre .SimpleIntavec une expression d'affectation n'est pas autorisée (cela muterait une copie qui n'est pas conservée) . Eh bien, qui utilise de toute façon des structures mutables?
Jeppe Stig Nielsen
2
@Jeppe Stig Nielson: Oui. Et, de même, le compilateur vous empêchera également de faire des choses comme foreach (var s in listOfStructs) { s.SimpleInt = 42; }. Le truc vraiment méchant est lorsque vous essayez quelque chose comme listOfStructs.ForEach(s => s.SimpleInt = 42): le compilateur le permet et le code s'exécute sans exceptions, mais les structures de la liste resteront inchangées!
LukeH le
62

De la source du réflecteur:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return new List<TSource>(source);
}

Alors oui, votre liste d'origine ne sera pas mise à jour (c'est-à-dire ajouts ou suppressions) mais les objets référencés le seront.

Chris S
la source
32

ToList créera toujours une nouvelle liste, qui ne reflètera aucune modification ultérieure de la collection.

Cependant, il reflétera les modifications apportées aux objets eux-mêmes (à moins qu'ils ne soient des structures modifiables).

En d'autres termes, si vous remplacez un objet de la liste d'origine par un autre objet, le ToListcontiendra toujours le premier objet.
Cependant, si vous modifiez l'un des objets de la liste d'origine, le ToListcontiendra toujours le même objet (modifié).

SLaks
la source
12

La réponse acceptée répond correctement à la question du PO sur la base de son exemple. Cependant, il ne s'applique que lorsqu'il ToListest appliqué à une collection de béton; il ne tient pas lorsque les éléments de la séquence source doivent encore être instanciés (en raison d'une exécution différée). Dans ce dernier cas, vous pourriez obtenir un nouvel ensemble d'éléments à chaque fois que vous appelez ToList(ou énumérez la séquence).

Voici une adaptation du code de l'OP pour démontrer ce comportement:

public static void RunChangeList()
{
    var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 });
    var whatInt = ChangeToList(objs);   // whatInt gets 0
}

public static int ChangeToList(IEnumerable<MyObject> objects)
{
    var objectList = objects.ToList();
    objectList.First().SimpleInt = 5;
    return objects.First().SimpleInt;
}

Alors que le code ci-dessus peut sembler artificiel, ce comportement peut apparaître comme un bogue subtil dans d'autres scénarios. Voir mon autre exemple pour une situation dans laquelle des tâches sont générées à plusieurs reprises.

Douglas
la source
11

Oui, cela crée une nouvelle liste. C'est par conception.

La liste contiendra les mêmes résultats que la séquence énumérable d'origine, mais matérialisés dans une collection persistante (en mémoire). Cela vous permet de consommer les résultats plusieurs fois sans encourir le coût de recalcul de la séquence.

La beauté des séquences LINQ est qu'elles sont composables. Souvent, ce que IEnumerable<T>vous obtenez est le résultat de la combinaison de plusieurs opérations de filtrage, de classement et / ou de projection. Les méthodes d'extension comme ToList()et ToArray()vous permettent de convertir la séquence calculée en une collection standard.

LBushkin
la source
6

Une nouvelle liste est créée mais les éléments qu'elle contient sont des références aux éléments originaux (tout comme dans la liste d'origine). Les modifications apportées à la liste elle-même sont indépendantes, mais les modifications apportées aux éléments se trouvent dans les deux listes.

Preet Sangha
la source
6

Trébuchez simplement sur ce vieux poste et pensez à ajouter mes deux cents. Généralement, en cas de doute, j'utilise rapidement la méthode GetHashCode () sur n'importe quel objet pour vérifier les identités. Donc pour ci-dessus -

    public class MyObject
{
    public int SimpleInt { get; set; }
}


class Program
{

    public static void RunChangeList()
    {
        var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } };
        Console.WriteLine("objs: {0}", objs.GetHashCode());
        Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode());
        var whatInt = ChangeToList(objs);
        Console.WriteLine("whatInt: {0}", whatInt.GetHashCode());
    }

    public static int ChangeToList(List<MyObject> objects)
    {
        Console.WriteLine("objects: {0}", objects.GetHashCode());
        Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode());
        var objectList = objects.ToList();
        Console.WriteLine("objectList: {0}", objectList.GetHashCode());
        Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode());
        objectList[0].SimpleInt = 5;
        return objects[0].SimpleInt;

    }

    private static void Main(string[] args)
    {
        RunChangeList();
        Console.ReadLine();
    }

Et réponds sur ma machine -

  • objs: 45653674
  • objs [0]: 41149443
  • objets: 45653674
  • objets [0]: 41149443
  • objectList: 39785641
  • objectList [0]: 41149443
  • whatInt: 5

Donc, essentiellement, l'objet que la liste porte reste le même dans le code ci-dessus. J'espère que l'approche aide.

vibhu
la source
4

Je pense que cela équivaut à demander si ToList fait une copie profonde ou superficielle. Comme ToList n'a aucun moyen de cloner MyObject, il doit faire une copie superficielle, de sorte que la liste créée contient les mêmes références que l'original, le code renvoie donc 5.

Timores
la source
2

ToList créera une toute nouvelle liste.

Si les éléments de la liste sont des types valeur, ils seront directement mis à jour, s'il s'agit de types référence, toutes les modifications seront reflétées dans les objets référencés.

Oded
la source
2

Dans le cas où l'objet source est un vrai IEnumerable (c'est-à-dire pas simplement une collection empaquetée et comme énumérable), ToList () ne peut PAS retourner les mêmes références d'objet que dans le IEnumerable d'origine. Il renverra une nouvelle liste d'objets, mais ces objets peuvent ne pas être identiques ou même égaux aux objets générés par IEnumerable lorsqu'il est à nouveau énuméré

prmph
la source
1
 var objectList = objects.ToList();
  objectList[0].SimpleInt=5;

Cela mettra également à jour l'objet d'origine. La nouvelle liste contiendra des références aux objets qu'elle contient, tout comme la liste d'origine. Vous pouvez modifier les éléments l'un ou l'autre et la mise à jour se reflétera dans l'autre.

Maintenant, si vous mettez à jour une liste (en ajoutant ou en supprimant un élément) qui ne sera pas reflétée dans l'autre liste.

kemiller2002
la source
1

Je ne vois nulle part dans la documentation que ToList () est toujours garanti de renvoyer une nouvelle liste. Si un IEnumerable est une liste, il peut être plus efficace de vérifier cela et de renvoyer simplement la même liste.

Le souci est que parfois vous voudrez peut-être être absolument sûr que la liste renvoyée est! = À la liste d'origine. Étant donné que Microsoft ne documente pas que ToList renverra une nouvelle liste, nous ne pouvons pas être sûrs (à moins que quelqu'un ait trouvé cette documentation). Cela pourrait également changer à l'avenir, même si cela fonctionne maintenant.

new List (IEnumerable enumerablestuff) est garanti pour renvoyer une nouvelle List. J'utiliserais plutôt ceci.

Ben B.
la source
2
Il le dit dans la documentation ici: « La méthode ToList <TSource> (IEnumerable <TSource>) force l'évaluation immédiate de la requête et renvoie une List <T> qui contient les résultats de la requête. Vous pouvez ajouter cette méthode à votre requête afin d'obtenir une copie mise en cache des résultats de la requête. "La deuxième phrase réitère que toute modification apportée à la liste d'origine après l'évaluation de la requête n'a aucun effet sur la liste renvoyée.
Raymond Chen
1
@RaymondChen Ceci est vrai pour IEnumerables, mais pas pour les listes. Bien qu'il ToListsemble créer une nouvelle référence d'objet de liste lorsqu'il est appelé sur a List, BenB a raison de dire que ce n'est pas garanti par la documentation MS.
Teejay
@RaymondChen Selon la source ChrisS, IEnumerable<>.ToList()est en fait implémenté en tant que new List<>(source)et il n'y a pas de remplacement spécifique pour List<>, donc List<>.ToList()renvoie effectivement une nouvelle référence d'objet de liste. Mais encore une fois, selon la documentation MS, il n'y a aucune garantie que cela ne changera pas à l'avenir, bien que cela cassera probablement la plupart du code.
Teejay