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

Fio*_*ona 545 c# generics clone list

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

有一个简单的方法吗?

Jef*_*tes 483

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

List<YourType> newList = new List<YourType>(oldList);
Run Code Online (Sandbox Code Playgroud)

但是,如果它们是引用类型并且您需要深层复制(假设您的元素正确实现ICloneable),您可以执行以下操作:

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

oldList.ForEach((item) =>
    {
        newList.Add((ICloneable)item.Clone());
    });
Run Code Online (Sandbox Code Playgroud)

显然,替换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));
    });
Run Code Online (Sandbox Code Playgroud)

就个人而言,我会避免ICloneable因为需要保证所有成员的深层副本.相反,我建议复制构造函数或类似的工厂方法YourType.CopyFrom(YourType itemToCopy)返回一个新的实例YourType.

任何这些选项都可以通过方法(扩展名或其他方式)进行包装.

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

小智 363

您可以使用扩展方法.

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

  • 我认为List.ConvertAll可能会在更快的时间内完成此任务,因为它可以为列表预先分配整个数组,而不是一直调整大小. (68认同)
  • @IbrarMumtaz:这与var clonedList = new List <string>(ListOfStrings)相同; (27认同)
  • 这不是只是答案的一半吗,因为它依赖于 ICloneable 的实现,而这将是问题的重要部分? (7认同)
  • 好的解决方案 顺便说一下,我更喜欢公共静态List <T> CLone <T> ......在这种情况下它更有用,因为不需要进一步的转换:List <MyType> cloned = listToClone.Clone(); (4认同)
  • @MichaelGG,如果您不想转换而只是克隆/复制列表中的项目怎么办?这行得通吗?|| var clonedList = ListOfStrings.ConvertAll(p =&gt; p); (2认同)
  • 这是深度克隆 (2认同)

Ant*_*tts 80

对于浅表副本,您可以改为使用泛型List类的GetRange方法.

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

List<int> newList = oldList.GetRange(0, oldList.Count);
Run Code Online (Sandbox Code Playgroud)

引自:仿制食谱

  • 您还可以通过使用List <T>的构造函数来指定要从中复制的List <T>来实现此目的.例如var shallowClonedList = new List <MyObject>(originalList); (36认同)
  • 我经常使用`List <int> newList = oldList.ToList()`.同样的效果.但是,在我看来,Arkiliknam的解决方案最适合可读性. (3认同)
  • @DanBechard 几年后,但我更喜欢“ToList”,因为它避免了所有冗余 - 我想知道哪个实际上性能更高......查了一下。看起来列表 `ToList` 调用 `new List&lt;T&gt;` 最终将使用 `Array.CopyTo`,所以大致相同。 (2认同)

Pat*_*ins 78

public static object DeepClone(object obj) 
{
  object objResult = null;
  using (MemoryStream  ms = new MemoryStream())
  {
    BinaryFormatter  bf =   new BinaryFormatter();
    bf.Serialize(ms, obj);

    ms.Position = 0;
    objResult = bf.Deserialize(ms);
  }
  return objResult;
}
Run Code Online (Sandbox Code Playgroud)

这是使用C#和.NET 2.0实现此目的的一种方法.你的对象需要[Serializable()].目标是丢失所有引用并构建新引用.

  • +1 - 我喜欢这个答案 - 它快速,肮脏,讨厌且非常有效.我在silverlight中使用,并使用DataContractSerializer,因为BinarySerializer不可用.当你可以这样做时,谁需要编写对象克隆代码页?:) (11认同)
  • 我喜欢这个.虽然做"正确"的事情很好,但快速和肮脏通常会派上用场. (3认同)
  • 快!但是:为什么脏? (3认同)
  • 这种深度克隆,快速简便.仔细阅读此页面上的其他建议.我尝试了几个,他们没有深刻克隆. (2认同)
  • 只有消极的方面,如果你可以称之为,那么你的类必须被标记为Serializable才能使用. (2认同)

