回调函数和闭包

时间:2021-09-12 22:46:01
以前对回调函数和闭包都有稍许了解,但绝对只是皮毛,我理解的是回调函数在如下情况下使用:被调用者需要获取调用者内部的对象,包括方法体,变量等的时候,就要用到回调函数。而闭包的作用是使方法内部变量的作用域延生到方法之外,相当于延长内部变量的生命周期。那两者的应用场合有什么区别呢,小弟不才,求解惑! 回调函数和闭包

27 个解决方案

#1


该回复于2013-10-23 21:31:01被版主删除

#2


百度了一下闭包,表示没用过。。学习了 回调函数和闭包

#3


感觉是不是放错区了。貌似c#中没有闭包和回调函数吧???
js中倒是经常用到这两个东西。。
闭包么类似于c#中的内部类,匿名方法,匿名类等。
回调函数么类似于c#中的事件,委托参数等。
真没在c#中这样考虑过。。
所以也不知道说得对不对

#4


方法内部就地定义的捕获局部变量的匿名函数就是闭包,回调函数不一定是闭包,也可以是类的方法。

#5


回调函数和闭包

#6


回调函数,这一般是在C语言中这么称呼,对于定义一个函数,但是并不由自己去调用,而是由被调用者间接调用,都可以叫回调函数。本质上,回调函数和一般的函数没有什么区别,也许只是因为我们定义了一个函数,却从来没有直接调用它,这一点很奇怪,所以有人发明了回调函数这个词来统称这种间接的调用关系。

在包括C#在内的很多高级语言中,我们有其它更具体的名词来称呼它,比如事件处理函数,委托,匿名委托,匿名函数,Lambda表达式,所以很少直接称呼某个函数为回调函数,除非是编写和C打交道的程序。

闭包一般是指函数的嵌套定义中,内部的函数可以超越作用于“看见”外侧的变量,反之则不行,它描述的就是这样一种关系,比如

Action foo1 = () =>
{
    int i = 1;
    Action foo2 = () =>
    {
        int j = 2;
        Action foo3 = () =>
        {
            int k = 3;
            Console.WriteLine(i + j + k);
        };
        foo3();
        // error Console.WriteLine(k);
    };
    foo2();
    // error Console.WriteLine(j);
};
foo1();
// error Console.WriteLine(i);


闭包的作用是使方法内部变量的作用域延生到方法之外,相当于延长内部变量的生命周期。
这不是闭包的作用,而是闭包的副作用,好比汽车的作用绝对不是开得太快能把人撞死。

不恰当地使用匿名函数,会导致变量作用域不正确地延长,比如

class A
{
    static public Action action;
}

class Program
{
    void foo()
    {
        int i = 1;
        A.action = () => Console.WriteLine(i); //很明显,i这个变量按理说在foo()执行完了以后就没用了,但是当我们在匿名函数中访问它以后,它在函数退出之后仍然有效。
    }
    void bar()
    {
        A.action();
    }
    void Main()
    {
        foo();
        bar();
    }
}

#7


本帖最后由 caozhy 于 2013-10-23 22:03:49 编辑
回调函数最主要的作用,不是“获取调用者内部的对象,包括方法体,变量”,而是让一个函数可以实现总体流程的复用,同时让调用者“填空”,自定义某个细节。而一般的函数,则只能复用功能本身。

比如说我编写这么一个函数:
void PrintElementsInTheArray(int[] array)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        Console.WriteLine(array[i]);
    }
}

我调用这个函数,就可以输出一个数组的元素,这很好,但是,如果我的需求变化下,我想输出以逗号分隔,而不是以行分隔,怎么办?
如果不用委托,我们只能直接修改它,或者再写一个函数,比如
void PrintElementsInTheArray2(int[] array)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        if (i == 0) Console.Write(array[i]);
        else Console.Write("," + array[i]);
    }
}

当然,我们也可以把这两个函数写在一起,比如:
void PrintElementsInTheArray(int[] array, bool SplitByComma)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        if (SplitByComma)
        {
            if (i == 0) Console.Write(array[i]);
            else Console.Write(", " + array[i]);
        }
        else
        {
            Console.WriteLine(array[i]);
        }
    }
}

但是你看,写来写去,我们都只能由这个函数的定义者决定整个输出的细节,而遍历数组的流程无法真正复用,如果我们要再换一种输出方式,还得修改代码。有没有一劳永逸的办法呢?那就是使用委托,直接把foreach的循环空出来,让调用者自己填空:
void PrintElementsInTheArray(int[] array, Action<bool, int> action)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        action(i == 0, array[i]);
    }
}

好了,有了这个通用的函数,再实现上面两个需求就很简单了,比如
PrintElementsInTheArray(data, (isFirst, item) => Console.WriteLine(item));

这就是按行输出
PrintElementsInTheArray(data, (isFirst, item) => {
    if (isFirst) Console.Write(item); else Console.Write(", " + item);
});

这就是按照逗号输出

我们还可以不在控制台输出,而在Web页面上输出
PrintElementsInTheArray(data, (isFirst, item) => Response.WriteLine(item));

#8


该回复于2013-10-23 22:24:13被版主删除

#9


C# closure 还是很简单的
 public Func<int> c(int del)
{
            int y = del;
            Func<int> x = ()=>y++;
            return x;
}

 var func = c(3);var i = func();var i1 = func();var i2 = func(); 345

#10


引用 6 楼 caozhy 的回复:
回调函数,这一般是在C语言中这么称呼,对于定义一个函数,但是并不由自己去调用,而是由被调用者间接调用,都可以叫回调函数。本质上,回调函数和一般的函数没有什么区别,也许只是因为我们定义了一个函数,却从来没有直接调用它,这一点很奇怪,所以有人发明了回调函数这个词来统称这种间接的调用关系。

在包括C#在内的很多高级语言中,我们有其它更具体的名词来称呼它,比如事件处理函数,委托,匿名委托,匿名函数,Lambda表达式,所以很少直接称呼某个函数为回调函数,除非是编写和C打交道的程序。

闭包一般是指函数的嵌套定义中,内部的函数可以超越作用于“看见”外侧的变量,反之则不行,它描述的就是这样一种关系,比如

Action foo1 = () =>
{
    int i = 1;
    Action foo2 = () =>
    {
        int j = 2;
        Action foo3 = () =>
        {
            int k = 3;
            Console.WriteLine(i + j + k);
        };
        foo3();
        // error Console.WriteLine(k);
    };
    foo2();
    // error Console.WriteLine(j);
};
foo1();
// error Console.WriteLine(i);


闭包的作用是使方法内部变量的作用域延生到方法之外,相当于延长内部变量的生命周期。
这不是闭包的作用,而是闭包的副作用,好比汽车的作用绝对不是开得太快能把人撞死。

不恰当地使用匿名函数,会导致变量作用域不正确地延长,比如

class A
{
    static public Action action;
}

class Program
{
    void foo()
    {
        int i = 1;
        A.action = () => Console.WriteLine(i); //很明显,i这个变量按理说在foo()执行完了以后就没用了,但是当我们在匿名函数中访问它以后,它在函数退出之后仍然有效。
    }
    void bar()
    {
        A.action();
    }
    void Main()
    {
        foo();
        bar();
    }
}
谢谢曹版主

#11


本帖最后由 caozhy 于 2013-10-23 22:39:00 编辑
关于6L我再多解释几句。

延长变量的生命周期,只是一种现象,并不是错误,也不是副作用。好比汽车可以开得很快只是一种现象而已。
导致错误的原因是因为“代码没有按照设计者的意图工作”,闭包只是使得代码变得复杂,导致更有可能使得它不按照编写者的意图工作,明白这个逻辑关系么?
好比
int i = arr[3];
这行代码对不对?
不好说,如果arr长度是3,那么这样写,越界了。如果arr长度是100,但是你想访问的是第三个元素,你这么写访问了第四个,还是不对,因此,导致代码不对的原因不是代码本身(否则编译器就可以拦下来了),而是它和你的意图不一致。
我们看这样两段代码:
//在另一个很远的地方
public const int MaxLength = 2;
...
int[] arr = new int[MaxLength];
int i = arr[3];


int[] arr = new int[2];
int i = arr[3];

我问你,哪个代码中的错误容易被发现?
显然是后者。
一样的道理,闭包只是使得错误更隐蔽,它本身不是一种错误或者问题。
好比汽车开得快会撞死人,但是开多快会撞死人?这个说不准。你在闹市区开70码是作死,但是在高速上开120一点问题没有。

言归正传,闭包延长了变量的生命周期会导致哪些原本很容易发现的问题不容易发现,我可以举两个例子,
第一个是导致原本应该释放的大量内存没有及时释放,造成性能问题。这个很好理解。
再有,我们都知道,如果一个对象拥有非托管资源,并且用Dispose释放过,那么继续访问或者调用它是错误的,比如
using (obj)
{
   ...
}
obj.foo();
这个明显就是错误的,using之后,这个对象就不要再调用了。
但是如果有闭包,问题就可能很隐蔽:
Action a;
using (obj)
{
     a = () => obj.foo(); 
}
a();
这段代码粗略一看,我没有在using之外再访问它的foo()方法啊。
其实因为闭包的缘故,obj被带了出来。
那么这么写,可能就会有问题。并且不易发现。所以要小心。
注意,我用了“可能”这个词。

#12


引用 7 楼 caozhy 的回复:
回调函数最主要的作用,不是“获取调用者内部的对象,包括方法体,变量”,而是让一个函数可以实现总体流程的复用,同时让调用者“填空”,自定义某个细节。而一般的函数,则只能复用功能本身。

比如说我编写这么一个函数:
void PrintElementsInTheArray(int[] array)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        Console.WriteLine(array[i]);
    }
}

我调用这个函数,就可以输出一个数组的元素,这很好,但是,如果我的需求变化下,我想输出以逗号分隔,而不是以行分隔,怎么办?
如果不用委托,我们只能直接修改它,或者再写一个函数,比如
void PrintElementsInTheArray2(int[] array)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        if (i == 0) Console.Write(array[i]);
        else Console.Write("," + array[i]);
    }
}

当然,我们也可以把这两个函数写在一起,比如:
void PrintElementsInTheArray(int[] array, bool SplitByComma)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        if (SplitByComma)
        {
            if (i == 0) Console.Write(array[i]);
            else Console.Write(", " + array[i]);
        }
        else
        {
            Console.WriteLine(array[i]);
        }
    }
}

但是你看,写来写去,我们都只能由这个函数的定义者决定整个输出的细节,而遍历数组的流程无法真正复用,如果我们要再换一种输出方式,还得修改代码。有没有一劳永逸的办法呢?那就是使用委托,直接把foreach的循环空出来,让调用者自己填空:
void PrintElementsInTheArray(int[] array, Action<bool, int> action)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        action(i == 0, array[i]);
    }
}

好了,有了这个通用的函数,再实现上面两个需求就很简单了,比如
PrintElementsInTheArray(data, (isFirst, item) => Console.WriteLine(item));

这就是按行输出
PrintElementsInTheArray(data, (isFirst, item) => {
    if (isFirst) Console.Write(item); else Console.Write(", " + item);
});

这就是按照逗号输出

我们还可以不在控制台输出,而在Web页面上输出
PrintElementsInTheArray(data, (isFirst, item) => Response.WriteLine(item));


这段委托的例子写的真好! 回调函数和闭包回调函数和闭包

#13


该回复于2013-10-24 00:37:22被版主删除

#14


该回复于2013-10-24 09:11:56被管理员删除

#15


回调函数和闭包,学习一下

#16


引用 11 楼 caozhy 的回复:
关于6L我再多解释几句。

延长变量的生命周期,只是一种现象,并不是错误,也不是副作用。好比汽车可以开得很快只是一种现象而已。
导致错误的原因是因为“代码没有按照设计者的意图工作”,闭包只是使得代码变得复杂,导致更有可能使得它不按照编写者的意图工作,明白这个逻辑关系么?
好比
int i = arr[3];
这行代码对不对?
不好说,如果arr长度是3,那么这样写,越界了。如果arr长度是100,但是你想访问的是第三个元素,你这么写访问了第四个,还是不对,因此,导致代码不对的原因不是代码本身(否则编译器就可以拦下来了),而是它和你的意图不一致。
我们看这样两段代码:
//在另一个很远的地方
public const int MaxLength = 2;
...
int[] arr = new int[MaxLength];
int i = arr[3];


int[] arr = new int[2];
int i = arr[3];

我问你,哪个代码中的错误容易被发现?
显然是后者。
一样的道理,闭包只是使得错误更隐蔽,它本身不是一种错误或者问题。
好比汽车开得快会撞死人,但是开多快会撞死人?这个说不准。你在闹市区开70码是作死,但是在高速上开120一点问题没有。

言归正传,闭包延长了变量的生命周期会导致哪些原本很容易发现的问题不容易发现,我可以举两个例子,
第一个是导致原本应该释放的大量内存没有及时释放,造成性能问题。这个很好理解。
再有,我们都知道,如果一个对象拥有非托管资源,并且用Dispose释放过,那么继续访问或者调用它是错误的,比如
using (obj)
{
   ...
}
obj.foo();
这个明显就是错误的,using之后,这个对象就不要再调用了。
但是如果有闭包,问题就可能很隐蔽:
Action a;
using (obj)
{
     a = () => obj.foo(); 
}
a();
这段代码粗略一看,我没有在using之外再访问它的foo()方法啊。
其实因为闭包的缘故,obj被带了出来。
那么这么写,可能就会有问题。并且不易发现。所以要小心。
注意,我用了“可能”这个词。
真心感谢,受益匪浅

#17


这么直击本质的代码,信手拈来。
曹斑竹真乃大神。

#18


    回调函数貌似是C/C++的说法,还真没听过C#有回调函数。

#19


引用 17 楼 sj178220709 的回复:
这么直击本质的代码,信手拈来。
曹斑竹真乃大神。

同意此观点!

#20


C#管那叫委托什么的。js里倒是叫callback。

#21


楼主认为回调函数的概念其实不是很正确,回调函数并不由自己去调用,而是由被调用者间接调用。回调函数作为参数传给被调用者,其作用是告诉被调用者在调用的时候需要调用者的这个函数,而不必知道这个函数具体是干嘛的。回调函数一般用在跨语言平台上,比如js↔Flex  ,java↔C。

#22


百度了一下闭包,表示没用过。。学习了

#23


匿名方法 经常会 碰到 闭包引起的 副作用,其它的地方还没见到过。

#24


回调函数稍微容易理解;
闭包一直迷迷糊糊的;

#25


其实就是C#的一种委托,让别的对象或线程来帮你执行你 要完成的事情

#26


老是说我感觉比闭包在C语言中用goto类似地实现。不知对不对。

int main()
{
int i=0;
goto here;
back:
printf("%d",i);
return 0;
here:
i++;
goto back:
}

#27


好贴。进来学习了

#1


该回复于2013-10-23 21:31:01被版主删除

#2


百度了一下闭包,表示没用过。。学习了 回调函数和闭包

#3


感觉是不是放错区了。貌似c#中没有闭包和回调函数吧???
js中倒是经常用到这两个东西。。
闭包么类似于c#中的内部类,匿名方法,匿名类等。
回调函数么类似于c#中的事件,委托参数等。
真没在c#中这样考虑过。。
所以也不知道说得对不对

#4


方法内部就地定义的捕获局部变量的匿名函数就是闭包,回调函数不一定是闭包,也可以是类的方法。

#5


回调函数和闭包

#6


回调函数,这一般是在C语言中这么称呼,对于定义一个函数,但是并不由自己去调用,而是由被调用者间接调用,都可以叫回调函数。本质上,回调函数和一般的函数没有什么区别,也许只是因为我们定义了一个函数,却从来没有直接调用它,这一点很奇怪,所以有人发明了回调函数这个词来统称这种间接的调用关系。

在包括C#在内的很多高级语言中,我们有其它更具体的名词来称呼它,比如事件处理函数,委托,匿名委托,匿名函数,Lambda表达式,所以很少直接称呼某个函数为回调函数,除非是编写和C打交道的程序。

闭包一般是指函数的嵌套定义中,内部的函数可以超越作用于“看见”外侧的变量,反之则不行,它描述的就是这样一种关系,比如

Action foo1 = () =>
{
    int i = 1;
    Action foo2 = () =>
    {
        int j = 2;
        Action foo3 = () =>
        {
            int k = 3;
            Console.WriteLine(i + j + k);
        };
        foo3();
        // error Console.WriteLine(k);
    };
    foo2();
    // error Console.WriteLine(j);
};
foo1();
// error Console.WriteLine(i);


闭包的作用是使方法内部变量的作用域延生到方法之外,相当于延长内部变量的生命周期。
这不是闭包的作用,而是闭包的副作用,好比汽车的作用绝对不是开得太快能把人撞死。

不恰当地使用匿名函数,会导致变量作用域不正确地延长,比如

class A
{
    static public Action action;
}

class Program
{
    void foo()
    {
        int i = 1;
        A.action = () => Console.WriteLine(i); //很明显,i这个变量按理说在foo()执行完了以后就没用了,但是当我们在匿名函数中访问它以后,它在函数退出之后仍然有效。
    }
    void bar()
    {
        A.action();
    }
    void Main()
    {
        foo();
        bar();
    }
}

#7


本帖最后由 caozhy 于 2013-10-23 22:03:49 编辑
回调函数最主要的作用,不是“获取调用者内部的对象,包括方法体,变量”,而是让一个函数可以实现总体流程的复用,同时让调用者“填空”,自定义某个细节。而一般的函数,则只能复用功能本身。

比如说我编写这么一个函数:
void PrintElementsInTheArray(int[] array)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        Console.WriteLine(array[i]);
    }
}

我调用这个函数,就可以输出一个数组的元素,这很好,但是,如果我的需求变化下,我想输出以逗号分隔,而不是以行分隔,怎么办?
如果不用委托,我们只能直接修改它,或者再写一个函数,比如
void PrintElementsInTheArray2(int[] array)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        if (i == 0) Console.Write(array[i]);
        else Console.Write("," + array[i]);
    }
}

当然,我们也可以把这两个函数写在一起,比如:
void PrintElementsInTheArray(int[] array, bool SplitByComma)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        if (SplitByComma)
        {
            if (i == 0) Console.Write(array[i]);
            else Console.Write(", " + array[i]);
        }
        else
        {
            Console.WriteLine(array[i]);
        }
    }
}

但是你看,写来写去,我们都只能由这个函数的定义者决定整个输出的细节,而遍历数组的流程无法真正复用,如果我们要再换一种输出方式,还得修改代码。有没有一劳永逸的办法呢?那就是使用委托,直接把foreach的循环空出来,让调用者自己填空:
void PrintElementsInTheArray(int[] array, Action<bool, int> action)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        action(i == 0, array[i]);
    }
}

好了,有了这个通用的函数,再实现上面两个需求就很简单了,比如
PrintElementsInTheArray(data, (isFirst, item) => Console.WriteLine(item));

这就是按行输出
PrintElementsInTheArray(data, (isFirst, item) => {
    if (isFirst) Console.Write(item); else Console.Write(", " + item);
});

这就是按照逗号输出

我们还可以不在控制台输出,而在Web页面上输出
PrintElementsInTheArray(data, (isFirst, item) => Response.WriteLine(item));

#8


该回复于2013-10-23 22:24:13被版主删除

#9


C# closure 还是很简单的
 public Func<int> c(int del)
{
            int y = del;
            Func<int> x = ()=>y++;
            return x;
}

 var func = c(3);var i = func();var i1 = func();var i2 = func(); 345

#10


引用 6 楼 caozhy 的回复:
回调函数,这一般是在C语言中这么称呼,对于定义一个函数,但是并不由自己去调用,而是由被调用者间接调用,都可以叫回调函数。本质上,回调函数和一般的函数没有什么区别,也许只是因为我们定义了一个函数,却从来没有直接调用它,这一点很奇怪,所以有人发明了回调函数这个词来统称这种间接的调用关系。

在包括C#在内的很多高级语言中,我们有其它更具体的名词来称呼它,比如事件处理函数,委托,匿名委托,匿名函数,Lambda表达式,所以很少直接称呼某个函数为回调函数,除非是编写和C打交道的程序。

闭包一般是指函数的嵌套定义中,内部的函数可以超越作用于“看见”外侧的变量,反之则不行,它描述的就是这样一种关系,比如

Action foo1 = () =>
{
    int i = 1;
    Action foo2 = () =>
    {
        int j = 2;
        Action foo3 = () =>
        {
            int k = 3;
            Console.WriteLine(i + j + k);
        };
        foo3();
        // error Console.WriteLine(k);
    };
    foo2();
    // error Console.WriteLine(j);
};
foo1();
// error Console.WriteLine(i);


闭包的作用是使方法内部变量的作用域延生到方法之外,相当于延长内部变量的生命周期。
这不是闭包的作用,而是闭包的副作用,好比汽车的作用绝对不是开得太快能把人撞死。

不恰当地使用匿名函数,会导致变量作用域不正确地延长,比如

class A
{
    static public Action action;
}

class Program
{
    void foo()
    {
        int i = 1;
        A.action = () => Console.WriteLine(i); //很明显,i这个变量按理说在foo()执行完了以后就没用了,但是当我们在匿名函数中访问它以后,它在函数退出之后仍然有效。
    }
    void bar()
    {
        A.action();
    }
    void Main()
    {
        foo();
        bar();
    }
}
谢谢曹版主

#11


本帖最后由 caozhy 于 2013-10-23 22:39:00 编辑
关于6L我再多解释几句。

延长变量的生命周期,只是一种现象,并不是错误,也不是副作用。好比汽车可以开得很快只是一种现象而已。
导致错误的原因是因为“代码没有按照设计者的意图工作”,闭包只是使得代码变得复杂,导致更有可能使得它不按照编写者的意图工作,明白这个逻辑关系么?
好比
int i = arr[3];
这行代码对不对?
不好说,如果arr长度是3,那么这样写,越界了。如果arr长度是100,但是你想访问的是第三个元素,你这么写访问了第四个,还是不对,因此,导致代码不对的原因不是代码本身(否则编译器就可以拦下来了),而是它和你的意图不一致。
我们看这样两段代码:
//在另一个很远的地方
public const int MaxLength = 2;
...
int[] arr = new int[MaxLength];
int i = arr[3];


int[] arr = new int[2];
int i = arr[3];

我问你,哪个代码中的错误容易被发现?
显然是后者。
一样的道理,闭包只是使得错误更隐蔽,它本身不是一种错误或者问题。
好比汽车开得快会撞死人,但是开多快会撞死人?这个说不准。你在闹市区开70码是作死,但是在高速上开120一点问题没有。

言归正传,闭包延长了变量的生命周期会导致哪些原本很容易发现的问题不容易发现,我可以举两个例子,
第一个是导致原本应该释放的大量内存没有及时释放,造成性能问题。这个很好理解。
再有,我们都知道,如果一个对象拥有非托管资源,并且用Dispose释放过,那么继续访问或者调用它是错误的,比如
using (obj)
{
   ...
}
obj.foo();
这个明显就是错误的,using之后,这个对象就不要再调用了。
但是如果有闭包,问题就可能很隐蔽:
Action a;
using (obj)
{
     a = () => obj.foo(); 
}
a();
这段代码粗略一看,我没有在using之外再访问它的foo()方法啊。
其实因为闭包的缘故,obj被带了出来。
那么这么写,可能就会有问题。并且不易发现。所以要小心。
注意,我用了“可能”这个词。

#12


引用 7 楼 caozhy 的回复:
回调函数最主要的作用,不是“获取调用者内部的对象,包括方法体,变量”,而是让一个函数可以实现总体流程的复用,同时让调用者“填空”,自定义某个细节。而一般的函数,则只能复用功能本身。

比如说我编写这么一个函数:
void PrintElementsInTheArray(int[] array)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        Console.WriteLine(array[i]);
    }
}

我调用这个函数,就可以输出一个数组的元素,这很好,但是,如果我的需求变化下,我想输出以逗号分隔,而不是以行分隔,怎么办?
如果不用委托,我们只能直接修改它,或者再写一个函数,比如
void PrintElementsInTheArray2(int[] array)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        if (i == 0) Console.Write(array[i]);
        else Console.Write("," + array[i]);
    }
}

当然,我们也可以把这两个函数写在一起,比如:
void PrintElementsInTheArray(int[] array, bool SplitByComma)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        if (SplitByComma)
        {
            if (i == 0) Console.Write(array[i]);
            else Console.Write(", " + array[i]);
        }
        else
        {
            Console.WriteLine(array[i]);
        }
    }
}

但是你看,写来写去,我们都只能由这个函数的定义者决定整个输出的细节,而遍历数组的流程无法真正复用,如果我们要再换一种输出方式,还得修改代码。有没有一劳永逸的办法呢?那就是使用委托,直接把foreach的循环空出来,让调用者自己填空:
void PrintElementsInTheArray(int[] array, Action<bool, int> action)
{
    for (int i = 0; i < array.GetLength(0); i++)
    {
        action(i == 0, array[i]);
    }
}

