Delphi 知识 彻底搞懂Delphi中的匿名方法

news2024/12/23 5:59:25

  

    

 

前言:

        顾名思义,匿名方法是一个没有与之相关的名字的过程或函数。一个匿名方法将一个代码块视为一个实体,可以分配给一个变量或作为一个方法的参数使用。此外,匿名方法可以引用变量,并在定义该方法的上下文中为变量绑定值。匿名方法可以用简单的语法进行定义和使用。它们类似于其他语言中定义的闭包结构。

目录

一、语法

二、使用匿名方法

三、匿名方法的变量绑定

1. 可变的装订图示

2. 作为事件的匿名方法

3. 可变的绑定机制

三、 匿名方法的效用

1. 变量的绑定

2. 使用的便利性

3. 使用参数的代码


一、语法

匿名方法的定义与普通的过程或函数类似,但没有名称。例如,下面这个函数返回一个被定义为匿名方法的函数:

function MakeAdder(y: Integer): TFuncOfInt;
begin
  Result := { start anonymous method } function(x: Integer) : Integer
  begin
    Result := x + y;
  end; { end anonymous method }
end;

函数 MakeAdder 返回一个它声明的没有名字的函数:一个匿名方法。

注意,MakeAdder 返回一个 TFuncOfInt 类型的值。一个匿名方法类型被声明为对一个方法的引用:

type
  TFuncOfInt = reference to function(x: Integer): Integer;

这个声明表示匿名方法: 

  1. 是一个函数
  2. 接受一个整数参数
  3. 返回一个整数值。

一般来说,匿名函数类型被声明为过程或函数: 

type
  TType1 = reference to procedure (parameterlist);
  TType2 = reference to function (parameterlist): returntype;

其中 parameterlist(参数表)是可选的。

下面是几个类型的例子: 

type
  TSimpleProcedure = reference to procedure;
  TSimpleFunction = reference to function(x: string): Integer;

一个匿名方法被声明为一个没有名字的过程或函数: 

// Procedure
procedure (parameters)
begin
  { statement block }
end;
// Function
function (parameters): returntype
begin
  { statement block }
end;

其中(参数)是可选的。

二、使用匿名方法

匿名方法通常被分配给某个事情,如这些例子中:

myFunc := function(x: Integer): string
begin
  Result := IntToStr(x);
end;

myProc := procedure(x: Integer)
begin
  Writeln(x);
end;

匿名方法也可以由函数返回或在调用方法时作为参数值传递。例如,使用上面刚刚定义的匿名方法变量myFunc: 

type
  TFuncOfIntToString = reference to function(x: Integer): string;

procedure AnalyzeFunction(proc: TFuncOfIntToString);
begin
  { some code }
end;

// Call procedure with anonymous method as parameter
// Using variable:
AnalyzeFunction(myFunc);

// Use anonymous method directly:
AnalyzeFunction(function(x: Integer): string
begin
  Result := IntToStr(x);
end;)

方法引用也可以被分配给方法以及匿名方法。比如说: 

type
  TMethRef = reference to procedure(x: Integer);
TMyClass = class
  procedure Method(x: Integer);
end;

var
  m: TMethRef;
  i: TMyClass;
begin
  // ...
  m := i.Method;   //assigning to method reference
end;

然而,反之亦然:你不能将一个匿名方法分配给一个普通的方法指针。方法引用是可管理的类型,但方法指针是不可管理的类型。因此,出于类型安全的考虑,不支持将方法引用分配给方法指针。例如,事件是方法指针值的属性,所以你不能为一个事件使用匿名方法。

三、匿名方法的变量绑定

匿名方法的一个关键特征是,它们可以引用变量,这些变量在它们被定义的地方对它们是可见的。此外,这些变量可以被绑定到值上,并与对匿名方法的引用一起被包装起来。这可以捕获状态并延长变量的寿命。

1. 可变的装订图示

再考虑一下上面定义的函数: 

function MakeAdder(y: Integer): TFuncOfInt;
begin
  Result := function(x: Integer): Integer
  begin
    Result := x + y;
  end;
end;

