c# - 我应该使用 ToList() 深度克隆 IList 吗?

标签 c# linq clone

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

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

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

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

有什么区别?

最佳答案

如果您了解数据的存储方式,就可以对其进行解释。数据的存储类型有两种,值类型和引用类型。下面是基本类型和对象声明的示例

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 具有不同值,已经改变了。这是因为两个指针都指向同一个对象。如果您编辑该对象,两个指针都会调用该对象。

这就是为什么有两种类型的克隆,深克隆和浅克隆。下面的示例是深度克隆

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

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

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

我希望您现在已经清楚了。

关于c# - 我应该使用 ToList() 深度克隆 IList 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22729080/

相关文章:

c# - 使用 linq 对计数的不同唯一事件进行排序

xcode - 打开从git存储库克隆或提取的项目时Xcode 4崩溃

JQuery 克隆不保留单选和选择状态

java - 创建对象的独立副本

c# - ASP.NET Core HttpClient无法在容器中进行HTTPS调用

c# - 内存高效递归

c# - AccessViolationException 在 Outlook 2007 中读取电子邮件发件人

c# - 无法构建我的控制台应用程序

c# - 使用 Linq 反转层次结构

c# - 在 Linq 中执行先父后子排序