วิธีที่ดีที่สุดในการโคลน / คัดลอก. NET generic Dictionary <string, T> คืออะไร


211

ฉันมีพจนานุกรมทั่วไปDictionary<string, T>ที่ฉันต้องการสร้าง Clone () ของ .. ข้อเสนอแนะใด ๆ

คำตอบ:


184

ตกลงคำตอบ. NET 2.0:

หากคุณไม่จำเป็นต้องทำการโคลนนิ่งค่าคุณสามารถใช้ Constructor Overload เพื่อ Dictionary ซึ่งใช้ IDictionary ที่มีอยู่ (คุณสามารถระบุเครื่องมือเปรียบเทียบเป็นพจนานุกรมเปรียบเทียบที่มีอยู่ได้เช่นกัน)

หากคุณไม่จำเป็นต้องโคลนค่าที่คุณสามารถใช้อะไรเช่นนี้

public static Dictionary<TKey, TValue> CloneDictionaryCloningValues<TKey, TValue>
   (Dictionary<TKey, TValue> original) where TValue : ICloneable
{
    Dictionary<TKey, TValue> ret = new Dictionary<TKey, TValue>(original.Count,
                                                            original.Comparer);
    foreach (KeyValuePair<TKey, TValue> entry in original)
    {
        ret.Add(entry.Key, (TValue) entry.Value.Clone());
    }
    return ret;
}

แน่นอนว่าต้องอาศัยTValue.Clone()การโคลนนิ่งที่ลึกล้ำเช่นกัน


ฉันคิดว่านั่นเป็นเพียงการทำสำเนาค่าพจนานุกรมแบบตื้นเท่านั้น entry.Valueค่าอาจจะยังอีก [ย่อย] คอลเลกชัน
ChrisW

6
@ChrisW: ก็ขอให้แต่ละค่าโคลน - มันขึ้นอยู่กับClone()วิธีการไม่ว่าจะลึกหรือตื้น ฉันได้เพิ่มบันทึกย่อลงในเอฟเฟกต์นั้นแล้ว
Jon Skeet

1
@SeedGanji: ถ้าค่าไม่จำเป็นต้องถูกโคลนการ "ใช้ constructor overload to Dictionary ซึ่งใช้ IDictionary ที่มีอยู่" เป็นเรื่องปกติและเป็นคำตอบของฉันแล้ว ถ้าค่าไม่ต้องมีการโคลนแล้วคำตอบที่คุณได้เชื่อมโยงกับการไม่ได้ช่วยที่ทุกคน
Jon Skeet

1
@ SaeedGanji: น่าจะดีใช่ (แน่นอนถ้า structs มีการอ้างอิงประเภทการอ้างอิงที่ไม่แน่นอนที่อาจยังคงเป็นปัญหา ... แต่หวังว่าไม่ใช่กรณี.)
จอนสกีต

1
@ SaeedGanji: ขึ้นอยู่กับว่าเกิดอะไรขึ้นอีก หากกระทู้อื่นอ่านจากพจนานุกรมต้นฉบับเท่านั้นฉันเชื่อว่ามันน่าจะดี หากมีสิ่งใดที่แก้ไขได้คุณจะต้องล็อคทั้งเธรดนั้นและเธรดการโคลนนิ่งเพื่อหลีกเลี่ยงสิ่งเหล่านี้ในเวลาเดียวกัน ConcurrentDictionaryหากคุณต้องการความปลอดภัยหัวข้อเมื่อใช้พจนานุกรมการใช้งาน
Jon Skeet

210

(หมายเหตุ: แม้ว่ารุ่นโคลนอาจมีประโยชน์สำหรับการคัดลอกตื้น ๆ ตัวสร้างที่ฉันพูดถึงในโพสต์อื่น ๆ เป็นตัวเลือกที่ดีกว่า)

คุณต้องการให้สำเนาลึกแค่ไหนและคุณใช้. NET รุ่นใด ฉันสงสัยว่า LINQ โทรไปที่ ToDictionary โดยระบุทั้งตัวเลือกคีย์และองค์ประกอบจะเป็นวิธีที่ง่ายที่สุดถ้าคุณใช้. NET 3.5

ตัวอย่างเช่นหากคุณไม่คำนึงถึงคุณค่าที่เป็นโคลนตื้น:

var newDictionary = oldDictionary.ToDictionary(entry => entry.Key,
                                               entry => entry.Value);

หากคุณได้บังคับให้ T ใช้งาน ICloneable แล้ว:

var newDictionary = oldDictionary.ToDictionary(entry => entry.Key, 
                                               entry => (T) entry.Value.Clone());

(สิ่งเหล่านี้ยังไม่ได้ทดสอบ แต่ควรใช้งานได้)


ขอบคุณสำหรับคำตอบของจอน จริง ๆ แล้วฉันใช้ v2.0 ของกรอบงาน
mikeymo

"entry => entry.Key, entry => entry.Value" คืออะไรในบริบทนี้ ฉันจะเพิ่มคีย์และค่าได้อย่างไร มันแสดงข้อผิดพลาดในตอนท้ายของฉัน
Pratik

2
@Pratik: พวกเขากำลังแสดงออกแลมบ์ดา - เป็นส่วนหนึ่งของ C # 3
Jon Skeet

2
ตามค่าเริ่มต้น ToDictionary ของ LINQ ไม่ได้คัดลอกเครื่องมือเปรียบเทียบ คุณพูดถึงการคัดลอกเครื่องมือเปรียบเทียบในคำตอบอื่นของคุณ แต่ฉันคิดว่าการโคลนรุ่นนี้ควรผ่านการเปรียบเทียบ
user420667

86
Dictionary<string, int> dictionary = new Dictionary<string, int>();

Dictionary<string, int> copy = new Dictionary<string, int>(dictionary);

5
พอยน์เตอร์ของค่ายังคงเหมือนเดิมหากคุณใช้การเปลี่ยนแปลงค่าในการคัดลอกการเปลี่ยนแปลงจะปรากฏในวัตถุพจนานุกรมด้วย
Fokko Driesprong

3
@FokkoDriesprong ไม่เป็นไรมันแค่คัดลอกคีย์ค่าคู่ในออบเจ็กต์ใหม่

17
สิ่งนี้ใช้ได้ดี - สร้างทั้งรหัสและค่า แน่นอนว่าจะใช้งานได้เฉพาะในกรณีที่ค่าไม่ใช่ประเภทการอ้างอิงหากค่าเป็นประเภทการอ้างอิงก็จะใช้สำเนาของคีย์เป็นสำเนาที่ตื้นเท่านั้น
Contango

1
@Contango ดังนั้นในกรณีนี้เนื่องจากสตริงและ int ไม่ใช่ประเภทอ้างอิงมันจะทำงานใช่มั้ย
MonsterMMORPG

3
@ UğurAldanmazคุณลืมที่จะทดสอบการเปลี่ยนแปลงจริงกับวัตถุอ้างอิงคุณเพียงทดสอบการเปลี่ยนค่าพอยน์เตอร์ในพจนานุกรมที่โคลนซึ่งเห็นได้ชัดว่าทำงานได้ แต่การทดสอบของคุณจะล้มเหลวหากคุณเพิ่งเปลี่ยนคุณสมบัติของวัตถุทดสอบของคุณเช่น: dotnetfiddle.net / xmPPKr
Jens

10

สำหรับ. NET 2.0 คุณสามารถใช้คลาสที่สืบทอดDictionaryและใช้งานICloneableได้