我们可以创建一个这个函数的实例,绑定一个变量值:

var
  adder: TFuncOfInt;
begin
  adder := MakeAdder(20);
  Writeln(adder(22)); // prints 42
end.

变量 adder 包含一个匿名方法,它将值20绑定到匿名方法代码块中引用的变量y上。即使该值超出了范围,这种绑定也会持续存在。

2. 作为事件的匿名方法

使用方法引用的一个动机是有一个可以包含约束变量的类型,也被称为闭包值。由于闭包在其定义环境中关闭,包括在定义点引用的任何局部变量,它们有必须被释放的状态。方法引用是被管理的类型(它们是引用计数的),所以它们可以跟踪这种状态,并在必要时释放它。如果一个方法引用或闭包可以自由地分配给一个方法指针,比如一个事件,那么就很容易产生具有悬空指针或内存泄漏的错误类型的程序。

Delphi的事件是属性的一种约定。除了类型的不同,事件和属性之间没有任何区别。如果一个属性是方法指针类型的,那么它就是一个事件。

如果一个属性是方法引用类型,那么它在逻辑上也应该被视为一个事件。然而,IDE并不把它当作一个事件。这对作为组件和自定义控件安装在IDE中的类来说很重要。

因此,要在一个组件或自定义控件上有一个可以使用方法引用或闭合值分配的事件,该属性必须是方法引用类型。然而,这很不方便,因为IDE并不承认它是一个事件。

下面是一个使用方法引用类型的属性的例子,所以它可以作为一个事件操作:

type
  TProc = reference to procedure;
  TMyComponent = class(TComponent)
  private
    FMyEvent: TProc;
  public
    // MyEvent property serves as an event:
    property MyEvent: TProc read FMyEvent write FMyEvent;
    // some other code invokes FMyEvent as usual pattern for events
  end;

// …

var
  c: TMyComponent;
begin
  c := TMyComponent.Create(Self);
  c.MyEvent := procedure
  begin
    ShowMessage('Hello World!'); // shown when TMyComponent invokes MyEvent
  end;
end;

3. 可变的绑定机制

为了避免产生内存泄漏,更详细地了解变量绑定过程是很有用的。

在程序、函数或方法(以下简称 "例程")开始时定义的局部变量,通常只在该例程处于活动状态时存在。匿名方法可以延长这些变量的生存期。

如果一个匿名方法在其主体中引用了一个外部局部变量,那么这个变量就被 "捕获 "了。捕获意味着延长了变量的寿命,因此它的寿命与匿名方法的值一样长,而不是与它的声明例程一起死亡。注意,变量捕获是指捕获变量--而不是值。如果一个变量的值在被匿名方法捕获后发生了变化,那么匿名方法捕获的变量的值也会发生变化,因为它们是具有相同存储空间的同一个变量。捕获的变量存储在堆中,而不是堆栈中。

匿名方法的值是方法引用类型的,并且是引用计数的。当给定的匿名方法值的最后一个方法引用超出范围,或被清除(初始化为nil)或最终确定,它所捕获的变量最终也会超出范围。

在多个匿名方法捕获同一个局部变量的情况下,这种情况会更加复杂。为了理解在所有情况下这是如何工作的,有必要对实现的机制进行更精确的说明。

每当一个局部变量被捕获,它就会被添加到一个与其声明的例程相关的 "框架对象 "中。每个在例程中声明的匿名方法都会被转换成与其包含的例程相关的框架对象上的一个方法。最后,任何由于匿名方法值被构建或变量被捕获而创建的框架对象都会通过另一个引用链接到它的父框架上--如果有这样的框架存在的话,并且如果有必要的话,可以访问一个捕获的外部变量。这些从一个框架对象到它的父框架的链接也是参考计算的。在一个嵌套的本地例程中声明的匿名方法,从其父例程中捕获变量,使该父框架对象保持活力,直到它自己超出范围。

例如,考虑这种情况:
 

type
  TProc = reference to procedure;
procedure Call(proc: TProc);
// ...
procedure Use(x: Integer);
// ...