小智 22

稍作修改后你也可以克隆:

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;
}
Run Code Online (Sandbox Code Playgroud)


Xav*_*ohn 22

要克隆列表,只需调用.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 }
> 
Run Code Online (Sandbox Code Playgroud)

  • 稍微警告这是一个浅拷贝...这将创建两个列表对象,但内部的对象将是相同的.即更改一个属性将更改原始列表中的相同对象/属性. (17认同)
  • 迄今为止最简单的解决方案 (2认同)

Jad*_*ijo 16

除非您需要对您内部的每个对象进行实际克隆,否则List<T>克隆列表的最佳方法是使用旧列​​表作为集合参数创建新列表.

List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);
Run Code Online (Sandbox Code Playgroud)

myList插入或删除等更改不会影响cloneOfMyList,反之亦然.

但是,两个列表包含的实际对象仍然相同.

  • 又名浅拷贝 (4认同)
  • @WellingtonZanelli 刚刚确认从 myList 中删除一个元素也会将其从 cloneOfMyList 中删除。 (3认同)
  • @Seidleroni,你错了。对列表项所做的更改会影响另一个列表,而列表本身的更改则不会。 (2认同)
  • 这是浅复制。 (2认同)

Jam*_*ran 14

如果你只关心价值类型......

你知道的类型:

List<int> newList = new List<int>(oldList);
Run Code Online (Sandbox Code Playgroud)

如果您之前不知道类型,则需要辅助函数:

List<T> Clone<T>(IEnumerable<T> oldList)
{
    return newList = new List<T>(oldList);
}
Run Code Online (Sandbox Code Playgroud)

正义:

List<string> myNewList = Clone(myOldList);
Run Code Online (Sandbox Code Playgroud)

  • 这不会克隆元素. (15认同)
  • 请记住,这仅适用于值类型. (12认同)

Der*_*ang 13

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

定义您的映射:

Mapper.CreateMap<YourType, YourType>();
Run Code Online (Sandbox Code Playgroud)

做魔术:

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);
Run Code Online (Sandbox Code Playgroud)


Pro*_*rod 10

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

List<T> newList = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(listToCopy))
Run Code Online (Sandbox Code Playgroud)

可能不是最有效的方法,但除非你做1000次,否则你可能甚至没有注意到速度差异.

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

zto*_*tri 8

对于深层复制,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();
Run Code Online (Sandbox Code Playgroud)

您将需要以下库来制作副本

using System.Linq
Run Code Online (Sandbox Code Playgroud)

您还可以使用 for 循环代替 System.Linq,但 Linq 使其变得简洁明了。同样,您可以按照其他答案的建议进行操作并制定扩展方法等,但这些都不是必要的。

  • 即使使用 ICloneable,您的类上也必须有一个“Clone”方法。除非您使用反射(您也可以在上述方法中使用),否则该 Clone 方法看起来与上面的复制构造函数方法非常相似,并且将遇到必须更新新/更改字段的相同问题。但这是说“当类的字段发生更改时,必须更新类”。当然可以;) (2认同)

F.H*_*.H. 5

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

使用Newtonsoft JSON 的更快选项:

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

    return JsonConvert.DeserializeObject<T>(json);
}
Run Code Online (Sandbox Code Playgroud)

  • 不使用 JSON 方法克隆私有成员。/sf/answers/5502871/ (4认同)

Joh*_*rtz 5

如果有人读过这篇文章,我会很幸运......但为了不在我的 Clone 方法中返回类型对象列表,我创建了一个接口:

public interface IMyCloneable<T>
{
    T Clone();
}
Run Code Online (Sandbox Code Playgroud)

然后我指定了扩展名:

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

这是我的 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在类中使用扩展:

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

//Other methods instantiate and fill the lists

private void SetUndoVidMarks()
{
    _UndoVidMarks = _VidMarks.Clone();
}
Run Code Online (Sandbox Code Playgroud)

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