簡體   English   中英

如何在 C# 中克隆通用列表?

[英]How do I clone a generic list in C#?

我在 C# 中有一個對象的通用列表,並希望克隆該列表。 列表中的項目是可克隆的,但似乎沒有執行list.Clone()的選項。

有沒有簡單的方法解決這個問題?

如果您的元素是值類型,那么您可以這樣做:

List<YourType> newList = new List<YourType>(oldList);

但是,如果它們是引用類型並且您想要深層復制(假設您的元素正確實現ICloneable ),則可以執行以下操作:

List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);

oldList.ForEach((item) =>
    {
        newList.Add((ICloneable)item.Clone());
    });

顯然,替換上述泛型中的ICloneable並使用實現ICloneable任何元素類型進行轉換。

如果您的元素類型不支持ICloneable但確實有一個復制構造函數,您可以這樣做:

List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);

oldList.ForEach((item)=>
    {
        newList.Add(new YourType(item));
    });

就個人而言,我會避免使用ICloneable因為需要保證所有成員的深層副本。 相反,我建議拷貝構造函數或工廠方法一樣YourType.CopyFrom(YourType itemToCopy)返回的新實例YourType

這些選項中的任何一個都可以由方法(擴展或其他)包裝。

您可以使用擴展方法。

static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}

對於淺拷貝,您可以改用泛型 List 類的 GetRange 方法。

List<int> oldList = new List<int>( );
// Populate oldList...

List<int> newList = oldList.GetRange(0, oldList.Count);

引自: 泛型食譜

public static object DeepClone(object obj) 
{
    object objResult = null;

    using (var ms = new MemoryStream())
    {
        var bf = new BinaryFormatter();
        bf.Serialize(ms, obj);

        ms.Position = 0;
        objResult = bf.Deserialize(ms);
     }

     return objResult;
}

這是使用 C# 和 .NET 2.0 實現的一種方法。 您的對象需要是[Serializable()] 目標是丟失所有引用並構建新引用。

要克隆一個列表,只需調用 .ToList()。 這將創建一個淺拷貝。

Microsoft (R) Roslyn C# Compiler version 2.3.2.62116
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> var x = new List<int>() { 3, 4 };
> var y = x.ToList();
> x.Add(5)
> x
List<int>(3) { 3, 4, 5 }
> y
List<int>(2) { 3, 4 }
> 

稍作修改后,您還可以克隆:

public static T DeepClone<T>(T obj)
{
    T objResult;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, obj);
        ms.Position = 0;
        objResult = (T)bf.Deserialize(ms);
    }
    return objResult;
}

除非您需要對List<T>的每個對象進行實際克隆,否則克隆List<T>的最佳方法是創建一個以舊列表作為集合參數的新列表。

List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);

myList更改(例如插入或刪除)不會影響cloneOfMyList ,反之亦然。

然而,兩個列表包含的實際對象仍然相同。

如果您只關心值類型...

你知道類型:

List<int> newList = new List<int>(oldList);

如果您之前不知道類型,則需要一個輔助函數:

List<T> Clone<T>(IEnumerable<T> oldList)
{
    return newList = new List<T>(oldList);
}

剛剛:

List<string> myNewList = Clone(myOldList);

使用 AutoMapper(或您喜歡的任何映射庫)進行克隆非常簡單且易於維護。

定義您的映射:

Mapper.CreateMap<YourType, YourType>();

施展魔法:

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);

如果您已經在項目中引用了 Newtonsoft.Json 並且您的對象是可序列化的,則可以始終使用:

List<T> newList = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(listToCopy))

可能不是最有效的方法,但除非您執行 100 次或 1000 次,否則您甚至可能不會注意到速度差異。

無需將類標記為可序列化,在我們的測試中,使用 Newtonsoft JsonSerializer 甚至比使用 BinaryFormatter 更快。 具有可用於每個對象的擴展方法。

標准 .NET JavascriptSerializer 選項:

public static T DeepCopy<T>(this T value)
{
    JavaScriptSerializer js = new JavaScriptSerializer();

    string json = js.Serialize(value);

    return js.Deserialize<T>(json);
}

使用Newtonsoft JSON 的更快選項:

public static T DeepCopy<T>(this T value)
{
    string json = JsonConvert.SerializeObject(value);

    return JsonConvert.DeserializeObject<T>(json);
}

如果有人讀過這個,我會很幸運……但是為了不在我的 Clone 方法中返回類型對象的列表,我創建了一個接口:

public interface IMyCloneable<T>
{
    T Clone();
}

然后我指定了擴展名:

public static List<T> Clone<T>(this List<T> listToClone) where T : IMyCloneable<T>
{
    return listToClone.Select(item => (T)item.Clone()).ToList();
}

這是我的 A/V 標記軟件中接口的實現。 我想讓我的 Clone() 方法返回一個 VidMark 列表(而 ICloneable 接口想讓我的方法返回一個對象列表):

public class VidMark : IMyCloneable<VidMark>
{
    public long Beg { get; set; }
    public long End { get; set; }
    public string Desc { get; set; }
    public int Rank { get; set; } = 0;

    public VidMark Clone()
    {
        return (VidMark)this.MemberwiseClone();
    }
}

最后,在類中使用擴展:

private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;

//Other methods instantiate and fill the lists

private void SetUndoVidMarks()
{
    _UndoVidMarks = _VidMarks.Clone();
}

有人喜歡嗎? 有什么改進嗎?

    public List<TEntity> Clone<TEntity>(List<TEntity> o1List) where TEntity : class , new()
    {
        List<TEntity> retList = new List<TEntity>();
        try
        {
            Type sourceType = typeof(TEntity);
            foreach(var o1 in o1List)
            {
                TEntity o2 = new TEntity();
                foreach (PropertyInfo propInfo in (sourceType.GetProperties()))
                {
                    var val = propInfo.GetValue(o1, null);
                    propInfo.SetValue(o2, val);
                }
                retList.Add(o2);
            }
            return retList;
        }
        catch
        {
            return retList;
        }
    }
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();

對於深層復制,ICloneable 是正確的解決方案,但這里有一個類似的方法,使用構造函數而不是 ICloneable 接口來處理 ICloneable。

public class Student
{
  public Student(Student student)
  {
    FirstName = student.FirstName;
    LastName = student.LastName;
  }

  public string FirstName { get; set; }
  public string LastName { get; set; }
}

// wherever you have the list
List<Student> students;

// and then where you want to make a copy
List<Student> copy = students.Select(s => new Student(s)).ToList();

您將需要以下庫來制作副本

using System.Linq

您也可以使用 for 循環代替 System.Linq,但 Linq 使其簡潔明了。 同樣,您可以按照其他答案的建議進行操作並制作擴展方法等,但這都不是必需的。

public static Object CloneType(Object objtype)
{
    Object lstfinal = new Object();

    using (MemoryStream memStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
        binaryFormatter.Serialize(memStream, objtype); memStream.Seek(0, SeekOrigin.Begin);
        lstfinal = binaryFormatter.Deserialize(memStream);
    }

    return lstfinal;
}
public class CloneableList<T> : List<T>, ICloneable where T : ICloneable
{
  public object Clone()
  {
    var clone = new List<T>();
    ForEach(item => clone.Add((T)item.Clone()));
    return clone;
  }
}

您可以使用擴展方法:

namespace extension
{
    public class ext
    {
        public static List<double> clone(this List<double> t)
        {
            List<double> kop = new List<double>();
            int x;
            for (x = 0; x < t.Count; x++)
            {
                kop.Add(t[x]);
            }
            return kop;
        }
   };

}

例如,您可以使用它們的值類型成員克隆所有對象,考慮這個類:

