- 浏览: 3046564 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
之前正好发了些帖子是关于CLR里的委托的,然后看到老赵说事件也应该是对象才好,我想提一下委托与方法和隐藏参数的关系,以此来类比到“事件对象”。
在C++、Java、C#等支持面向对象编程的语言里,“静态方法”与“实例方法”有显著的不同。一方面,静态方法不是虚方法,不参与继承相关的多态(但是可以参与参数化多态——泛型),而实例方法可以是虚方法,可以通过继承和覆盖达到多态的目的;另一方面,静态方法不需要实例的信息,而实例方法需要跟实例来提供执行环境,因而需要一个隐藏参数,“this”。下文的讨论主要针对后一方面。
考虑这样一段Java代码:
Accumulator中的add()与currentValue()都是实例方法,并且可以很明显看出它们对实例所提供的环境的依赖——它们使用了“this”,通过this才可以访问到实例变量value。上例中的acc.add(2),虽然参数列表里只有一个2,但方法的“接收者”(receiver)也很明确的出现在代码中,就是“点”之前的acc,它作为一个隐藏参数被传到了方法中;如果要把所有实际上用到的参数都显式写明,用伪代码可以写成:Accumulator.add(acc, 2)。
如果换用Python来写就会更明显:
Python要求声明实例方法时将提供环境的对象作为显式的第一个参数,习惯上命名为self。使用实例方法时则与Java等语言一样,把“self”作为隐藏参数传递。
如果不考虑虚方法的多态语义,把上面的Java代码换成C来写,会是类似:
(这里模仿Java等语言的语义,把分配空间与初始化的步骤分离了。实际要在C里模拟面向对象编程倒不会这么写……我只是为了突出“this”而已)
C里可以用裸的函数指针,它只是指向函数的代码,而不关心函数可能需要的执行环境。对上述Accumulator的例子中的add(),如果创建函数指针并使用,可以是这样:
(注:虽然我在注释里写了“member function”,注意我并不是指C++里的member function/pointer to member function。只是笼统的模拟一个概念而已)
说了半天,C#都还没登场呢,于是扯回正题,来看看C#的委托:
上例中,第20行的acc.Add是通过一个对象实例去引用一个MethodGroup,C#的自动转换规则为Accumulator.Add()方法针对acc实例创建了一个“闭合委托”(closed delegate),然后将该委托赋值给add变量。要怎么知道一个委托是“闭合”的还是“开放”(open)的呢?只要看看委托的Target属性是否为null就行。上例的add变量所引用的委托就是闭合的,add.Target == acc。也就是说,通过闭合委托调用一个方法,就跟直接通过实例调用实例方法一样,带有一个隐藏参数,“this”;开放委托则不包含“this”隐藏参数,看上去就跟C里的函数指针很类似。
虽然C#默认是为静态方法创建开放委托,为实例方法创建闭合委托,但可以通过反射为实例方法创建出开放委托来:
通过开放委托来调用实例方法,就很明显的展现出“this”隐藏参数的存在。
废那么话扯到C#的开放与闭合委托,我是想说明什么呢?先看看先前的几条tweet:
看看C#的闭合委托为我们提供了怎样的能力:
1、为方法创建别名。看这个例子:
本来Foo和Baz两个类没有任何继承关系,Bar()与Quux()两个方法的名字也不同,所以不可能通过继承多态的方式来统一的调用它们。但是由于C#的闭合委托给予了我们对实例方法创建“别名”的能力(例中action与foo.Bar之间、action与baz.Quux之间建立了别名关系),我们就可以在Invoke()方法里以统一的方式去调用这两个实例方法。
可能有人会想到跟duck-typing类比,不过这个跟duck-typing比较不同——duck-typing是要求方法名和参数列表都匹配,不关心接收者类型;闭合委托是方法名和接收者类型都不关心,只要参数列表的类型和个数都匹配就行。
2、可以将它看作对应的开放委托将“this”给curry化之后的形态,也就是:
因为闭合委托包含了对实例的引用,所以有足够信息对实例的状态做查询和操作。像是前面例子里的add、closedAdd,调用它们就达到了改变acc.Value的值的效果。
如果说方法的核心操作就是“调用”,那么事件呢?应该有两点:
1、添加/去除事件的监听器
2、发送一个事件
C#只允许在声明事件的类里发送事件,从外部来看实际上就只有第一点功能。
跟方法与实例的关系一样,C#里事件也有静态事件或者实例事件;后者必须依赖于实例才可以正常运行。C#里,在声明事件的类中通过事件的名字引用到的是它背后的委托,而不是事件本身;在声明事件的类外部则变成对add_XXX或remove_XXX方法语法糖,这两个方法返回void。如果要改变事件的“状态”,也就是为事件添加或去除监听器的话,必须要有类或实例的引用才行。老赵想说的,说不定是这样:(伪代码)
如果C#支持这种语言结构,那么代码中
1、ev就与foo.Bar建立了别名关系
2、ev隐藏着“this”参数,指向foo。
不知道老赵说的“事件对象”是不是像这样的呢?如果是的话,要自己实现出包装事件的对象看来并不困难,不过我一时能想到的办法都要用反射,至少要在开始用一次反射。真正的问题是C#没有专门语法来支持它的话,用起来不会太好看,或许也就失去了创建这种对象的意义。
更新:老赵说就是这样的,他发了一帖在这里:把事件当作对象进行传递
在C++、Java、C#等支持面向对象编程的语言里,“静态方法”与“实例方法”有显著的不同。一方面,静态方法不是虚方法,不参与继承相关的多态(但是可以参与参数化多态——泛型),而实例方法可以是虚方法,可以通过继承和覆盖达到多态的目的;另一方面,静态方法不需要实例的信息,而实例方法需要跟实例来提供执行环境,因而需要一个隐藏参数,“this”。下文的讨论主要针对后一方面。
考虑这样一段Java代码:
public class Accumulator { private int value; public Accumulator(int initValue) { this.value = initValue; } public void add(int increment) { this.value += increment; } public int value() { return this.value; } } //... public class Program { public static void main(String[] args) { Accumulator acc = new Accumulator(0); acc.add(2); acc.add(3); System.out.println(acc.value()); // 5 } }
Accumulator中的add()与currentValue()都是实例方法,并且可以很明显看出它们对实例所提供的环境的依赖——它们使用了“this”,通过this才可以访问到实例变量value。上例中的acc.add(2),虽然参数列表里只有一个2,但方法的“接收者”(receiver)也很明确的出现在代码中,就是“点”之前的acc,它作为一个隐藏参数被传到了方法中;如果要把所有实际上用到的参数都显式写明,用伪代码可以写成:Accumulator.add(acc, 2)。
如果换用Python来写就会更明显:
class Accumulator: def __init__(self, init_value = 0): self.__value = init_value def add(self, increment): self.__value += increment def value(self): return self.__value acc = Accumulator() acc.add(2) acc.add(3) print(acc.value())
Python要求声明实例方法时将提供环境的对象作为显式的第一个参数,习惯上命名为self。使用实例方法时则与Java等语言一样,把“self”作为隐藏参数传递。
如果不考虑虚方法的多态语义,把上面的Java代码换成C来写,会是类似:
typedef struct tagAccumulator { int value; } Accumulator; Accumulator* Accumulator_new() { return (Accumulator*)malloc(sizeof(Accumulator)); } void Accumulator_init(Accumulator* this, int init_value) { this->value = init_value; } void Accumulator_add(Accumulator* this, int increment) { this->value += increment; } int Accumulator_value(Accumulator* this) { return this->value; } void Accumulator_free(Accumulator* this) { free(this); }
(这里模仿Java等语言的语义,把分配空间与初始化的步骤分离了。实际要在C里模拟面向对象编程倒不会这么写……我只是为了突出“this”而已)
C里可以用裸的函数指针,它只是指向函数的代码,而不关心函数可能需要的执行环境。对上述Accumulator的例子中的add(),如果创建函数指针并使用,可以是这样:
#include <stdio.h> typedef void (*add_ptr_t)(Accumulator*, int); int main() { add_ptr_t pAdd = &Accumulator_add; // create pointer to "member function" Accumulator* acc = Accumulator_new(); // object allocation Accumulator_init(acc, 0); // object initialization // call "member function" through pointer pAdd(acc, 2); pAdd(acc, 3); printf("%d\n", Accumulator_value(acc)); Accumulator_free(acc); return 0; }
(注:虽然我在注释里写了“member function”,注意我并不是指C++里的member function/pointer to member function。只是笼统的模拟一个概念而已)
说了半天,C#都还没登场呢,于是扯回正题,来看看C#的委托:
using System; public class Accumulator { public int Value { get; private set; } public Accumulator(int initValue) { Value = initValue; } public void Add(int increment) { Value += increment; } } static class Program { static void Main(string[] args) { var acc = new Accumulator(0); // create a closed delegate Action<int> add = acc.Add; // invoke instance method acc.Add(2); // invoke instance method through closed delegate add(3); Console.WriteLine(acc.Value); } }
上例中,第20行的acc.Add是通过一个对象实例去引用一个MethodGroup,C#的自动转换规则为Accumulator.Add()方法针对acc实例创建了一个“闭合委托”(closed delegate),然后将该委托赋值给add变量。要怎么知道一个委托是“闭合”的还是“开放”(open)的呢?只要看看委托的Target属性是否为null就行。上例的add变量所引用的委托就是闭合的,add.Target == acc。也就是说,通过闭合委托调用一个方法,就跟直接通过实例调用实例方法一样,带有一个隐藏参数,“this”;开放委托则不包含“this”隐藏参数,看上去就跟C里的函数指针很类似。
虽然C#默认是为静态方法创建开放委托,为实例方法创建闭合委托,但可以通过反射为实例方法创建出开放委托来:
using System; public class Accumulator { public int Value { get; private set; } public Accumulator(int initValue) { Value = initValue; } public void Add(int increment) { Value += increment; } } static class Program { static void Main(string[] args) { var acc = new Accumulator(0); var addInfo = typeof(Accumulator).GetMethod("Add"); // create an open delegate var openAdd = (Action<Accumulator, int>)Delegate.CreateDelegate( typeof(Action<Accumulator, int>), addInfo); // invoke instance method acc.Add(2); // invoke instance method through open delegate openAdd(acc, 3); Console.WriteLine(acc.Value); } }
通过开放委托来调用实例方法,就很明显的展现出“this”隐藏参数的存在。
废那么话扯到C#的开放与闭合委托,我是想说明什么呢?先看看先前的几条tweet:
@jeffz_cn 写道
C#中的事件无法作为一个对象传递,我认为是一个语言设计上的失误
@rednaxelafx 写道
RT @jeffz_cn: C#中的事件无法作为一个对象传递,我认为是一个语言设计上的失误。//如果可以传递会是怎样的场景?
@jeffz_cn 写道
@rednaxelafx 最简单的,我可以把事件传入一个方法,然后再方法里添加handler咯。
@rednaxelafx 写道
@jeffz_cn 就是说如果你把指向实例方法的委托看作把实例curry了的方法话,“事件对象”就应该是把实例curry了的事件?外加可以用别名来引用事件的能力
看看C#的闭合委托为我们提供了怎样的能力:
1、为方法创建别名。看这个例子:
using System; public class Foo { public void Bar() { Console.WriteLine("Foo.Bar()"); } } public class Baz { public void Quux() { Console.WriteLine("Baz.Quux()"); } } static class Program { static void Invoke(Action action) { action(); } static void Main(string[] args) { Foo foo = new Foo(); Baz baz = new Baz(); Invoke(foo.Bar); Invoke(baz.Quux); } }
本来Foo和Baz两个类没有任何继承关系,Bar()与Quux()两个方法的名字也不同,所以不可能通过继承多态的方式来统一的调用它们。但是由于C#的闭合委托给予了我们对实例方法创建“别名”的能力(例中action与foo.Bar之间、action与baz.Quux之间建立了别名关系),我们就可以在Invoke()方法里以统一的方式去调用这两个实例方法。
可能有人会想到跟duck-typing类比,不过这个跟duck-typing比较不同——duck-typing是要求方法名和参数列表都匹配,不关心接收者类型;闭合委托是方法名和接收者类型都不关心,只要参数列表的类型和个数都匹配就行。
2、可以将它看作对应的开放委托将“this”给curry化之后的形态,也就是:
// create a closed delegate from an open delegate by currying Action<int> closedAdd = (int increment) => openAdd(acc, increment); // invoke the closed delegate as usual closedAdd(3);
因为闭合委托包含了对实例的引用,所以有足够信息对实例的状态做查询和操作。像是前面例子里的add、closedAdd,调用它们就达到了改变acc.Value的值的效果。
如果说方法的核心操作就是“调用”,那么事件呢?应该有两点:
1、添加/去除事件的监听器
2、发送一个事件
C#只允许在声明事件的类里发送事件,从外部来看实际上就只有第一点功能。
跟方法与实例的关系一样,C#里事件也有静态事件或者实例事件;后者必须依赖于实例才可以正常运行。C#里,在声明事件的类中通过事件的名字引用到的是它背后的委托,而不是事件本身;在声明事件的类外部则变成对add_XXX或remove_XXX方法语法糖,这两个方法返回void。如果要改变事件的“状态”,也就是为事件添加或去除监听器的话,必须要有类或实例的引用才行。老赵想说的,说不定是这样:(伪代码)
using System; // declare an event type, just like declaring a delegate type public event Action MyEventType; public class Foo { // declare an event public event Action Bar; public void TriggerBar() { var bar = Bar; if (null != bar) bar(); } } static class Program { static void PopulateEventHandlers(MyEventType ev) { ev += () => Console.WriteLine("Howdy"); ev += () => Console.WriteLine("Doody"); } static void Main(string[] args) { var foo = new Foo(); PopulateEventHandlers(foo.Bar); foo.TriggerBar(); // should print: // Howdy // Doody } }
如果C#支持这种语言结构,那么代码中
1、ev就与foo.Bar建立了别名关系
2、ev隐藏着“this”参数,指向foo。
不知道老赵说的“事件对象”是不是像这样的呢?如果是的话,要自己实现出包装事件的对象看来并不困难,不过我一时能想到的办法都要用反射,至少要在开始用一次反射。真正的问题是C#没有专门语法来支持它的话,用起来不会太好看,或许也就失去了创建这种对象的意义。
更新:老赵说就是这样的,他发了一帖在这里:把事件当作对象进行传递
发表评论
-
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22385(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对象的重量
2011-08-21 17:15 0http://domino.research.ibm.com/ ... -
GetCustomAttribute()每次都返回新Attribute实例
2009-11-10 10:30 0Jeffrey Zhao: 一次失败的尝试(上):原来GetC ... -
要让CLR挂掉的话(第二弹)……
2009-09-04 03:26 12868(Disclaimer:如果需要转 ... -
要让CLR挂掉的话……
2009-09-02 16:53 4775(Disclaimer:如果需要转载请先与我联系。 作者:Re ... -
趣味编程:函数式链表的快速排序
2009-08-31 08:53 3442(恢复自2009-08-28的备份 ... -
事件处理器导致内存泄漏
2009-08-25 15:03 0Memory leak via event handlers ... -
C# 3.0的类型推导
2009-08-23 12:24 0Howard Dierking: Lambda, Lambda ... -
把lock的意思给弄混了 T T
2009-08-20 17:49 2594悲剧啊……前几天有个同学不停问我Java里的同步问题,今天写C ... -
把IEnumerable<T>和IObservable<T>粘起来?
2009-07-23 03:02 0Channel 9: Expert to Expert: Br ... -
Scott Peterson: Variance, Thy Name is Ambiguity
2009-07-01 23:49 1634原文作者:Scott Peterson 原文地址:http:/ ... -
void无法协变
2009-06-30 11:17 0Eric Lippert The void is invari ... -
同一个表达式算出来的浮点数结果会不相等?
2009-05-30 03:27 0浮点数有很多可把玩的地方。例如下面这段C程序: #includ ... -
C#开始默认引用Microsoft.CSharp.dll
2009-05-20 16:14 0记得VB6的运行时么?留意到VB.NET的程序都需要额外的VB ... -
反射与显式实现接口的方法
2009-05-20 11:43 4051在前一帖里,我用到了下面三处Expression.Call() ... -
看到一个关于ref参数与多态的问题,记一下
2009-05-18 10:48 1940刚才读到Alan McGovern的一帖,问为什么形式参数是r ... -
C#的+=运算符两例
2009-05-06 18:18 2029刚偶尔看到了justjavac写的java解惑 - 半斤八两( ... -
Nullable的诡异之处……
2009-04-02 20:52 1826原来Nullable type是null的时候,以它作为被调用 ...
相关推荐
8. **URL编码**:即使无法完全隐藏参数,也可以对URL参数进行编码,防止因为特殊字符导致的安全问题,但这并不意味着数据被加密。 在开发过程中,合理运用上述技术能够有效地提升Web应用的安全性。同时,理解并实施...
这种设计虽然直观,但不利于扩展,因为每添加一个新语言就需要修改枚举和方法。 如果我们使用委托,可以创建一个新的委托类型,例如`GreetingDelegate`,它定义了一个接受`string`参数并返回`void`的方法。然后,`...
封装性保证了对象的内部状态对外部是隐藏的,外部代码不能直接访问对象内部的成员,而是通过对象暴露的接口和方法来间接访问。这种设计可以减少系统各部分之间的耦合,增加模块的独立性和可复用性。当事件作为封装的...
- 通过接口,可以减少类型暴露的属性和方法,从而便于保护类型对象。 综上所述,C#中的重载、重写、隐藏、数组、集合和委托都是编程中非常重要的概念。掌握这些概念可以帮助开发者更加高效地进行软件开发工作。
1. 声明一个委托类型,定义其参数列表和返回类型,使其与目标方法匹配。 2. 创建委托实例,传入要调用的方法名。 3. 使用委托实例来调用相应的方法。 例如: ```csharp public delegate void MyDelegate(string ...
总结一下,委托是C#中的一种强大的工具,允许你传递方法作为参数,实现回调机制和事件处理。事件则进一步封装了委托,提供了安全的发布/订阅模型,使得类可以通知其他对象其状态的变化。在实际开发中,理解并熟练...
这里的`EventHandler`是一个内置的委托类型,它接受`object`类型的`sender`参数和`EventArgs`类型的`e`参数。`OnMyEvent`方法是事件的触发器,`?.`操作符确保在没有订阅者时不会抛出异常。 讲义可能涵盖以下内容: ...
事件通常与委托(Delegate)一起使用,委托是引用方法的类型,相当于函数指针。有参委托意味着它可以传递参数,使得接收事件的处理函数能够根据传入的参数执行相应的操作。 索引器(Indexer)在C#中提供了类似数组...
3. **匿名方法与Lambda表达式**:在创建委托实例时,我们可以使用匿名方法或Lambda表达式,这使得代码更加简洁易读。 4. **事件处理**:委托在事件处理中起到桥梁作用,连接事件发布者和事件订阅者。 **事件...
在C#编程语言中,委托和事件是两个关键的概念,它们在处理方法调用和对象间的通信方面扮演着重要角色。对于任何C#开发者来说,理解这两者的差异至关重要,尤其是在面试过程中,这个问题经常被用来评估候选人的专业...
除了 `hide()` 和 `show()`,jQuery 还提供了一个更灵活的方法 `toggle()`,它可以交替执行隐藏和显示操作: ```javascript $("tr").click(function() { $(this).toggle(); }); ``` 上面的代码将为每个表格行添加...
### C# 中的委托与事件详解 #### 一、引言 在.NET Framework中,委托和事件是非常重要的概念。它们不仅频繁地出现在各种应用程序中,也是实现特定设计模式(如观察者模式)的关键组成部分。本文将通过两个示例来...
#### 2.7 委托和方法的异步调用 .NET Framework提供了多种方式进行异步调用,如`BeginInvoke`/`EndInvoke`和`async/await`等。通过异步调用,可以在不阻塞主线程的情况下执行耗时操作,从而提高应用程序的性能和...
它定义了一种方法签名,允许我们存储和传递方法作为参数,或者将方法作为其他方法的返回值。这样,我们就可以在运行时动态地组合和调用方法,实现回调机制或者策略模式等设计模式。 委托的关键特性有: 1. **类型...
委托可以作为其他方法的参数,这使得我们可以创建更通用的函数,这些函数能够在执行时调用不同的实现。例如,排序算法(如`Array.Sort()`)可以接受一个比较方法作为参数,这样就可以根据不同的比较逻辑对数组进行...
接下来,我们创建一个名为`map`的模板方法,它接受一个`mapfun`类型的委托和一个整数数组作为参数。模板方法负责遍历数组并将每个元素传递给委托处理,这样就可以将重复的逻辑封装起来。 ```csharp static void map...
4. **类的继承**:这是面向对象编程的核心概念之一,一个类可以继承另一个类的属性和方法。项目中可能包含了不同类之间的层次结构,以及如何通过继承来实现代码复用和扩展性。 5. **new 虚函数继承**:在C#中,如果...
委托允许我们将方法作为参数传递给其他方法,或者存储在变量中,这样就可以在之后的某个时间调用这些方法。C#提供了两种类型的委托:`Action`和`Func`。`Action`用于无返回值的方法,而`Func`则对应有返回值的方法。...
标题中的“委托工厂与用户可编程接口”是一个关于软件设计模式和技术的话题,它涉及到如何在应用程序中创建灵活、可扩展的用户界面,特别是在处理多时区时钟显示的情况下。在这个场景中,用户能够自定义他们想要查看...