【问题标题】:Should I use ToList() Deep Clone IList?我应该使用 ToList() 深度克隆 IList 吗?
【发布时间】:2014-05-08 20:58:36
【问题描述】:

假设我有以下代码可以将 a 深度克隆到 b

IList<int> a = new List<int>();
a.Add(5);
IList<int> b = a.ToList();

是好是坏? ToList 返回一个新列表似乎很有效。但是当我用谷歌搜索时,其他人总是使用类似的东西

listToClone.Select(item => (T)item.Clone()).ToList();

有什么区别?

【问题讨论】:

  • 视情况而定。如果您有一个值类型列表,它将复制它们。如果你有一个引用类型列表,那么它只会复制引用而不是对象。
  • 第一种方法只是浅克隆。第二种方法可能是深度克隆——它取决于Clone的实现。

标签: c# linq clone


【解决方案1】:

这取决于。如果您有value types 的集合,它将复制它们。如果你有一个引用类型列表,那么它只会复制对真实对象的引用,而不是真实值。这是一个小例子

void Main()
{
    var a = new List<int> { 1, 2 };
    var b = a.ToList();
    b[0] = 2;
    a.Dump();
    b.Dump();
    
    var c = new List<Person> { new Person { Age = 5 } };
    var d = c.ToList();
    d[0].Age = 10;
    c.Dump();
    d.Dump();
}

class Person
{
    public int Age { get; set; }
}

前面的代码导致

a - 1, 2

b - 2, 2

c - 年龄 = 10

d - 年龄 = 10

如您所见,新集合中的第一个数字发生了变化,并且没有影响另一个数字。但我创建的人的年龄并非如此。