procedure L1; // frame F1
var
  v1: Integer;

  procedure L2; // frame F1_1
  begin
    Call(procedure // frame F1_1_1
    begin
      Use(v1);
    end);
  end;

begin
  Call(procedure // frame F1_2
  var
    v2: Integer;
  begin
    Use(v1);
    Call(procedure // frame F1_2_1
    begin
      Use(v2);
    end);
  end);
end;

每个例程和匿名方法都有一个框架标识符,以便更容易识别哪个框架对象链接到哪个:

  1. v1是F1中的一个变量
  2. v2是F1_2中的一个变量(由F1_2_1捕获)。
  3. F1_1_1的匿名方法是F1_1中的一个方法。
  4. F1_1链接到F1(F1_1_1使用v1)。
  5. F1_2的匿名方法是F1中的一个方法。
  6. F1_2_1的匿名方法是F1_2中的一个方法。

框架F1_2_1和F1_1_1不需要框架对象,因为它们既没有声明匿名方法也没有被捕获的变量。它们也不在嵌套的匿名方法和外部捕获变量之间的任何父子关系路径上。(它们有隐含的框架存储在栈上)。

仅仅给了匿名方法F1_2_1一个引用,变量v1和v2就被保留了下来。相反,如果唯一超过F1调用时间的引用是F1_1_1,那么只有变量v1被保留下来。

有可能在方法引用/框架链接链中创建一个循环,导致内存泄漏。例如,将匿名方法直接或间接地存储在匿名方法本身捕获的变量中,就会产生一个循环,导致内存泄漏。

三、 匿名方法的效用

匿名方法提供的不仅仅是一个简单的指向可调用事物的指针。它们提供了几个优点: 

  1. 绑定变量值
  2. 定义和使用方法的简单方法
  3. 易于使用代码进行参数化

1. 变量的绑定

匿名方法提供了一个代码块和变量绑定到它们所定义的环境中,即使该环境不在范围内。一个指向函数或过程的指针不能做到这一点。

例如,上面的代码样本中的语句adder := MakeAdder(20);产生了一个变量adder,封装了一个变量与数值20的绑定。

其他一些实现这种结构的语言把它们称为闭包。从历史上看,我们的想法是,对adder := MakeAdder(20);这样的表达式进行评估会产生一个闭包。它代表了一个对象,其中包含了对函数中引用的、在函数之外定义的所有变量的绑定的引用,从而通过捕获变量的值来关闭它。

2. 使用的便利性

下面的例子显示了一个典型的类定义,定义一些简单的方法,然后调用它们: 

type
  TMethodPointer = procedure of object; // delegate void TMethodPointer();
  TStringToInt = function(x: string): Integer of object;

TObj = class
  procedure HelloWorld;
  function GetLength(x: string): Integer;
end;

procedure TObj.HelloWorld;
begin
  Writeln('Hello World');
end;

function TObj.GetLength(x: string): Integer;
begin
  Result := Length(x);
end;

var
  x: TMethodPointer;
  y: TStringToInt;
  obj: TObj;

begin
  obj := TObj.Create;

  x := obj.HelloWorld;
  x;
  y := obj.GetLength;
  Writeln(y('foo'));
end.

这与使用匿名方法定义和调用的相同方法形成了对比: 

type
  TSimpleProcedure = reference to procedure;
  TSimpleFunction = reference to function(x: string): Integer;

var
  x1: TSimpleProcedure;
  y1: TSimpleFunction;

begin
  x1 := procedure
    begin
      Writeln('Hello World');
    end;
  x1;   //invoke anonymous method just defined

  y1 := function(x: string): Integer
    begin
      Result := Length(x);
    end;
  Writeln(y1('bar'));
end.

注意到使用匿名方法的代码是多么的简单和简短。如果你想明确而简单地定义这些方法并立即使用它们,而不需要为创建一个可能永远不会在其他地方使用的类而付出开销和努力,这就是理想的做法。这样的代码更容易理解。

3. 使用参数的代码

匿名方法使得编写以代码为参数的函数和结构更加容易,而不仅仅是数值。

多线程是匿名方法的一个很好的应用。如果你想并行地执行一些代码,你可能有一个parallel-for函数,看起来像这样: 

