dart method calling context
In Dart there is a difference between an instance-method, declared as part of a class, and other functions (like closures and static functions).
Instance methods are the only ones (except for constructors) that can access this
. Conceptually they are part of the class description and not the object. That is, when you do a method call o.foo()
Dart first extracts the class-type of o
. Then it searches for foo
in the class description (recursively going through the super classes, if necessary). Finally it applies the found method with this
set to o
.
In addition to being able to invoke methods on objects (o.foo()
) it is also possible to get a bound closure: o.foo
(without the parenthesis for the invocation). However, and this is crucial, this form is just syntactic sugar for (<args>) => o.foo(<args>)
. That is, this just creates a fresh closure that captures o
and redirects calls to it to the instance method.
This whole setup has several important consequences:
You can tear off instance methods and get a bound closure. The result of
o.foo
is automatically bound too
. No need to bind it yourself (but also no way to bind it to a different instance). This is way, in your example,one.getMyId
works. You are actually getting the following closure:() => one.getMyId()
instead.It is not possible to add or remove methods to objects. You would need to change the class description and this is something that is (intentionally) not supported.
var f = o.foo;
implies that you get a fresh closure all the time. This means that you cannot use this bound closure as a key in a hashtable. For example,register(o.foo)
followed byunregister(o.foo)
will most likely not work, because each o.foo will be different. You can easily see this by tryingprint(o.foo == o.foo)
.You cannot transfer methods from one object to another. However you try to access instance methods, they will always be bound.
Looking at your examples:
print('one ${caller(one.getMyId)}'); //one 1 print('two ${caller(two.getMyId)}'); //two 2 print('one ${callerJustForThree(one.getMyId)}'); //NoSuchMethod Exception
These lines are equivalent to:
print('one ${caller(() => one.getMyId())}'); print('two ${caller(() => two.getMyId())}'); print('one ${callerJustForThree(() => one.getMyId())}';
Inside callerJustForThree
:
callerJustForThree(fn){ var three = new IDable(3); three.fn();}
The given argument fn
is completely ignored. When doing three.fn()
in the last line Dart will find the class description of three
(which is IDable
) and then search for fn
in it. Since it doesn't find one it will call the noSuchMethod
fallback. The fn
argument is ignored.
If you want to call an instance member depending on some argument you could rewrite the last example as follows:
main() { ... callerJustForThree((o) => o.getMyId());}callerJustForThree(invokeIDableMember){ var three = new IDable(3); invokeIDableMember(three);}
I'll try to explain, which is not necessarily a strength of mine. If something I wrote isn't understandable, feel free to give me a shout.
Think of methods as normal objects, like every other variable, too.
When you call caller(one.getMyId)
, you aren't really passing a reference to the method of the class definition - you pass the method "object" specific for instance one
.
In callerJustForThree
, you pass the same method "object" of instance one
. But you don't call it. Instead of calling the object fn
in the scope if your method, you are calling the object fn
of the instance three
, which doesn't exist, because you didn't define it in the class.
Consider this code, using normal variables:
void main() { var one = new IDable(1); var two = new IDable(2); caller(one.id); caller(two.id); callerJustForThree(one.id);}class IDable{ int id; IDable(this.id);}caller(param){ print(param);}callerJustForThree(param){ var three = new IDable(3); print(three.id); // This works print(param); // This works, too print(three.param); // But why should this work?}
It's exactly the same concept. Think of your callbacks as normal variables, and everything makes sense. At least I hope so, if I explained it good enough.