public class CloneableDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TValue : ICloneable
{
    public IDictionary<TKey, TValue> Clone()
    {
        CloneableDictionary<TKey, TValue> clone = new CloneableDictionary<TKey, TValue>();

        foreach (KeyValuePair<TKey, TValue> pair in this)
        {
            clone.Add(pair.Key, (TValue)pair.Value.Clone());
        }

        return clone;
    }
}

จากนั้นคุณสามารถโคลนพจนานุกรมได้ง่ายๆโดยการเรียกCloneวิธีการ ของหลักสูตรการดำเนินงานนี้ต้องว่าประเภทค่าของการดำเนินการพจนานุกรมICloneableแต่อย่างอื่นการใช้งานทั่วไปไม่จริงที่ทุกคน


8

มันใช้งานได้ดีสำหรับฉัน

 // assuming this fills the List
 List<Dictionary<string, string>> obj = this.getData(); 

 List<Dictionary<string, string>> objCopy = new List<Dictionary<string, string>>(obj);

ดังที่ Tomer Wolberg อธิบายไว้ในความคิดเห็นสิ่งนี้ไม่ทำงานหากประเภทค่าเป็นคลาสที่ไม่แน่นอน


1
ต้องใช้ upvotes อย่างจริงจัง! หากพจนานุกรมต้นฉบับอ่านได้อย่างเดียวสิ่งนี้จะยังคงทำงานอยู่: var newDict = readonlyDict.ToDictionary (kvp => kvp.Key, kvp => kvp.Value)
Stephan

2
ไม่สามารถใช้งานได้หากประเภทค่าเป็นคลาสที่ไม่แน่นอน
Tomer Wolberg

5

คุณสามารถใช้การทำให้เป็นอนุกรมได้เสมอ คุณสามารถทำให้วัตถุเป็นอันดับ ที่จะให้คุณคัดลอกพจนานุกรมและรายการทั้งหมดในนั้น ตอนนี้คุณสามารถสร้างสำเนาลึกของวัตถุใด ๆ ที่ทำเครื่องหมายเป็น [ต่อเนื่อง] โดยไม่ต้องเขียนโค้ดพิเศษใด ๆ

ต่อไปนี้เป็นสองวิธีที่จะใช้การทำให้เป็นอันดับแบบไบนารี หากคุณใช้วิธีการเหล่านี้คุณเพียงแค่โทร

object deepcopy = FromBinary(ToBinary(yourDictionary));

public Byte[] ToBinary()
{
  MemoryStream ms = null;
  Byte[] byteArray = null;
  try
  {
    BinaryFormatter serializer = new BinaryFormatter();
    ms = new MemoryStream();
    serializer.Serialize(ms, this);
    byteArray = ms.ToArray();
  }
  catch (Exception unexpected)
  {
    Trace.Fail(unexpected.Message);
    throw;
  }
  finally
  {
    if (ms != null)
      ms.Close();
  }
  return byteArray;
}

public object FromBinary(Byte[] buffer)
{
  MemoryStream ms = null;
  object deserializedObject = null;

  try
  {
    BinaryFormatter serializer = new BinaryFormatter();
    ms = new MemoryStream();
    ms.Write(buffer, 0, buffer.Length);
    ms.Position = 0;
    deserializedObject = serializer.Deserialize(ms);
  }
  finally
  {
    if (ms != null)
      ms.Close();
  }
  return deserializedObject;
}

5

วิธีที่ดีที่สุดสำหรับฉันคือ:

Dictionary<int, int> copy= new Dictionary<int, int>(yourListOrDictionary);

3
นี่ไม่ใช่แค่การคัดลอกการอ้างอิงไม่ใช่ค่าเนื่องจาก Dictionary เป็นประเภทการอ้างอิงหรือไม่ นั่นหมายความว่าถ้าคุณเปลี่ยนค่าในหนึ่งมันจะเปลี่ยนค่าในอื่น ๆ ?
Goku

3