type
  TProcOfInteger = reference to procedure(x: Integer);

procedure ParallelFor(start, finish: Integer; proc: TProcOfInteger);

ParallelFor过程在不同的线程上迭代一个过程。假设这个过程使用线程或线程池正确而有效地实现,那么它就可以很容易地用于利用多处理器的优势: 

procedure CalculateExpensiveThings;
var
  results: array of Integer;
begin
  SetLength(results, 100);
  ParallelFor(Low(results), High(results),
    procedure(i: Integer)                           // \
    begin                                           //  \ code block
      results[i] := ExpensiveCalculation(i);        //  /  used as parameter
    end                                             // /
    );
  // use results
  end;

这与没有匿名方法的情况下需要做的事情形成对比:可能是一个带有虚拟抽象方法的 "任务 "类,以及ExpensiveCalculation的具体后裔,然后将所有的任务添加到队列中--几乎没有那么自然或整合。

在这里,"并行换 "算法是被代码参数化的抽象概念。在过去,实现这种模式的常见方法是使用一个具有一个或多个抽象方法的虚拟基类;考虑TThread类和它的抽象Execute方法。然而,匿名方法使这种模式--使用代码对算法和数据结构进行参数化--变得容易得多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/467050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Off-CPU分析

前言 性能问题可以分为两种类型: On-CPU:线程花时间在CPU上运行的地方; Off-CPU:在I/O,锁,计数器,分页/交换上阻塞等待的时间 Off-CPU的分析是一种性能分析的方法,用于测量和研究Off…

java懒加载实现

懒加载,也称为“不需要”加载,是一种内存管理方式。在 Java中,当一个类不再使用时,就会将其转化为另一个类对象。这也是所谓的“垃圾回收”。java中的懒加载有三种方式: 3、在对象被回收时,会将其销毁。 在…

DIY可视化必看教程 FLEX组件使用,教大家如何布局界面

DIY可视化必看教程FLEX组件使用 水平布局实现、两端对齐 1、拖个FLEX组件过来,排列方向改为水平。 2、拖个文件内容组件进去、栅格化到0 3、复制多一个文本内容组件 4、修改FLEX组件显示对齐方式 5、图标对齐 6、修改FLEX组件对齐方式 7、修改中间占位大&#xff0…

Windows 11 本地 php环境搭建:PHP + Apache + MySQL 安装和环境配置

目录 前言1. PHP 的下载、安装和配置1.1 下载 php1.2 安装 php1.3 配置 php 系统变量1.4 配置 php.ini 2. Apache 的下载、安装和配置2.1 下载 Apache2.2 安装 Apache2.3 修改配置 Apache2.4 指定服务端口(非必须)2.5 配置系统变量2.6 安装服务2.7 启动服…

计算机网络闲谈01——QUIC协议

计算机网络闲谈01——QUIC协议 预备知识 重传机制 RTT 一个连接的往返时间 RTO 重传超时时间 RTT和RTO 的关系是:由于网络波动的不确定性,每个RTT都是动态变化的,所以RTO也应随着RTT动态变化。 流量控制 对发送方发送速率的控制 称之为…

【BIM+GIS】BIM模型导入GIS软件之前的一些处理设置

文章目录 一、模型位置发生偏移二、模型对象丢失或增加三、模型材质发生变化四、导出过程缓慢五、模型属性批量丢失一、模型位置发生偏移 在视图→可见性/图形替换模型类别→场地(VV可见性快捷),勾选项目基点。 单击选中项目基点,在属性中修改几点坐标。 即使修改了项目基…

界面控件DevExpress Blazor UI v22.2 - 支持.NET 7

DevExpress拥有.NET开发需要的所有平台控件,包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具,该组件拥有众多新产品和数十个具有高影响力的功能,可为桌面、Web和移动应…

# Java 并发编程的艺术(二)

Java 并发编程的艺术(二) 文章目录 Java 并发编程的艺术(二)并发编程的挑战上下文切换如何减少上下文的切换 死锁资源限制的挑战 Java 并发机制的底层实现原理volatile 的应用synchronized 的实现原理与应用三大特性实现原理 Java…