public class matrix
{
    public List<List<double>> mat;
    public int rows,cols;
    public matrix clone()
    { 
        // create new object
        matrix copy = new matrix();
        // firstly I can directly copy rows and cols because they are value types
        copy.rows = this.rows;  
        copy.cols = this.cols;
        // but now I can no t directly copy mat because it is not value type so
        int x;
        // I assume I have clone method for List<double>
        for(x=0;x<this.mat.count;x++)
        {
            copy.mat.Add(this.mat[x].clone());
        }
        // then mat is cloned
        return copy; // and copy of original is returned 
    }
};

注意:如果您對復制(或克隆)進行任何更改,它不會影響原始對象。

如果您需要具有相同容量的克隆列表,您可以試試這個:

public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}

在這種情況下,對於淺拷貝,使用強制轉換可能會有所幫助:

IList CloneList(IList list)
{
    IList result;
    result = (IList)Activator.CreateInstance(list.GetType());
    foreach (object item in list) result.Add(item);
    return result;
}

適用於通用列表:

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);

我為自己做了一些擴展,它轉換了不實現 IClonable 的項目的 ICollection

static class CollectionExtensions
{
    public static ICollection<T> Clone<T>(this ICollection<T> listToClone)
    {
        var array = new T[listToClone.Count];
        listToClone.CopyTo(array,0);
        return array.ToList();
    }
}

我使用 automapper 來復制一個對象。 我只是設置了一個映射,將一個對象映射到自身。 您可以按您喜歡的任何方式包裝此操作。

http://automapper.codeplex.com/

您也可以簡單地使用ToArray將列表轉換為數組,然后使用Array.Clone(...)克隆數組。 根據您的需要,Array 類中包含的方法可以滿足您的需要。

有一種簡單的方法可以使用 JSON 序列化器和反序列化器在 C# 中克隆對象。

您可以創建一個擴展類:

using Newtonsoft.Json;

static class typeExtensions
{
    [Extension()]
    public static T jsonCloneObject<T>(T source)
    {
    string json = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(json);
    }
}

克隆和對象:

obj clonedObj = originalObj.jsonCloneObject;

以下代碼應該轉移到一個列表中,更改最少。

基本上它的工作原理是在每個連續循環中插入一個更大范圍的新隨機數。 如果已經存在與它相同或更高的數字,則將這些隨機數向上移動一個,以便它們轉移到新的更大范圍的隨機索引中。

// Example Usage
int[] indexes = getRandomUniqueIndexArray(selectFrom.Length, toSet.Length);

for(int i = 0; i < toSet.Length; i++)
    toSet[i] = selectFrom[indexes[i]];


private int[] getRandomUniqueIndexArray(int length, int count)
{
    if(count > length || count < 1 || length < 1)
        return new int[0];

    int[] toReturn = new int[count];
    if(count == length)
    {
        for(int i = 0; i < toReturn.Length; i++) toReturn[i] = i;
        return toReturn;
    }

    Random r = new Random();
    int startPos = count - 1;
    for(int i = startPos; i >= 0; i--)
    {
        int index = r.Next(length - i);
        for(int j = startPos; j > i; j--)
            if(toReturn[j] >= index)
                toReturn[j]++;
        toReturn[i] = index;
    }

    return toReturn;
}

另一件事:你可以使用反射。 如果您正確緩存它,那么它將在 5.6 秒內克隆 1,000,000 個對象(遺憾的是,內部對象需要 16.4 秒)。

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Person
{
       ...
      Job JobDescription
       ...
}

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Job
{...
}

private static readonly Type stringType = typeof (string);

public static class CopyFactory
{
    static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

    private static readonly MethodInfo CreateCopyReflectionMethod;

    static CopyFactory()
    {
        CreateCopyReflectionMethod = typeof(CopyFactory).GetMethod("CreateCopyReflection", BindingFlags.Static | BindingFlags.Public);
    }

    public static T CreateCopyReflection<T>(T source) where T : new()
    {
        var copyInstance = new T();
        var sourceType = typeof(T);

        PropertyInfo[] propList;
        if (ProperyList.ContainsKey(sourceType))
            propList = ProperyList[sourceType];
        else
        {
            propList = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            ProperyList.Add(sourceType, propList);
        }

        foreach (var prop in propList)
        {
            var value = prop.GetValue(source, null);
            prop.SetValue(copyInstance,
                value != null && prop.PropertyType.IsClass && prop.PropertyType != stringType ? CreateCopyReflectionMethod.MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { value }) : value, null);
        }