好了,有了这个通用的函数,再实现上面两个需求就很简单了,比如
PrintElementsInTheArray(data, (isFirst, item) => Console.WriteLine(item));

这就是按行输出
PrintElementsInTheArray(data, (isFirst, item) => {
    if (isFirst) Console.Write(item); else Console.Write(", " + item);
});

这就是按照逗号输出

我们还可以不在控制台输出,而在Web页面上输出
PrintElementsInTheArray(data, (isFirst, item) => Response.WriteLine(item));


这段委托的例子写的真好! 回调函数和闭包回调函数和闭包

#13


该回复于2013-10-24 00:37:22被版主删除

#14


该回复于2013-10-24 09:11:56被管理员删除

#15


回调函数和闭包,学习一下

#16


引用 11 楼 caozhy 的回复:
关于6L我再多解释几句。

延长变量的生命周期,只是一种现象,并不是错误,也不是副作用。好比汽车可以开得很快只是一种现象而已。
导致错误的原因是因为“代码没有按照设计者的意图工作”,闭包只是使得代码变得复杂,导致更有可能使得它不按照编写者的意图工作,明白这个逻辑关系么?
好比
int i = arr[3];
这行代码对不对?
不好说,如果arr长度是3,那么这样写,越界了。如果arr长度是100,但是你想访问的是第三个元素,你这么写访问了第四个,还是不对,因此,导致代码不对的原因不是代码本身(否则编译器就可以拦下来了),而是它和你的意图不一致。
我们看这样两段代码:
//在另一个很远的地方
public const int MaxLength = 2;
...
int[] arr = new int[MaxLength];
int i = arr[3];


int[] arr = new int[2];
int i = arr[3];

我问你,哪个代码中的错误容易被发现?
显然是后者。
一样的道理,闭包只是使得错误更隐蔽,它本身不是一种错误或者问题。
好比汽车开得快会撞死人,但是开多快会撞死人?这个说不准。你在闹市区开70码是作死,但是在高速上开120一点问题没有。

言归正传,闭包延长了变量的生命周期会导致哪些原本很容易发现的问题不容易发现,我可以举两个例子,
第一个是导致原本应该释放的大量内存没有及时释放,造成性能问题。这个很好理解。
再有,我们都知道,如果一个对象拥有非托管资源,并且用Dispose释放过,那么继续访问或者调用它是错误的,比如
using (obj)
{
   ...
}
obj.foo();
这个明显就是错误的,using之后,这个对象就不要再调用了。
但是如果有闭包,问题就可能很隐蔽:
Action a;
using (obj)
{
     a = () => obj.foo(); 
}
a();
这段代码粗略一看,我没有在using之外再访问它的foo()方法啊。
其实因为闭包的缘故,obj被带了出来。
那么这么写,可能就会有问题。并且不易发现。所以要小心。
注意,我用了“可能”这个词。
真心感谢,受益匪浅

#17


这么直击本质的代码,信手拈来。
曹斑竹真乃大神。

#18


    回调函数貌似是C/C++的说法,还真没听过C#有回调函数。

#19


引用 17 楼 sj178220709 的回复:
这么直击本质的代码,信手拈来。
曹斑竹真乃大神。

同意此观点!

#20


C#管那叫委托什么的。js里倒是叫callback。

#21


楼主认为回调函数的概念其实不是很正确,回调函数并不由自己去调用,而是由被调用者间接调用。回调函数作为参数传给被调用者,其作用是告诉被调用者在调用的时候需要调用者的这个函数,而不必知道这个函数具体是干嘛的。回调函数一般用在跨语言平台上,比如js↔Flex  ,java↔C。

#22


百度了一下闭包,表示没用过。。学习了

#23


匿名方法 经常会 碰到 闭包引起的 副作用,其它的地方还没见到过。

#24


回调函数稍微容易理解;
闭包一直迷迷糊糊的;

#25


其实就是C#的一种委托,让别的对象或线程来帮你执行你 要完成的事情

#26


老是说我感觉比闭包在C语言中用goto类似地实现。不知对不对。

int main()
{
int i=0;
goto here;
back:
printf("%d",i);
return 0;
here:
i++;
goto back:
}

#27


好贴。进来学习了