【讨论】:

    【解决方案2】:

    如果您了解数据是如何存储的,则可以对其进行解释。数据有两种存储类型,值类型和引用类型。下面是一个原始类型和对象的声明示例

    int i = 0;
    MyInt myInt = new MyInt(0);
    

    MyInt 类是

    public class MyInt {
        private int myint;
    
        public MyInt(int i) {
            myint = int;
        }
    
        public void SetMyInt(int i) {
            myint = i;
        }
        public int GetMyInt() {
            return myint;
        }
    }
    

    这将如何存储在内存中?下面是一个例子。 请注意,以下所有内存示例均已简化!

     _______ __________
    |   i   |     0    |
    |       |          |
    | myInt | 0xadfdf0 |
    |_______|__________|
    

    对于您在代码中创建的每个对象,您将创建对所述对象的引用。该对象将被分组到一个堆中。栈内存和堆内存的区别请参考this explanation

    现在,回到您的问题,克隆一个列表。下面是创建整数列表和MyInt 对象的示例

    List<int> ints = new List<int>();
    List<MyInt> myInts = new List<MyInt>();
    // assign 1 to 5 in both collections
    for(int i = 1; i <= 5; i++) {
        ints.Add(i);
        myInts.Add(new MyInt(i));
    }
    

    然后我们看看内存,

     _______ __________
    | ints  | 0x856980 |
    |       |          |
    | myInts| 0xa02490 |
    |_______|__________|
    

    由于列表来自一个集合,所以每个字段都包含一个引用地址,从而导致下一个

     ___________ _________
    | 0x856980  |   1     |
    | 0x856981  |   2     |
    | 0x856982  |   3     |
    | 0x856983  |   4     |
    | 0x856984  |   5     |
    |           |         |
    |           |         |
    |           |         |
    | 0xa02490  | 0x12340 |
    | 0xa02491  | 0x15631 |
    | 0xa02492  | 0x59531 |
    | 0xa02493  | 0x59421 |
    | 0xa02494  | 0x59921 |
    |___________|_________|
    

    现在您可以看到列表myInts 再次包含引用,而ints 包含值。当我们想用ToList()克隆一个列表时,

    List<int> cloned_ints = ints.ToList();
    List<MyInt> cloned_myInts = myInts.ToList();
    

    我们得到如下结果。

           original                    cloned
     ___________ _________      ___________ _________
    | 0x856980  |   1     |    | 0x652310  |   1     |
    | 0x856981  |   2     |    | 0x652311  |   2     |
    | 0x856982  |   3     |    | 0x652312  |   3     |
    | 0x856983  |   4     |    | 0x652313  |   4     |
    | 0x856984  |   5     |    | 0x652314  |   5     |
    |           |         |    |           |         |
    |           |         |    |           |         |
    |           |         |    |           |         |
    | 0xa02490  | 0x12340 |    | 0xa48920  | 0x12340 |
    | 0xa02491  | 0x15631 |    | 0xa48921  | 0x12340 |
    | 0xa02492  | 0x59531 |    | 0xa48922  | 0x59531 |
    | 0xa02493  | 0x59421 |    | 0xa48923  | 0x59421 |
    | 0xa02494  | 0x59921 |    | 0xa48924  | 0x59921 |
    |           |         |    |           |         |
    |           |         |    |           |         |
    | 0x12340   |   0     |    |           |         |
    |___________|_________|    |___________|_________|
    

    0x12340 是第一个MyInt 对象的引用,包含变量 0。这里简化显示以很好地解释它。

    您可以看到列表显示为克隆。但是当我们要更改克隆列表的一个变量时,第一个变量会被设置为 7。

    cloned_ints[0] = 7;
    cloned_myInts[0].SetMyInt(7);
    

    然后我们得到下一个结果

           original                    cloned
     ___________ _________      ___________ _________
    | 0x856980  |   1     |    | 0x652310  |   7     |
    | 0x856981  |   2     |    | 0x652311  |   2     |
    | 0x856982  |   3     |    | 0x652312  |   3     |
    | 0x856983  |   4     |    | 0x652313  |   4     |
    | 0x856984  |   5     |    | 0x652314  |   5     |
    |           |         |    |           |         |
    |           |         |    |           |         |
    |           |         |    |           |         |
    | 0xa02490  | 0x12340 |    | 0xa48920  | 0x12340 |
    | 0xa02491  | 0x15631 |    | 0xa48921  | 0x12340 |
    | 0xa02492  | 0x59531 |    | 0xa48922  | 0x59531 |
    | 0xa02493  | 0x59421 |    | 0xa48923  | 0x59421 |
    | 0xa02494  | 0x59921 |    | 0xa48924  | 0x59921 |
    |           |         |    |           |         |
    |           |         |    |           |         |
    | 0x12340   |   7     |    |           |         |
    |___________|_________|    |___________|_________|
    

    你看到变化了吗? 0x652310 中的第一个值已更改为 7。但在 MyInt 对象处,引用地址没有更改。但是,该值将分配给0x12340 地址。

    当我们要显示结果时,我们有下一个

    ints[0]   -------------------> 1
    cloned_ints[0]  -------------> 7
    
    myInts[0].GetMyInt()  -------> 7
    cloned_myInts[0].GetMyInt() -> 7
    

    如您所见,原始ints 保持其值原始myInts 具有不同 值,它已更改。这是因为两个指针都指向 same 对象。如果你编辑那个对象,两个指针都会调用那个对象。

    这就是为什么有两种类型的克隆,一种是深的,一种是浅的。这里下面的例子是一个深度克隆

    listToClone.Select(item => (T)item.Clone()).ToList();
    

    这会选择原始列表中的每个项目,并克隆列表中找到的每个对象。 Clone() 来自 Object 类,它将创建一个具有相同变量的新对象。

    但是,请注意,如果您的类中有对象或任何引用类型是不安全的,您必须自己实现克隆机制。或者您将面临与上述相同的问题,即原始对象和克隆对象只是持有一个引用。您可以通过实现ICloneable 接口和实现它的this example 来做到这一点。

    我希望你现在清楚了。

    【讨论】:

      【解决方案3】:

      如果IList&lt;T&gt;的内容要么直接封装值,标识不可变对象以封装其中的值,要么封装共享可变对象的身份,然后调用ToList将创建一个新的列表,与原始列表分离,封装了相同的数据。

      但是,如果IList&lt;T&gt; 的内容将值或状态封装在可变对象中,那么这种方法将不起作用。对可变对象的引用只能是值,或者如果有问题的对象是未共享的。如果对可变对象的引用是共享的,那么所有这些引用的下落将成为由此封装的状态的一部分。为了避免这种情况,复制此类对象的列表需要生成一个新列表,其中包含对其他(可能是新创建的)对象的引用,这些对象封装了与原始对象相同的值或状态。如果有问题的对象包含一个可用的Clone 方法,这可能是可用的,但如果有问题的对象本身就是集合,那么他们的Clone 方法的正确和有效的行为将依赖于他们知道他们是否包含不得向复制列表的接收者公开的对象——这是 .NET Framework 无法告诉他们的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-12-22
        • 2010-09-14
        • 2019-09-06
        • 2010-09-08
        • 2017-09-01
        • 2010-09-09
        • 1970-01-01
        相关资源
        最近更新 更多