วิธีการทำให้เป็นอนุกรมของไบนารีทำงานได้ดี แต่ในการทดสอบของฉันมันแสดงให้เห็นว่าช้ากว่าการใช้โคลนที่ไม่ใช่การทำให้เป็นอนุกรม ทดสอบแล้วDictionary<string , List<double>>


คุณแน่ใจหรือไม่ว่าคุณได้ทำสำเนาแบบลึก? ทั้งสตริงและรายการจะต้องคัดลอกแบบลึก นอกจากนี้ยังมีข้อบกพร่องบางอย่างในรุ่นอนุกรมทำให้มันจะช้าในวิธีการที่เรียกว่ามีแทน จากนั้นในไบต์ [] จะถูกคัดลอกไปยัง MemStream ด้วยตนเองก่อน แต่สามารถส่งไปยังตัวสร้างได้ ToBinary()Serialize()thisyourDictionaryFromBinary()
ดาวพฤหัสบดี

1

นั่นคือสิ่งที่ช่วยฉันได้เมื่อฉันพยายามคัดลอกพจนานุกรม <string, string>

Dictionary<string, string> dict2 = new Dictionary<string, string>(dict);

โชคดี


ทำงานได้ดีสำหรับ. NET 4.6.1 นี่ควรเป็นคำตอบที่อัพเดทแล้ว
Tallal Kazmi

0

ลองใช้วิธีนี้หากคีย์ / ค่านั้นเป็น ICloneable:

    public static Dictionary<K,V> CloneDictionary<K,V>(Dictionary<K,V> dict) where K : ICloneable where V : ICloneable
    {
        Dictionary<K, V> newDict = null;

        if (dict != null)
        {
            // If the key and value are value types, just use copy constructor.
            if (((typeof(K).IsValueType || typeof(K) == typeof(string)) &&
                 (typeof(V).IsValueType) || typeof(V) == typeof(string)))
            {
                newDict = new Dictionary<K, V>(dict);
            }
            else // prepare to clone key or value or both
            {
                newDict = new Dictionary<K, V>();

                foreach (KeyValuePair<K, V> kvp in dict)
                {
                    K key;
                    if (typeof(K).IsValueType || typeof(K) == typeof(string))
                    {
                        key = kvp.Key;
                    }
                    else
                    {
                        key = (K)kvp.Key.Clone();
                    }
                    V value;
                    if (typeof(V).IsValueType || typeof(V) == typeof(string))
                    {
                        value = kvp.Value;
                    }
                    else
                    {
                        value = (V)kvp.Value.Clone();
                    }

                    newDict[key] = value;
                }
            }
        }

        return newDict;
    }

0

ตอบกลับโพสต์เก่า แต่ฉันพบว่ามีประโยชน์ในการห่อดังนี้

using System;
using System.Collections.Generic;

public class DeepCopy
{
  public static Dictionary<T1, T2> CloneKeys<T1, T2>(Dictionary<T1, T2> dict)
    where T1 : ICloneable
  {
    if (dict == null)
      return null;
    Dictionary<T1, T2> ret = new Dictionary<T1, T2>();
    foreach (var e in dict)
      ret[(T1)e.Key.Clone()] = e.Value;
    return ret;
  }

  public static Dictionary<T1, T2> CloneValues<T1, T2>(Dictionary<T1, T2> dict)
    where T2 : ICloneable
  {
    if (dict == null)
      return null;
    Dictionary<T1, T2> ret = new Dictionary<T1, T2>();
    foreach (var e in dict)
      ret[e.Key] = (T2)(e.Value.Clone());
    return ret;
  }

  public static Dictionary<T1, T2> Clone<T1, T2>(Dictionary<T1, T2> dict)
    where T1 : ICloneable
    where T2 : ICloneable
  {
    if (dict == null)
      return null;
    Dictionary<T1, T2> ret = new Dictionary<T1, T2>();
    foreach (var e in dict)
      ret[(T1)e.Key.Clone()] = (T2)(e.Value.Clone());
    return ret;
  }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.