Sunday, October 12, 2014

Reflecting properties

A recently posted issue (CLJCLR-45) raises the question of the behavior of ClojureCLR with respect to properties in interfaces  The example is:

(def o 
  (reify IAsyncResult 
    (get_AsyncState [_] (println "Hello") 7)))

(.AsyncState o)

The question is why the .AsyncState interop call generates a missing method exception. There is also an assertion that calling AsyncState on o from C# code (by pulling in the AOT-compiled code) works okay.

Well, yes and no. Equivalent operations in C# and ClojureCLR yield the same results; in fact, they yield essentially identical IL code.

Let's start with C#.  The recommended method for accessing a Var is via the methods in the clojure.clr.api.Clojure class. (See Using ClojureCLR in a C# project.) Assume the code above is the clojure.junk namespace.  You would write:

// load clojure.junk
IFn require = Clojure.var("clojure.core", "require");

// access o
IFn deref = Clojure.var("clojure.core", "deref");
dynamic o = deref.invoke(Clojure.var("clojure.junk", "o"));

The method Clojure.var returns an IFn. To access a non-function value, the recommended method is to deref the value, as shown.

Note that I have declared the C# variable o as dynamic.  If I had declared it as Object, the only way to call AsyncState or get_AsyncState on o would be to cast it to an IAsyncResult--this is the magic sauce that makes the calls work in C# -- and in ClojureCLR.  Declaring o as dynamic allows us call those methods directly -- which is what makes our life interesting.

Consider the following ways of making the calls in C#:

((IAsyncResult)o).get_AsyncState();  // 1
((IAsyncResult)o).AsyncState;        // 2
o.get_AsyncState();                  // 3
o.AsyncState                         // 4

The calls in lines 1 and 2 work, as you would expect. What you might not know is that the compiler generates identical IL code for the calls: the call in line 2 becomes a call to the get_AsyncState method. The call in line 3 works. It turns into a dynamic callsite (System.Runtime.CompilerServices.CallSite) with an InvokeMember binder looking for get_AsyncState. The call in line 4, however, fails with a missing member exception. That call also turns into a dynamic callsite with a GetMember binder looking for AsyncState. This binder does not work for properties.

What is the equivalent code in ClojureCLR? Assuming x is a local variable, look at:
(.get_AsyncState ^IAsyncResult x)    ; 1
(.AsyncState ^IAsyncResult x)        ; 2
(.get_AsyncState x)                  ; 3
(.AsyncState x)                      ; 4

The type hints in lines 1 and 2 allow the calls to be resolved at compile-time, thus avoiding reflection. The generated IL is identical to that produced by the C# compiler. The calls in lines 3 and 4 will generate reflection warnings. In ClojureCLR, this type of reflection results in the generation of dynamic callsites, as with dynamic in C#. The binders used are ones developed for ClojureCLR, but the results are identical: the call in line 3 succeeds, and the call in line 4 fails with a missing method exception.

Moral: Use properties only with type hinting.  Otherwise, use the get_XXX and set_XXX methods.

1 comment: