新足迹

 找回密码
 注册

精华好帖回顾

· New Zealand trip (2005-9-29) miller2 · 星*--及几张重口味(41#加一张无敌日出) (2013-8-1) Wolongshan
· 猎梦人谈交易中一些心理现象 (2010-10-10) 猎梦人 · IT 菜鸟程序员跳槽感想(坑已填满, 全文完!) (2007-11-30) bffbffbff
Advertisement
Advertisement
查看: 1693|回复: 16

昨天做程序一下没留神,中了这 C# "Outer Variable Trap" [复制链接]

发表于 2013-4-4 09:24 |显示全部楼层
此文章由 yangwulong1978 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 yangwulong1978 所有!转贴必须注明作者、出处和本声明,并保持内容完整
昨天做程序一下没留神,中了这 C# "Outer Variable Trap"

想了半天才发现错误在哪。


The "Outer Variable Trap" occurs when a developer expects the value of a variable to be captured by a lambda expression or anonymous delegate, when actually the variable is captured itself.

Example:

var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
    actions.Add(() => Console.Write("{0} ", i));
}
foreach (var action in actions)
{
    action();
}
Possible output #1:

0 1 2 3 4 5 6 7 8 9
Possible output #2:

10 10 10 10 10 10 10 10 10 10
If you expected output #1, you've fallen into the Outer Variable Trap. You get output #2.

Fix:

Declare an "Inner Variable" to be captured repeatedly instead of the "Outer Variable" which is captured only once.

var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
    var j = i;
    actions.Add(() => Console.Write("{0} ", j));
}
foreach (var action in actions)
{
    action();
}

评分

参与人数 1积分 +6 收起 理由
IsDonIsGood + 6 感谢分享

查看全部评分

Advertisement
Advertisement

发表于 2013-4-4 09:38 |显示全部楼层
此文章由 joerkky 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 joerkky 所有!转贴必须注明作者、出处和本声明,并保持内容完整
嗯,不留神的话我也会犯这个错误。

不过这种func用法很不OO, 看起来不太讲究,还是少用为妙

发表于 2013-4-4 09:41 |显示全部楼层
此文章由 yangwulong1978 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 yangwulong1978 所有!转贴必须注明作者、出处和本声明,并保持内容完整
我刚查了查书。

错误
// Suppose we want to build up a query that strips all the vowels from a string.
// The following (although inefficient) gives the correct result:

IEnumerable<char> query = "Not what you might expect";

query = query.Where (c => c != 'a');
query = query.Where (c => c != 'e');
query = query.Where (c => c != 'i');
query = query.Where (c => c != 'o');
query = query.Where (c => c != 'u');

new string (query.ToArray()).Dump ("All vowels are stripped, as you'd expect.");

"Now, let's refactor this. First, with a for-loop:".Dump();

string vowels = "aeiou";

for (int i = 0; i < vowels.Length; i++)
        query = query.Where (c => c != vowels[i]);   // IndexOutOfRangeException

foreach (char c in query) Console.Write (c);

// An IndexOutOfRangeException is thrown! This is because, as we saw in Chapter 4
// (see "Capturing Outer Variables"), the compiler scopes the iteration variable
// in the for loop as if it was declared outside the loop. Hence each closure captures
// the same variable (i) whose value is 5 when the query is enumerated.

解决方案。
// We can make the preceding query work correctly by assigning the loop variable to another
// variable declared inside the statement block:

IEnumerable<char> query = "Not what you might expect";
string vowels = "aeiou";

for (int i = 0; i < vowels.Length; i++)
{
        char vowel = vowels[i];
        query = query.Where (c => c != vowel);
}

foreach (char c in query) Console.Write (c);



FOREACH 在  C#3,4 里也是同样的错误,5 里就FIX了,,

// Let's now see what happens when you capture the iteration variable of a foreach loop:

IEnumerable<char> query = "Not what you might expect";
string vowels = "aeiou";

foreach (char vowel in vowels)
        query = query.Where (c => c != vowel);

foreach (char c in query) Console.Write (c);

// The output depends on which version of C# you're running! In C# 4.0 and C# 3.0, we
// get the same problem we had with the for-loop: each loop iteration captures the same
// variable, whose final value is 'u'. Hence only the 'u' is stripped. The workaround
// for this is to use a temporary variable (see next example).

// In C# 5.0, they fixed the compiler so that the iteration variable of a foreach loop
// is treated as *local* to each loop iteration. Hence our example strips all vowels
// as expected.

发表于 2013-4-4 09:44 |显示全部楼层
此文章由 fishinmel 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 fishinmel 所有!转贴必须注明作者、出处和本声明,并保持内容完整
本帖最后由 fishinmel 于 2013-4-4 17:44 编辑

C# 5.0 has covered this issue. You must have used an earlier version.

发表于 2013-4-4 12:39 |显示全部楼层
此文章由 jerryclark 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 jerryclark 所有!转贴必须注明作者、出处和本声明,并保持内容完整
fishinmel 发表于 2013-4-4 08:44
C# 5.0 has covered this issue. You must use an earlier version.

你是想说“you must have used an earlier version”吧?
‎( ͡° ͜ʖ ͡°)

