ChatGPT解决这个技术问题 Extra ChatGPT

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

我在 C# 中有一个通用的对象列表,并希望克隆该列表。列表中的项目是可克隆的,但似乎没有执行 list.Clone() 的选项。

有没有简单的方法解决这个问题?

你应该说你是在寻找深拷贝还是浅拷贝
什么是深拷贝和浅拷贝?
@orip clone() 根据定义不是深拷贝吗?在 C# 中,您可以使用 = 轻松传递指针,我想。
@Chris 浅拷贝比指针拷贝更深一层。例如,列表的浅表副本将具有相同的元素,但将是不同的列表。

J
Jeff Yates

如果您的元素是值类型,那么您可以这样做:

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 实例。

这些选项中的任何一个都可以由方法(扩展或其他)包装。


我认为 List.ConvertAll 可能看起来比创建一个新列表并执行 foreach+add 更好。
@Dimitri:不,那不是真的。问题是,当定义 ICloneable 时,定义从未说明克隆是深还是浅,因此您无法确定对象实现时将执行哪种类型的克隆操作。这意味着如果您想要对 List<T> 进行深度克隆,则必须在没有 ICloneable 的情况下进行,以确保它是深度复制。
为什么不使用 AddRange 方法? (newList.AddRange(oldList.Select(i => i.Clone())newList.AddRange(oldList.Select(i => new YourType(i)
@phoog:我认为扫描代码时它的可读性/可理解性有点差,仅此而已。可读性对我来说是赢家。
@JeffYates:一个考虑不足的问题是,通常只有在存在某些会改变它们的执行路径时才需要复制事物。不可变类型持有对可变类型实例的引用是很常见的,但永远不要将该实例暴露给任何会改变它的东西。对永远不会改变的事物进行不必要的复制有时可能会导致主要的性能消耗,从而将内存使用量增加几个数量级。
n
nawfal

您可以使用扩展方法。

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.ConvertAll 可能会在更快的时间内完成此操作,因为它可以为列表预先分配整个数组,而不必一直调整大小。
@MichaelGG,如果您不想转换而只想克隆/复制列表中的项目怎么办?这行得通吗? || var clonedList = ListOfStrings.ConvertAll(p => p);
@IbrarMumtaz:这与 var clonedList = new List(ListOfStrings); 相同
不错的解决方案!顺便说一句,我更喜欢 public static List CLone... 在这种情况下它更有用,因为不需要进一步转换: List cloned = listToClone.Clone();
这不是答案的一半,因为它依赖于 ICloneable 的实现,而这将是问题的重要部分?
J
Jochem

对于浅拷贝,您可以改用通用 List 类的 GetRange 方法。

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

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

引自:Generics Recipes


您还可以通过使用 List 的构造函数指定要从中复制的 List 来实现此目的。例如 var shallowClonedList = new List(originalList);
我经常使用List<int> newList = oldList.ToList()。一样的效果。但是,在我看来,Arkiliknam 的解决方案最适合可读性。
@DanBechard 多年后,但我更喜欢 ToList 因为它避免了所有冗余 - 我想知道哪个实际上性能更高......查了一下。查找列表 ToList 调用 new List<T> 最终将使用 Array.CopyTo,所以大致相同。
A
AustinWBryan
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()]。目标是丢失所有引用并建立新的引用。


+1 - 我喜欢这个答案 - 它快速,肮脏,讨厌且非常有效。我在 silverlight 中使用过,并使用了 DataContractSerializer,因为 BinarySerializer 不可用。当您可以做到这一点时,谁需要编写对象克隆代码页面? :)
我喜欢这个。虽然“正确”地做事很好,但快速而肮脏的事情通常会派上用场。
快的!但是:为什么脏?
这种深度克隆并且快速简便。仔细阅读此页面上的其他建议。我尝试了几个,但它们没有深度克隆。
唯一的消极方面,如果你可以这么称呼的话,就是你的类必须被标记为 Serializable 才能工作。
X
Xavier John

要克隆一个列表,只需调用 .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 }
> 

一个小警告这是一个浅拷贝......这将创建两个列表对象,但里面的对象将是相同的。即更改一个属性将更改原始列表中的相同对象/属性。
P
Peter Mortensen

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

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

不要忘记 T 应该是可序列化的,否则会得到 System.Runtime.Serialization.SerializationException。
好答案。 提示:您可以添加 if (!obj.GetType().IsSerializable) return default(T); 作为防止异常的第一条语句。如果您将其更改为扩展方法,您甚至可以使用像 var b = a?.DeepClone(); 这样的 Elvis 运算符(例如给定 var a = new List<string>() { "a", "b" };)。
J
Jader Feijo

除非您需要实际克隆 List<T> 中的每个对象,否则克隆列表的最佳方法是创建一个新列表,并将旧列表作为集合参数。

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

myList 的更改(例如插入或删除)不会影响 cloneOfMyList,反之亦然。

然而,这两个列表包含的实际对象仍然相同。


我同意 user49126,我看到它是一个浅拷贝,对一个列表所做的更改会反映在另一个列表中。
@Seidleroni,你错了。对列表项所做的更改会影响另一个列表,而列表本身的更改则不会。
这是浅拷贝。
这怎么是浅拷贝?
@WellingtonZanelli 刚刚确认从 myList 中删除一个元素也会从 cloneOfMyList 中删除它。
D
Derek Liang

使用 AutoMapper(或您喜欢的任何映射库)进行克隆非常简单且易于维护。

定义你的映射:

Mapper.CreateMap<YourType, YourType>();

施展魔法:

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

L
Lucas B

如果您只关心值类型...

你知道类型:

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

这不会克隆元素。
P
ProfNimrod

如果您已经在项目中引用了 Newtonsoft.Json 并且您的对象是可序列化的,您可以始终使用:

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

可能不是最有效的方法,但除非您做 100 次或 1000 次,否则您甚至可能不会注意到速度差异。


这不是关于速度差异,而是关于可读性。如果我遇到这行代码,我会拍脑袋,想知道他们为什么要引入第三方库来序列化然后反序列化一个我不知道为什么会发生的对象。此外,这不适用于具有圆形结构的对象的模型列表。
这段代码非常适合我进行深度克隆。该应用程序正在将文档样板从 Dev 迁移到 QA 到 Prod。每个对象是几个文档模板对象的数据包,而每个文档又由段落对象列表组成。这段代码让我序列化 .NET “源”对象并立即将它们反序列化为新的“目标”对象,然后将其保存到不同环境中的 SQL 数据库中。经过大量研究,我发现了很多东西,其中很多都太麻烦了,并决定尝试一下。这种简短而灵活的方法“恰到好处”!
z
ztorstri

对于深拷贝,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 使其简洁明了。同样,您可以按照其他答案的建议进行操作并进行扩展方法等,但这些都不是必需的。


这称为“复制构造函数”。这是一种容易出错的方法,每当您向 Student 添加一个新字段时,您必须记住将其添加到复制构造函数中。 “克隆”背后的主要思想是避免这个问题。
即使使用 ICloneable,您的班级也必须有一个“克隆”方法。除非您使用反射(您也可以在上述方法中使用),否则该 Clone 方法看起来与上面的复制构造函数方法非常相似,并且会遇到必须更新新/更改字段的相同问题。但这就是说“当类的字段发生变化时,必须更新类”。当然可以;)
这种方法有一个严重的局限性。考虑 class StudentB : Student。在 Students 的列表中,有些可能是 StudentBnew Student(s) 会做错事 - 它会创建一个 Student,而不是一个 StudentBICloneable 没有这个限制。 (假设 StudentB 也实现了 ICloneable。)
F
F.H.

