请给我看一个显示“需要”代表(或)函数指针的情况
Posted
技术标签:
【中文标题】请给我看一个显示“需要”代表(或)函数指针的情况【英文标题】:Please show me a situtation which shows `need` for Delegates (or) function pointers 【发布时间】:2009-11-26 08:42:41 【问题描述】:我将为正在学习级别程序员的学生上一堂关于“委托和回调”的课程。他们有基本的 c/c++ & c# 背景。而不是直接展示如何使用它们。我想展示“为什么使用函数指针?”第一的。我想从一个例子开始,问他们“你会怎么做”?并让他们意识到对某些东西的需求,然后将他们介绍给 FunctionPointers、Delegates 和 CallBacks。
那么,任何人都可以给我展示一个很好的例子,它表明需要 C# 中的委托(或)C/C++ 中的函数指针。我不想要 GUI 示例中的事件处理示例,也不想要通过 add2numbers 等示例演示“如何使用委托”。
我正在寻找一些实际示例,让他们可以感受到 FunctionPointers、Delegates 和 CallBacks 的需求。
如果有好的文章,请发表。
【问题讨论】:
我想你已经有了答案——当你需要在某种对话系统中异步接收信息时,“GUI 中的事件处理”回调几乎就是它们所需要的全部内容 【参考方案1】:您可以向他们展示过滤软件中多个位置的项目列表的示例。
例如,您可能有
public List<Person> GetMale(List<Person> people)
List<Person> results = new List<Person>();
foreach (Person p in people)
if (p.IsMale)
results.Add(p);
return results;
或
public List<Person> GetFemale(List<Person> people)
List<Person> results = new List<Person>();
foreach (Person p in people)
if (!p.IsMale)
results.Add(p);
return results;
为避免在每个方法中重复 foreach
迭代,您需要提取实际条件(即在本例中为 谓词),并在其他地方实现它。
所以你将这两种方法替换为:
public List<Person> Filter(List<Person> people, Func<bool, Person> match)
List<Person> results = new List<Person>();
foreach (Person p in people)
if (match(p))
results.Add(p);
return results;
然后在你的代码中这样调用它:
List<Person> malePersons = Filter(people, p => p.IsMale);
List<Person> femalePersons = Filter(people, p => !p.IsMale);
请注意,实际条件现在是在迭代块之外提取的,您可以重用相同的方法来创建您喜欢的任何自定义过滤逻辑。通过提取此逻辑,您将问题委派给其他人,使您的代码松散耦合。
使用 C# 2.0 匿名方法语法,调用此方法如下所示:
List<Person> malePersons = Filter(people,
delegate (Person p) return p.IsMale; );
List<Person> femalePersons = Filter(people,
delegate (Person p) return !p.IsMale; );
或使用实际方法:
List<Person> malePersons = Filter(people, MaleMatch);
List<Person> femalePersons = Filter(people, FemaleMatch);
其中谓词定义为:
private bool MaleMatch(Person p)
return p.IsMale;
private bool FemaleMatch(Person p)
return !p.IsMale;
需要注意的是,我们传递的不是这些方法的result,而是实际的方法“指针”,所以在Filter
方法内部调用方法时会返回实际结果.
还要注意 LINQ in .Net 3.5 已经包含一个 Where
扩展方法,它与这个例子做同样的事情,以及许多其他使用委托来处理条件、投影和其他东西的方法,所以你基本上只需要传递一个委托带有适当的签名。
【讨论】:
【参考方案2】:我不确定您为什么不想使用 GUI 示例:“当我单击按钮时,我希望 X 发生 - 现在我如何表达 X?”的概念。挺好的。
其他例子:
我想发起一个话题:我如何表达我想要它做的事情? 我要过滤一些数据:如何表达过滤器? 我想投影一些数据:如何表达投影? 我想从网络上异步下载一个文件:我如何表达我想要在下载完成后发生的事情?基本上每一个都是在说“我想用一种简单的方式表达一些代码”。在每种情况下,您可以使用单个方法接口 - 委托/函数指针只是一种更方便的方式。
确实,如果有些学生习惯于使用单一方法接口(例如 Java 中的 Runnable
),那么这可能是一个很好的起点。想象一下,如果您可以通过说“在此处使用此方法...”来实现接口(在 Java 7 中,您似乎可以做到这一点;他们使用单一方法接口和方法引用代替专用委托类型。)在 C# 背景下,您还可以将 IComparer<T>
接口与 Comparer<T>
委托进行比较。
当然,当您有了委托的概念后,您可以引入 lambda 表达式(如果它是 C# 课程),向他们展示能够“内联”表达那一点逻辑是多么有用。然后向他们展示使用 lambdas 作为闭包来与本地环境交互是多么有用...
【讨论】:
可能是因为 GUI 在学习时往往会分散注意力。一个“纯”的代码示例更重要,并没有假设那么多的库功能 @jalf:对于一个实际的代码示例,也许 - 但解释 概念 我认为它很有用。事实上,如果使用手写而不是使用设计器,GUI 代码示例仍然可以非常小。【参考方案3】:我将展示如何编写/使用通用排序函数/方法,它将回调参数作为谓词。
【讨论】:
这很好,至少如果他们已经学习了基本的排序算法的话。【参考方案4】:异步调用:您调用将在后台执行的方法(通常是远程服务调用),并且您想指定方法完成时将执行哪些代码(因为您确实需要知道方法何时完成)。更多详情请看这里:http://msdn.microsoft.com/en-us/magazine/cc301332.aspx
【讨论】:
叮叮你中奖了!有很多示例(按钮按下是最明显的 UI 操作),包括 I/O 完成例程等,需要在方法完成时执行一些代码(回调)。在按钮的情况下,可能只是在某些处理完成后重新启用按钮,以便该功能再次可用。按钮 -> 禁用按钮 -> 启动带回调的异步线程 -> 线程完成 -> 输入回调并重新启用按钮。这可以防止在按下按钮后长时间操作期间出现可怕的沙漏。博学的穴居人【参考方案5】:Observer-Pattern 就是一个例子。 Callbacks/Delegates 的主要原因是,您希望减少耦合并增加架构的灵活性以进行进一步的开发。
【讨论】:
【参考方案6】:委托允许您将代码视为数据,因此无论何时您想以某种方式完成某事,但将细节留给调用方委托,委托都会派上用场。排序可能是最好的例子,但如一些答案所示,还有许多其他例子。
例如假设你想计时。由于无论您在计时什么,您基本上都希望经历相同的计时步骤,因此您可以让您的计时方法采用一个委托并以一致的方式计时。在伪代码中它可能看起来像这样
TimeThis(method_pointer)
setup_timing();
method_pointer(); // invoke the method
report_timing();
【讨论】:
【参考方案7】:又一个例子
假设您要定义一个为f(x)
形式的任何函数绘制曲线的方法。第一次尝试
public DrawFunction(string f)
for (int x = 0; x <= 10; x++)
DrawPoint( ??? ); // How are you calling f(x) here?
委托是将方法用作数据的一种手段。 IE。委托类型的变量或属性或参数可以存储方法。我们可以使用这样的委托来解决我们的问题:
public DrawFunction(Func<double, double> f)
for (int x = 0; x <= 10; x++)
DrawPoint(10.0 * x, f(x));
假设我们已经定义了这个方法
public double Square(double x)
return x * x;
现在你可以像这样绘制函数了
DrawFunction(Square);
请注意,我们不会在这里调用(执行)Square
,因此我们不会在Square
之后放置大括号()
。
我们也可以使用 lambda 表达式。我们得到了同样的结果
DrawFunction(x => x*x);
另一条曲线
DrawFunction(x => 1.0/(1.0 + x*x));
【讨论】:
【参考方案8】:您可以举个例子,说明事件是如何在 .NET 中实现的;您的学生可以很容易地与之相关。
【讨论】:
【参考方案9】:致乔恩·斯基特。是的,每个委托都可以表示为单方法接口,但您不能在一个类中实现它的不同版本。使用委托(函数指针),您可以拥有任意数量的实现 - 不同的名称但相同的签名:
class C
int Add(int a, int b)
return a + b;
int Mul(int a, int b)
return a * b;
;
但是你不能两次实现同一个接口(参见我上面的 C 类)。对于 C++ 和 C#,尽管我们可以使用接口模拟委托。但是对于 C in 来说,是实现回调和运行时多态性的必要工具。在 C++ 和 C# 中,它非常适合兼容性和便利性。
【讨论】:
我真的不明白你的答案。如果您的语言同时支持接口和委托,那么单个方法接口和委托之间没有实际区别。你可以有很多委托,你可以有很多接口的实现。在实现一个接口时,你需要做更多的工作,但是你在接口中得到了比在委托中更强的封装(恕我直言,你可以用奇怪的方式更容易地实现委托)。除此之外,它们可以简单地互换。 是的,我知道委托比单一方法接口方便得多的方式......因此,关于“想象一下,如果你可以通过说“只使用这个方法”来实现一个接口这边...""【参考方案10】:namespace WindowsApplication10
/// <summary>
/// This delegate will be used by the button
/// </summary>
public delegate Point GetCenter();
public partial class Form1 : Form
CentralButton central;
public Form1()
InitializeComponent();
central = new CentralButton(this.GetCenter);
this.Controls.Add(central);
public Point GetCenter()
return new Point(this.Width / 2, this.Height / 2);
protected override void OnSizeChanged(EventArgs e)
base.OnSizeChanged(e);
central.UpdateCenter();
/// <summary>
/// This button calculates its location in the center of the parent
/// </summary>
public class CentralButton : Button
GetCenter myGetCenterMethod;
public CentralButton(GetCenter findCenterMethod)
myGetCenterMethod = findCenterMethod;
public void UpdateCenter()
// use the delegate for obtain the external information
this.Location = myGetCenterMethod();
【讨论】:
【参考方案11】:查看 Joel 的 Can your programming language do this? 文章。
他有几个很好的例子,有两个函数几乎做同样的事情,但使用不同的函数来完成某个任务。
alert("get the lobster");
PutInPot("lobster");
PutInPot("water");
alert("get the chicken");
BoomBoom("chicken");
BoomBoom("coconut");
使用作为参数传递的函数进行重构:
function Cook( i1, i2, f )
alert("get the " + i1);
f(i1);
f(i2);
Cook( "lobster", "water", PutInPot );
Cook( "chicken", "coconut", BoomBoom );
【讨论】:
以上是关于请给我看一个显示“需要”代表(或)函数指针的情况的主要内容,如果未能解决你的问题,请参考以下文章
我使用密集等级函数为学生生成随机排名。请给我代码,只提取前5%的学生