发表于 2013-4-4 17:45 |显示全部楼层
此文章由 fishinmel 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 fishinmel 所有!转贴必须注明作者、出处和本声明,并保持内容完整
jerryclark 发表于 2013-4-4 12:39
你是想说“you must have used an earlier version”吧?

多谢提醒。已改正。。。
Advertisement
Advertisement

发表于 2013-4-4 19:34 |显示全部楼层
此文章由 seabookf_91 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 seabookf_91 所有!转贴必须注明作者、出处和本声明,并保持内容完整
C# 不错 比java 高级 还支持皮包 呵呵

发表于 2013-7-16 23:39 |显示全部楼层
此文章由 earthengine 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 earthengine 所有!转贴必须注明作者、出处和本声明,并保持内容完整
Java 8已经支持皮包了呀。

发表于 2013-7-17 09:16 |显示全部楼层
此文章由 findcaiyzh 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 findcaiyzh 所有!转贴必须注明作者、出处和本声明,并保持内容完整
很少用这么高级的用法。

发表于 2013-7-17 10:37 |显示全部楼层
此文章由 IsDonIsGood 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 IsDonIsGood 所有!转贴必须注明作者、出处和本声明,并保持内容完整
学习了~~

发表于 2013-7-17 10:38 |显示全部楼层
此文章由 yangwulong1978 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 yangwulong1978 所有!转贴必须注明作者、出处和本声明,并保持内容完整
earthengine 发表于 2013-7-16 22:39
Java 8已经支持皮包了呀。

有个词我差点看反了,罪过,罪过
Advertisement
Advertisement

发表于 2013-7-17 11:44 |显示全部楼层
此文章由 cdfei 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 cdfei 所有!转贴必须注明作者、出处和本声明,并保持内容完整
那是因为没有执行啊,加个tolist()就行了
皮包是啥意思。

发表于 2013-7-17 13:11 |显示全部楼层
此文章由 上班ing 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 上班ing 所有!转贴必须注明作者、出处和本声明,并保持内容完整
earthengine 发表于 2013-7-16 22:39
Java 8已经支持皮包了呀。

还在用JAVA 6 的飘过……
话说没听说JAVA 8 发布啊?

发表于 2013-7-24 12:15 |显示全部楼层
此文章由 changhongzhi 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 changhongzhi 所有!转贴必须注明作者、出处和本声明,并保持内容完整
记得javascript 里也有类似的问题

发表于 2013-7-25 22:27 |显示全部楼层
此文章由 melmonash 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 melmonash 所有!转贴必须注明作者、出处和本声明,并保持内容完整
学习了

发表于 2013-9-11 21:44 |显示全部楼层
此文章由 earthengine 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 earthengine 所有!转贴必须注明作者、出处和本声明,并保持内容完整
本帖最后由 earthengine 于 2013-9-11 21:09 编辑

Java 8目前的状态是开发人员预览版。 明年出货。

科普:皮包,正式名称闭包(B包? 一样容易看错哦。)英文名Closure,是现代语言必备的功能。以前常用的语言如C, 它的函数只能在调用的时候一次性传入所需的参数。这样它跟程序其它部分的互动只能依靠某种全局变量来完成。

但是函数式语言(鼻祖是LISP)可以允许先传入一些参数,得到一个带有部分参数的象函数一样可以调用的东西,然后再在适当的时候传入余下的参数并执行得到结果。这样可以允许一些高级的抽象,而且因为函数的执行和传参可以完全分开,可以实现惰性求值等功能。

通常,支持皮包的语言允许皮包自动把定义处可以观察到的周围环境装进去,就像斯诺登带出来的那个皮包一样。但这是一把双刃剑,搞不好容易带来一些不想要的依赖。它通常也意味着你可以让皮包把整个函数的定义写在另一个函数里面,而且不用给它起名字。但是,它最好且最不易被误用的用法就是我前面说的:分步传参和惰性求值。

支持皮包的常见语言:

1. Javascript 打娘胎出来就有。
2. PHP 5.3
3. C# 2.0
4. C++11
5. Java 8
...

自从Java 8加入这个阵营,可以放心地说一句:所有现代高级编程语言都支持皮包。
Advertisement
Advertisement

发表于 2013-9-11 22:29 |显示全部楼层
此文章由 earthengine 原创或转贴,不代表本站立场和观点,版权归 oursteps.com.au 和作者 earthengine 所有!转贴必须注明作者、出处和本声明,并保持内容完整
再科普一贴

楼主遇到的问题,叫做函数参数引用问题(Funarg problem, 参见维基百科),凡是实现皮包的语言都会遇到这个问题。因此,很重要的一点就是要搞明白你用的语言是如何解决这个问题的。

这个问题通常是这样的:一个皮包被定义时装了些参数和环境,然后定义它的函数可能已经返回了,或者那些参数的值改变了,那么这个皮包执行的时候到底会如何呢?

1. Javascript, C# 2.0 - 使用改变了的值。这就会导致楼主的问题。
2. Java 8 - 强制要求皮包里的值是不可变的 (final) ,外界不能改变它。这样有时候不方便。
3. C++11 - 让程序员设定是传值(皮包里的值)还是传引用(改变了的值)。这样够灵活,但语法就臃肿些。

发表回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Advertisement
Advertisement
返回顶部