ToList()-新しいリストを作成しますか?


176

クラスがあるとしましょう

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

そして、私はを持っていてList<MyObject>ToList()それを変更してからの1つをSimpleInt変更すると、私の変更が元のリストに反映されます。つまり、次のメソッドの出力はどうなるでしょうか。

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;

}

どうして?

P / S:それを見つけることが明らかであると思われる場合は申し訳ありません。でも今はコンパイラを持っていない...


それを言い換える一つの方法は.ToList()浅いコピーを作成することです。参照はコピーされますが、新しい参照は依然として元の参照が指しているのと同じインスタンスを指します。あなたはそれについて考えるとき、ToListいずれかを作成することができないnew MyObject()ときMyObjectであるclassタイプは。
Jeppe Stig Nielsen

回答:


217

はい、ToList新しいリストを作成しますが、この場合MyObjectは参照タイプであるため、新しいリストには元のリストと同じオブジェクトへの参照が含まれます。

SimpleInt新しいリストで参照されるオブジェクトのプロパティを更新すると、元のリストの対応するオブジェクトにも影響します。

(ではなくMyObjectとして宣言された場合、新しいリストには元のリストの要素のコピーが含まれ、新しいリストの要素のプロパティを更新しても元のリストの対応する要素には影響しません。)structclass


1
また、Listof構造体では、などの割り当てobjectList[0].SimpleInt=5は許可されません(C#コンパイル時エラー)。これは、リストインデクサーのgetアクセサーの戻り値が変数ではないためです(これは構造体の値の返されたコピーです)。そのため、割り当て式でメンバーを設定する.SimpleIntことはできません(保持されないコピーが変更されます)。 。とにかく、誰が可変構造を使用していますか?
Jeppe Stig Nielsen

2
@Jeppe Stig Nielson:はい。同様に、コンパイラーものようなことをやめますforeach (var s in listOfStructs) { s.SimpleInt = 42; }本当にあなたのような何かをしようとすると厄介な落とし穴があるlistOfStructs.ForEach(s => s.SimpleInt = 42):コンパイラは、例外なしに、コードの実行を可能にするが、リスト内の構造体は変わらないままになります!
LukeH 2016

62

リフレクターのソースから:

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

つまり、元のリストは更新されません(つまり、追加または削除)が、参照されるオブジェクトは更新されます。


32

ToList 常に新しいリストを作成します。これは、コレクションに対するその後の変更を反映しません。

ただし、オブジェクト自体への変更が反映されます(変更可能な構造体でない限り)。

つまり、元のリストのオブジェクトを別のオブジェクトに置き換えてToListも、には最初のオブジェクトが含まれています。
ただし、元のリストのオブジェクトの1つを変更した場合ToListでも、には同じ(変更された)オブジェクトが含まれます。


12

受け入れられた回答は、彼の例に基づいてOPの質問に正しく対応しています。ただし、ToList具体的なコレクションに適用される場合にのみ適用されます。(遅延実行のため)ソースシーケンスの要素がまだインスタンス化されていない場合は保持されません。後者の場合、呼び出すたびにToList(またはシーケンスを列挙するたびに)新しいアイテムのセットを取得する可能性があります。

この動作を示すためにOPのコードを変更したものを次に示します。

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

上記のコードは不自然に見えるかもしれませんが、この動作は他のシナリオでは微妙なバグとして表示されることがあります。タスクが繰り返し発生する原因となる状況については、他の例を参照しください。


11

はい、それは新しいリストを作成します。これは仕様によるものです。

リストには、元の列挙可能なシーケンスと同じ結果が含まれますが、永続的な(メモリ内の)コレクションに具体化されます。これにより、シーケンスを再計算するコストをかけずに、結果を複数回使用できます。

LINQシーケンスの優れた点は、それらが合成可能であることです。多くの場合、IEnumerable<T>得られるのは、複数のフィルタリング、順序付け、および/または投影操作を組み合わせた結果です。のような拡張メソッドToList()ToArray()使用すると、計算されたシーケンスを標準のコレクションに変換できます。


6

新しいリストが作成されますが、リスト内のアイテムは元のアイテムへの参照です(元のリストと同様)。リスト自体への変更は独立していますが、アイテムへの変更は両方のリストの変更を検出します。


6

この古い投稿に偶然遭遇し、私の2セントを追加することを考えました。一般的に、疑問がある場合は、任意のオブジェクトでGetHashCode()メソッドを使用してIDを確認します。したがって、上記の場合-

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

そして私のマシンで答える-

  • オブジェクト:45653674
  • objs [0]:41149443
  • オブジェクト:45653674
  • オブジェクト[0]:41149443
  • objectList:39785641
  • objectList [0]:41149443
  • whatInt:5

したがって、本質的に、リストが保持するオブジェクトは上記のコードでも同じです。アプローチが役立つことを願っています。


4

これは、ToListが深いコピーを行うか、浅いコピーを行うかを尋ねることと同じだと思います。ToListにはMyObjectを複製する方法がないため、浅いコピーを行う必要があるため、作成されたリストには元のリストと同じ参照が含まれているため、コードは5を返します。


2

ToList 新しいリストを作成します。

リスト内の項目が値タイプの場合は直接更新され、参照タイプの場合は、変更はすべて参照オブジェクトに反映されます。


2

ソースオブジェクトが真のIEnumerableである(つまり、列挙型としてパッケージ化されたコレクションだけではない)場合、ToList()は元のIEnumerableと同じオブジェクト参照を返さないことがあります。オブジェクトの新しいリストが返されますが、それらのオブジェクトは、IEnumerableが再度列挙されたときに生成されたオブジェクトと同じではないか、等しい場合さえあります。


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

これにより、元のオブジェクトも更新されます。新しいリストには、元のリストと同じように、リストに含まれるオブジェクトへの参照が含まれます。どちらの要素も変更でき、更新はもう一方にも反映されます。

ここで、他のリストに反映されないリストを更新(アイテムの追加または削除)した場合。


1

ドキュメントのどこにも、ToList()が常に新しいリストを返すことが保証されていることはありません。IEnumerableがリストの場合、これを確認して同じリストを返すほうが効率的です。

心配なのは、返されたリストが元のリストに対して!=であることを完全に確認したい場合があることです。マイクロソフトはToListが新しいリストを返すことを文書化していないため、確信が持てません(誰かがその文書を見つけた場合を除きます)。また、現在は機能していても、将来的には変更される可能性があります。

new List(IEnumerable enumerablestuff)は新しいリストを返すことが保証されています。代わりにこれを使用します。


2
それはここのドキュメントでそれを言います: " ToList <TSource>(IEnumerable <TSource>)メソッドは即時のクエリ評価を強制し、クエリ結果を含むList <T>を返します。取得するためにこのメソッドをクエリに追加できますクエリ結果のキャッシュコピー。2番目の文は、クエリが評価された後に元のリストに加えられた変更は、返されたリストに影響を与えないことを繰り返します。
Raymond Chen

1
@RaymondChenこれはIEnumerableに当てはまりますが、リストには当てはまりません。でToList呼び出されたときに新しいリストオブジェクト参照を作成するように見えますがList、BenBは、これがMSのドキュメントで保証されていないと言っているときに正しいです。
Teejay

@RaymondChen ChrisSソースによるIEnumerable<>.ToList()と、実際にはとして実装されてnew List<>(source)おりList<>、に対する特定のオーバーライドはないため、List<>.ToList()実際には新しいリストオブジェクト参照を返します。しかし、繰り返しになりますが、MSのドキュメントに従って、これが将来変更されないという保証はありませんが、コードの大部分が壊れる可能性があります。
Teejay
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.