请给我看一个显示“需要”代表(或)函数指针的情况

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&lt;T&gt; 接口与 Comparer&lt;T&gt; 委托进行比较。

当然,当您有了委托的概念后,您可以引入 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 );

【讨论】:

以上是关于请给我看一个显示“需要”代表(或)函数指针的情况的主要内容,如果未能解决你的问题,请参考以下文章

渲染数学公式:算法

JavaScript 中怎么不带括号的调用函数

forge Viewer-无法旋转下面的标记?

我使用密集等级函数为学生生成随机排名。请给我代码,只提取前5%的学生

请给我讲讲在hibernate中,啥时候用“一对多”、“多对一”、“一对一”、“多对多”?

CUDA主函数