智慧冷链园区三维可视化,数字孪生助力大数据实时监控

近年来,业界学者及企业就智慧冷链物流展开深入研究,2010 年 IBM 发布的《智慧的未来供应链》研究报告中提出智慧供应链概念,并由此延伸出智慧物流概念,即智慧物流是以信息化为依托并广泛应用物联网、人工智能、大数据、云计算等技…

基于U-Net系列的医学图像分割

U-Net 在FCN 的基础上增加了上采样操作的次数和跳跃连接,使用跳跃连接将解码器的输出特征与编码器的语义特征融合,提高了分割精度,改善了 FCN 上采样不足的问题。 U-Net中没有全连接层,通过互连卷积与反卷积过程中的特征&#xff…

一文打通java泛型

目录 为什么要有泛型 生活场景 泛型的设计背景 泛型的概念 那么为什么要有泛型呢,直接Object不是也可以存储数据吗? 在集合中使用泛型 自定义泛型结构 注意点 自定义泛型结构:泛型类 自定义泛型结构:泛型方法 泛型在…

【微服务笔记23】使用Spring Cloud微服务组件从0到1搭建一个微服务工程

这篇文章,主要介绍如何使用Spring Cloud微服务组件从0到1搭建一个微服务工程。 目录 一、从0到1搭建微服务工程 1.1、基础环境说明 (1)使用组件 (2)微服务依赖 1.2、搭建注册中心 (1)引入…

网课/网校/知识付费/在线教育系统,100%全功能开源,可免费商用

一、开源项目简介 酷瓜云课堂,依托腾讯云基础服务架构,采用C扩展框架Phalcon开发,GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源知识付费系统,开源在线教育系统。 酷瓜云课堂 - 网课系…

【LeetCode: 322. 零钱兑换 | 暴力递归=>记忆化搜索=>动态规划 | 背包模型】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

JavaScript 知识总结中篇(更新版)

72.get 请求传参长度的误区 常说:get 请求参数的大小存在限制,而 post 请求的参数大小无限制。 实际上 HTTP 协议并未规定 get / post 的请求参数大小限制。 纠正误区:是浏览器或 web 服务器对 get 请求参数的最大长度显示进行限制&#xf…

Nature Neuroscience:高家红团队首次发布中国人脑连接组计划研究成果及其大数据资源

人类生活在充满多样性的世界里。长久以来的研究发现,人类的脑与行为受到基因、环境和文化及其相互作用的塑造,然而这种影响发生的机制始终缺乏系统性探索与研究。近年来,前沿神经影像技术方法飞速进步,推动着多模态脑成像大数据集…

索尼ILCE-7SM3覆盖恢复案例

ILCE-7SM3算是索尼的流量级产品了,目前使用的比较多。今天我们来看一个格式化后又覆盖的恢复案例,看看这种情况下如何恢复残留的视频素材。 故障文件:80G SD卡 故障现象: 80G卡实际容量在74.5G左右,ExFat文件系统,格式化后又录…

Java8新特性-Stream

文章目录 简介Stream 的特性创建Stream通过集合创建流通过数组创建流通过Stream.of方法创建流创建规律的无限流创建无限流创建空流 Stream操作分类中间操作无状态filtermapflapMap 有状态distinctsortedreversedthenComparinglimitskipconcat 终结操作非短路操作forEachreducec…

D. Kilani and the Game(BFS模拟向四周漫延的过程)

Problem - D - Codeforces Kilani正在和他的朋友玩一个游戏。这个游戏可以表示为一个nm的网格,其中每个单元格都是空的或者被阻塞的,并且每个玩家在一些单元格中拥有一个或多个城堡(一个单元格中没有两个城堡)。 游戏分轮进行。每…

JS高级 -- 构造函数、数据常用函数

1. 深入对象 1.1 创建对象三种方式 利用对象字面量创建对象 const o {name:佩奇 }利用 new object 创建对象 const o new Object({ name:佩奇}) console.log(o) // {name: 佩奇}利用构造函数创建对象 1.2 构造函数 构造函数:是一种特殊的函数,主要…