2012-12-20 22 views
10

我有一个列表,获取来自操作的一些数据,我将它存储在内存缓存中。现在我需要另一个列表,其中包含基于某些条件的列表中的一些子数据。如何在不更改原始列表的情况下更改我的新列表?

从下面的代码可以看出,我在目标列表上做了一些操作。问题是,我对目标列表所做的任何更改也正在对mainList进行。我认为它的参考是相同的或某事。

我所需要做的就是对目标列表的操作不会影响主列表中的数据。

List<Item> target = mainList; 
SomeOperationFunction(target); 

void List<Item> SomeOperationFunction(List<Item> target) 
{ 
    target.removeat(3); 
    return target; 
} 
+1

你是说在两个列表中存在的对象都被修改?即你需要克隆()/创建它们的副本,而不是在相同的实例上工作? – Oli

回答

14

您需要克隆的列表中你的方法,因为List<T>是一类,所以它的引用类型和通过引用传递。

例如:

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> tmp = target.ToList(); 
    tmp.RemoveAt(3); 
    return tmp; 
} 

或者

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> tmp = new List<Item>(target); 
    tmp.RemoveAt(3); 
    return tmp; 
} 

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> tmp = new List<Item>(); 
    tmp.AddRange(target); 
    tmp.RemoveAt(3); 
    return tmp; 
} 
+0

这里select不做任何事情,你只需要'ToList'。 – Servy

+0

完美..谢谢你 – user505210

+1

第三个例子完全错了。 'tmp' var被用来填充自己。然后你仍然从'target'而不是'tmp''移除元素。 –

1

您的目标变量是一个引用类型。这意味着你对它做的任何事情都会反映在你传递给它的列表中。

要做到这一点,您需要在该方法中创建一个新列表,将target内容复制到该列表中,然后在新列表中执行删除操作。

About Reference and Value Types

4

先建立一个新的列表,并在该操作,因为名单是引用类型,即当您在函数中传递它,你不只是传递值,而是传递实际的对象本身。

如果你只是分配targetmainList,两个变量指向同一个对象,所以你需要创建一个新的列表:

List<Item> target = new List<Item>(mainList); 

void List<Item> SomeOperationFunction()是没有意义的,因为无论你什么也不返回(void)或你将返回一个List<T>。因此,要么从您的方法中删除返回语句,要么返回一个新的List<Item>。在后一种情况下,我会为改写这个:

List<Item> target = SomeOperationFunction(mainList); 

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    var newList = new List<Item>(target); 
    newList.RemoveAt(3); 
    return newList; 
} 
+0

'List是一个引用类型,因此通过引用传递 - 是循环的。如果你这样说,你将不得不解释一个引用类型是什么,或者链接到一个引用(不是双关语)。 –

+0

@RobertHarvey谢谢你指出。 – Residuum

+0

当然,除非有'ref'关键字,否则您仍然通过值传递引用,而不是引用。区分传递参考值与参考值是很重要的。 – Servy

6

你需要让列表的副本,以便更改副本不会影响原来的。最简单的方法是在System.Linq中使用ToList扩展方法。

var newList = SomeOperationFunction(target.ToList()); 
+3

@Bernhof:这是正确的;但是,请记住,如果项目是引用类型,则列表仍然保留对相同项目的引用。因此对项目本身的更改仍会影响这两个列表中的项目。 –

+0

要做的一点,但我会争辩说'SomeOperationFunction'应该处理复制。如果函数*返回一个'列表',那么人们会期待尽可能多。 – bernhof

+0

@Bernhof也许,是的。鉴于像其他5人一样建议改变,我没有看到需要重申它。重点仅仅是复制需要在某个地方进行。 – Servy

0

你所看到的原始列表进行修改,因为在默认情况下,任何非基本对象,通过引用传递(它实际上是按值传递,该值作为参考,但那是另一回事)。

你需要做的是复制对象。这个问题将帮助你用C#克隆一个列表的代码:How do I clone a generic list in C#?

+1

如果列表通过引用或按值传递,则完全没有关系。在任何情况下,'target'都将是一个指向原始列表的引用。如果您在'target = null;'中指定一个新的值给'target',那么通过值/引用只会产生差异。 –

+0

@ OlivierJacot-Descombes - 这就是为什么我简化说它是通过引用。括号中的注释只是指出,除非明确使用'ref'关键字,否则C#中的所有变量都是按值传递的。恰巧,用于复杂类型的'值'是一个指针。 –

+0

'string'是一个原始类型,但是是一个引用类型(它是不可变的)。 'System.Drawing.Point'不是一个原始类型,而是一个值类型(一个结构体)。 –

0

由于List是一个引用类型,传递给函数的是对原始列表的引用。

有关参数如何在C#中传递的更多信息,请参阅此MSDN article

为了达到你想要的效果,你应该在SomeOperationFunction中创建一个列表的副本,然后返回它。一个简单的例子:

void List<Item> SomeOperationFunction(List<Item> target) 
{ 
    var newList = new List<Item>(target); 
    newList.RemoveAt(3); 
    return newList; // return copy of list 
} 

正如奥利弗Jacot-Descombes在评论到另一个答案指出的那样,在牢记

[...]列表中仍持有的引用是非常重要的如果 这些项目是引用类型,则返回相同的项目。所以对项目本身 的更改仍会影响这两个列表中的项目。

+1

也许你应该花一些时间阅读你自己的链接。引用类型不是通过引用传递的,它们通过值传递,就像值类型一样,但传递的*是引用*。这是一个非常重要的区别。 – Servy

+0

'弄错了,我明白其中的差异,并编辑了答案,以避免误解。 – bernhof

+0

你为什么删除链接?这是一个足够好的链接。 – Servy

0

不是指定mainList的目标,我会做:target.AddRange(mainList);

然后,你将拥有的物品,而不是对列表的引用的副本。

+0

为什么要调用'ToArray'? 'AddRange'接受列表已经实现的'IEnumerable'。 – Servy

+0

我跑到实例(也许.net 2.0或3.5),其中'AddRange'只有一个数组。如果最新的框架接受“IEnumerable”,我很乐意修改我的答案。 –

+0

IEnumerable '.NET 2.0的重载。 [链接](http://msdn.microsoft.com/en-us/library/z883w3dc(V = VS.80)的.aspx)。唯一没有的版本是1.0。 – Servy

0

只要确保使用通过复制源列表元素创建的列表来初始化新列表。

List<Item> target = mainList;应该List<item> target = new List<Item>(mainList);

0

你需要做列表的副本,因为在你所做的只是路过围绕你的原代码,当你正确怀疑,参考(有人叫它一个指针)。

你既可以调用新的名单上的构造,通过原来的列表作为参数:

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> result = new List<Item>(target); 
    result.removeat(3); 
    return result; 
} 

或者创建一个MemberWiseClone:返回

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> result = target.MemberWiseClone(); 
    result.removeat(3); 
    return result; 
} 

而且,你是不是存储所以你可能也想修改那个部分(你声明方法为void,它不应该返回任何东西,但是在它内部你要返回一个对象)。 你应该调用的方法是这样的:

List<Item> target = SomeOperationFunction(mainList); 

注:列表中的的元素不会被复制(只有自己参考复制),所以修改元素的内部状态将影响名单。

0

即使您创建了新列表,对新列表中项目的引用仍将指向旧列表中的项目,因此如果需要使用新引用的新列表,我喜欢使用此扩展方法。 ..

public static IEnumerable<T> Clone<T>(this IEnumerable<T> target) where T : ICloneable 
{ 
    If (target.IsNull()) 
     throw new ArgumentException(); 

    List<T> retVal = new List<T>(); 

    foreach (T currentItem in target) 
     retVal.Add((T)(currentItem.Clone())); 

    return retVal.AsEnumerable(); 
} 
相关问题