无需将类标记为可序列化,并且在我们的测试中使用 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);
}

私有成员不使用 JSON 方法克隆。 stackoverflow.com/a/78612/885627
不确定这是否对任何人有帮助。在我的情况下,JSON选项不起作用(例外:“System.InvalidCastException:无法将'Newtonsoft.Json.Linq.JObject'类型的对象转换为类型......”)但是当我稍微改变最后一行时它起作用了,像这样: public static T DeepCloneJSON(this T value) { string json = JsonConvert.SerializeObject(value);返回 (T)JsonConvert.DeserializeObject(json, value.GetType()); }
J
John Kurtz

如果有人读过这篇文章,我会很幸运......但为了不在我的 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();
}

有人喜欢吗?有什么改进吗?


C
Cody Gray
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;
}

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

s
shahrooz.bazrafshan
    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;
        }
    }

P
Peter Mortensen

如果你需要一个相同容量的克隆列表,你可以试试这个:

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

2
2 revs
 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();

M
Martin Zaloga

如果我需要收集的深层副本,我最喜欢这样的方法:

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

与需要完成的实际工作相比,将数据序列化为文本是非常昂贵的资源。如果您不处理生产代码,这可能没问题,这只是一次性的事情。
A
Athafoud

您可以使用扩展方法:

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

注意:如果您对复制(或克隆)进行任何更改,它不会影响原始对象。


这是一个非常基本的示例,在现实世界的编程中没有用处。您必须克隆具有其他对象列表子级的复杂对象列表,依此类推。
T
Thomas Cerny

在这种情况下,对于浅拷贝,使用强制转换可能会有所帮助:

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

D
Dan H

我使用自动映射器来复制对象。我只是设置了一个将一个对象映射到自身的映射。你可以用任何你喜欢的方式包装这个操作。

http://automapper.codeplex.com/


A
Avi

您也可以使用 ToArray 简单地将列表转换为数组,然后使用 Array.Clone(...) 克隆该数组。根据您的需要,Array 类中包含的方法可以满足您的需要。


这不起作用;对克隆数组中值的更改仍然会更改原始列表中的值。
你可以使用 var clonedList = ListOfStrings.ConvertAll(p => p);正如@IbrarMumtaz 给出的那样......有效地工作......对一个列表的更改保留在自己身上,不会反映在另一个列表中
K
Kamil Budziewski

我为自己制作了一些扩展,它转换了未实现 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();
    }
}

似乎某些集合(例如 Silverlight 的 DataGrid 的 SelectedItems)跳过了 CopyTo 的实现,这是这种方法的一个问题
P
Peter Mortensen

以下代码应以最少的更改转移到列表中。

基本上,它通过在每个连续循环中插入更大范围的新随机数来工作。如果已经存在与它相同或更高的数字,则将这些随机数上移一个,以便它们转移到新的更大范围的随机索引中。

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

P
Peter Mortensen

另一件事:你可以使用反射。如果您正确缓存它,那么它将在 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 序列化程序的性能会更好。


P
Peter Mortensen

有一种使用 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;

Z
Zeyad

对于深度克隆,我使用反射如下:

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。