2014-09-25 121 views
1

我简直不明白为什么这个简单的代码不工作。我的预期输出是10和15,但它返回2和3.这意味着更新不起作用。更新ForEach循环内的变量

List<int> numbers = new List<int>(); 

numbers.Add(2); 
numbers.Add(3); 

numbers.ForEach(n => n = n*5); 

numbers.ForEach(n => Console.WriteLine(n)); 


注:我已经搜索了很多,但我无法理解这种行为。

我该如何解决?

更新:字符串的相同行为。

List<string> strings = new List<string>(); 
strings.Add("a"); 
strings.Add("b"); 

strings.ForEach(s => s = s + "--"); 

strings.ForEach(s => Console.WriteLine(s)); 
+0

这是因为整数是值类型,并通过值而不是通过引用传递。 – 2014-09-25 23:21:07

+0

我使用字符串得到相同的结果。我无法修改ForEach循环内的字符串。 – Ricardo 2014-09-25 23:22:12

+0

@Ricardo真的吗?你正在执行什么操作? – BradleyDotNET 2014-09-25 23:23:01

回答

7

n是你的当前值的列表中的一个副本不是要操纵你的列表中的值,以你的值。如果一个参考,然后用一个for循环

for(int i = 0; i<numbers.Count; i++) 
    numbers[i] *= 5; 

更详细的解释:

与正常的foreach循环你的代码甚至不编译:

foreach(var n in numbers) 
     n = n * 5; // Readonly local variable cannot be used as an assignment target 

记住循环是不一样的foreach但它只是需要一个Action<int>委托作为参数,并执行指定的动作的每一个元素的方法,你list.So它执行像这样(摘自源代码):

public void ForEach(Action<T> action) 
{ 
    // removed unnecessary parts for brevity 
    for(int i = 0 ; i < _size; i++) 
    { 
     action(_items[i]); 
    } 
} 

正如你可以在这里看到的_item[i]传递到行动,因为int是你的价值的副本传递值类型,而不是一个reference.And这就是为什么你的价值观没”改变。

对于字符串:除了一个事实,即字符串是不可改变的,分配一个新的参考引用类型并不改变持有相同reference.For例如考虑这个对象:

static void Update(string s) 
{ 
    s = "bar"; 
} 

string f = "foo"; 
Update(f); 
Console.WriteLine(f); // foo 

分配一个对s的新引用不会更改f,f stil保留旧的引用,s指向内存中的新位置。这不是因为s是副本,它不是。如果更改s的某个属性(使用字符串你不能这样做,但尝试使用另一个参考类型),它也会更新f的属性。它工作在这种方式,因为sf都指向同一位置memory.So s。你可以把两个不同的字符串没有被绑定到f他们声明如下:

string f = "foo"; 
string s = f; 
s = "bar"; 

唯一的例外是当你通过f作为ref参数,则该任务将改变f还有:

static void Update(ref string s) 
{ 
    s = "bar"; 
} 

string f = "foo"; 
Update(ref f); 
Console.WriteLine(f); // bar 
+1

的下面看到我的答案请注意,如果你赋值给变量而不是变更值,那么引用类型也是一样的。 – 2014-09-25 23:23:48

+0

@MattiVirkkunen好点我删除了误导性句子 – 2014-09-25 23:26:30

+0

很好的解释! TY。 – Ricardo 2014-09-26 00:44:20

1

的原因是因为参数传递给ForEach是按值而不是通过引用。 然而,如果你传递引用类型,就必须按预期工作如下图所示

class Program 
    { 
     static void Main(string[] args) 
     { 
      List<Frog> numbers = new List<Frog>(); 

      numbers.Add(new Frog { name = "balcha" }); 
      numbers.Add(new Frog { name = "Tibara" }); 
      numbers.ForEach(n => n.name = "Bontu"); 
      numbers.ForEach(n => Console.WriteLine(n.name)); 
      Console.ReadLine(); 
     } 

     class Frog 
     { 
      public string name { get; set; } 
     } 
    } 

输出:

Bontu 
Bontu 
+0

我相当确信OP不仅仅希望有人修复他们的代码,而是要解释发生了什么事情。 – 2014-09-25 23:24:37

3

因为他们是值类型,而不是突变的列表,你可以创建一个修改一个使用Select

var newList= numbers.Select(n => n = n*5); 

作为当务之急程序员,我们喜欢的事情突变,这是不是一个好主意! 它不能用于字符串的原因是因为默认情况下C#传递引用的副本而不是实际的引用。

void Fn(string s) 
{ 
    s = "not being changed"; 
} 

Main() 
{ 
var hello = "hello"; 
Fn(hello); 
Console.WriteLine (hello); // prints hello again!! 
} 

不过,如果你想改变你参考必须使用ref关键字。

void Fn(ref string s) 
{ 
    s = "Unfortunately, changed!"; 
} 

Main() 
{ 
var hello = "hello"; 
Fn(ref hello); 
Console.WriteLine (hello); // Unfortunately, changed!!! 
} 

我认为,改变的参数值是一个可怕的想法,你不应该这样做,你应该返回包含新修改的新字符串。

+0

Ty @Sleiman,这是迄今为止最好的代码修复(upvoted you),但我仍然想更好地理解为什么ForEach不能用于字符串,因为它们使用Reference而不是值。 – Ricardo 2014-09-26 00:09:46

+0

@Ricardo即使您将参考类型传递给函数,它也会传递参考的副本而不是实际的参考 – 2014-09-26 00:19:54

+0

现在很清楚! TY。 – Ricardo 2014-09-26 00:39:13