        return copyInstance;
    }

我通過使用 Watcher 類以一種簡單的方式對其進行了測量。

 var person = new Person
 {
     ...
 };

 for (var i = 0; i < 1000000; i++)
 {
    personList.Add(person);
 }
 var watcher = new Stopwatch();
 watcher.Start();
 var copylist = personList.Select(CopyFactory.CreateCopyReflection).ToList();
 watcher.Stop();
 var elapsed = watcher.Elapsed;

結果:使用內部對象 PersonInstance - 16.4,PersonInstance = null - 5.6

CopyFactory 只是我的測試類,我有十幾個測試,包括表達式的使用。 您可以在擴展或其他任何形式中以另一種形式實現這一點。 不要忘記緩存。

我還沒有測試序列化,但我懷疑有一百萬個類的改進。 我會嘗試一些快速的protobuf/newton。

PS:為了閱讀簡單,我這里只用了auto-property。 我可以使用 FieldInfo 進行更新,或者您應該自己輕松實現。

我最近使用開箱即用的 DeepClone 功能測試了Protocol Buffers序列化器。 它在一百萬個簡單對象上以 4.2 秒獲勝,但是對於內部對象,它以 7.4 秒的結果獲勝。

Serializer.DeepClone(personList);

總結:如果您無權訪問這些類,那么這將有所幫助。 否則,它取決於對象的數量。 我認為您可以使用最多 10,000 個對象的反射(可能會少一點),但除此之外,Protocol Buffers 序列化程序的性能會更好。

對於深度克隆,我使用反射如下:

public List<T> CloneList<T>(IEnumerable<T> listToClone) {
    Type listType = listToClone.GetType();
    Type elementType = listType.GetGenericArguments()[0];
    List<T> listCopy = new List<T>();
    foreach (T item in listToClone) {
        object itemCopy = Activator.CreateInstance(elementType);
        foreach (PropertyInfo property in elementType.GetProperties()) {
            elementType.GetProperty(property.Name).SetValue(itemCopy, property.GetValue(item));
        }
        listCopy.Add((T)itemCopy);
    }
    return listCopy;
}

您可以交替使用 List 或 IEnumerable。

我有一個問題,我認為是我組合列表的方法有問題,因為它曾經工作過,但再也沒有了。

結果完全沒有關系,花了我很長時間才解決,因為我走錯了路。

如果您嘗試組合列表並通過 bindingsource 推送它們,則上述所有方法都有效,但您需要使用 ObservableCollection 而不是列表。

試試這個:

var res = ConvertModel<sourceModel, responseModle>(model);

 public static T2 ConvertModel<T1, T2>(T1 data) where T1 : class, new()
 {
     return DeserializeObject<T2>(SerializeObject(data));
 } 
 public static string SerializeObject<T>(T objectData)
 {
    string defaultJson = JsonConvert.SerializeObject(objectData);
    return defaultJson;
 }

public static T DeserializeObject<T>(string json)
{
   T obj = default(T);
   obj = JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
   return obj;
}

如果我需要收藏的深層副本,我喜歡這樣的方法

public static IEnumerable<T> DeepCopy<T>(this IEnumerable<T> collectionToDeepCopy)
{
    var serializedCollection = JsonConvert.SerializeObject(collectionToDeepCopy);
    return JsonConvert.DeserializeObject<IEnumerable<T>>(serializedCollection);
}

您可以使用List<T>.ConvertAll(Converter<T, T>)方法創建一個包含原始列表所有元素的新列表,並使用返回輸入值的轉換 function。

List<int> originalList = new List<int> { 1, 2, 3, 4, 5 };
List<int> clonedList = new List<int>(originalList.ConvertAll(x => x));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM