From 775b34f23009c831340c48dfa4e4c7dfa41bc1f8 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 23 Sep 2015 20:24:34 -0400 Subject: [PATCH] [Java.Interop.Dynamic] Rethink the whole BindConvert() nonsense Recall commit 90576545: > Doubly awesome -- and a huge part of the complication! -- is that it > uses DynamicMetaObject.BindConvert() to detect that the field type > is `int`, as field type information is required in order to lookup the > field, and thus we need to delay the actual JNI field lookup and > access until *after* we know the desired type. Figuring out how to While it is awesome, it is not without its problems: not only is it complex, but it's also limiting, in that it requires that you have a type to convert to. As noted in commit f62858ac: > "Implicit" member access fails: > > dynamic d = new DynamicJavaClass ("java/lang/Object"); > Assert.AreEqual ("java/lang/Object", d.JniClassName); > // BOOM [2] "Implicit" access -- access where we're not casting or assigning to a variable -- fails because everything is "delayed" until we know the target type, until DYnamicMetaObject.BindConvert() is invoked. Not only does this make things "icky", as the above description shows, but "thinking forward" it will make invoking `void` methods "interesting" -- there IS no type to convert to! This could plausibly be supported/"worked around" by having DynamicMetaObject.BindInvokeMember() immediately try to invoke the inferred method with a `void` return type, and if that fails go through the defer logic...but that won't help with a Fallback method. Things get ugly fast. *Then*, there's the problem of inferring "uninferrable" types: *eventually* we'll need to support invoking instance methods, at whichi point something like this is desirable: dynamic Arrays = new DynamicJavaClass ("java/util/Arrays"); dynamic list = Arrays.asList(new[]{"a", "b", "c"}); java.util.Arrays.asList() returns a java.util.List. If we assume that java.util.List is unbound (no binding generator!), how is the above supposed to work? We can't "defer" the invoke until we know the return type: THERE IS NO RETURN TYPE (other than `object`). We could in turn "hack" this by using "meta" methods, e.g. a `.as()` method: dynamic list = Arrays.asList(new[]{"a", "b", "c"}).as("java/util/List"); But that's just fugly and user hostile. Thus, DUMP the DynamicMetaObject.BindConvert() "deferred lookup" mechanism from commit 90576545, and replace it with a more straightforward (if uglier) "use Java Reflection to lookup ALL the members in advance!" implementation. This allows DynamicJavaClass to know all the valid Java member names (methods, fields) and check for validity within the DynamicJavaClass.MetaObject.Bind*() methods, simplifying the implementation and allowing "inferred" use: since we know all valid member names, we can immediately invoke them within the appropriate DynamicMetaObject.Bind*() method, instead of needing to wrap up a shit load of data into a DeferredConvert instance for later execution. Pro: simple and expected stuff now works: Assert.AreEqual ("java/lang/Object", d.JniClassName); // Look ma, no cast! No explicit variable assignment! Con: OMFG IS THIS SLOW. Like, wow, incredibly slow, like ~11s to lookup the names and parameter types of all methods on java.util.Arrays. Because of this, I split up/delayed method parameter type lookup until the method overloads are actually needed, which sped things up some, but resolving all the parameter and return types for all overloads of Arrays.binarySearch() is still a ~4s process. It's terrible. Furthermore, this is NOT a problem with JNI: running a similar algorithm on "native" Xamarin.Android is MUCH faster, completing in a fraction of a second, so it's not JNI, it's our array marshaling. TODO: Figure out WTF is making array marshaling SO GODDADMN SLOW. --- .../Java.Interop.Dynamic/DynamicJavaClass.cs | 433 ++++++++++++------ .../DynamicJavaClassTests.cs | 3 +- 2 files changed, 285 insertions(+), 151 deletions(-) diff --git a/src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs b/src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs index b27609c5b..89891bb61 100644 --- a/src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs +++ b/src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs @@ -17,6 +17,22 @@ public class DynamicJavaClass : IDynamicMetaObjectProvider { readonly static Func CreatePeerMembers; + readonly static JniInstanceMethodID Class_getConstructors; + readonly static JniInstanceMethodID Class_getFields; + readonly static JniInstanceMethodID Class_getMethods; + + readonly static JniInstanceMethodID Constructor_getParameterTypes; + + readonly static JniInstanceMethodID Field_getName; + readonly static JniInstanceMethodID Field_getType; + + readonly static JniInstanceMethodID Method_getName; + readonly static JniInstanceMethodID Method_getReturnType; + + readonly static internal JniInstanceMethodID Method_getParameterTypes; + + readonly static JniInstanceMethodID Member_getModifiers; + static DynamicJavaClass () { CreatePeerMembers = (Func) @@ -25,12 +41,36 @@ static DynamicJavaClass () typeof(JniPeerMembers).GetMethod ("CreatePeerMembers", BindingFlags.NonPublic | BindingFlags.Static)); if (CreatePeerMembers == null) throw new NotSupportedException ("Could not find JniPeerMembers.CreatePeerMembers!"); + + using (var t = new JniType ("java/lang/Class")) { + Class_getConstructors = t.GetInstanceMethod ("getConstructors", "()[Ljava/lang/reflect/Constructor;"); + Class_getFields = t.GetInstanceMethod ("getFields", "()[Ljava/lang/reflect/Field;"); + Class_getMethods = t.GetInstanceMethod ("getMethods", "()[Ljava/lang/reflect/Method;"); + } + using (var t = new JniType ("java/lang/reflect/Constructor")) { + Constructor_getParameterTypes = t.GetInstanceMethod ("getParameterTypes", "()[Ljava/lang/Class;"); + } + using (var t = new JniType ("java/lang/reflect/Field")) { + Field_getName = t.GetInstanceMethod ("getName", "()Ljava/lang/String;"); + Field_getType = t.GetInstanceMethod ("getType", "()Ljava/lang/Class;"); + } + using (var t = new JniType ("java/lang/reflect/Method")) { + Method_getName = t.GetInstanceMethod ("getName", "()Ljava/lang/String;"); + Method_getParameterTypes = t.GetInstanceMethod ("getParameterTypes", "()[Ljava/lang/Class;"); + Method_getReturnType = t.GetInstanceMethod ("getReturnType", "()Ljava/lang/Class;"); + } + using (var t = new JniType ("java/lang/reflect/Member")) { + Member_getModifiers = t.GetInstanceMethod ("getModifiers", "()I"); + } } public string JniClassName {get; private set;} JniPeerMembers members; + Dictionary> StaticFields; + Dictionary> StaticMethods; + public DynamicJavaClass (string jniClassName) { if (jniClassName == null) @@ -40,82 +80,116 @@ public DynamicJavaClass (string jniClassName) members = CreatePeerMembers (jniClassName); } + void LookupMethods () + { + if (StaticMethods != null) + return; + + StaticMethods = new Dictionary> (); + + using (var methods = new JavaObjectArray (Class_getMethods.CallVirtualObjectMethod (members.JniPeerType.SafeHandle), JniHandleOwnership.Transfer)) { + foreach (var method in methods) { + var s = Member_getModifiers.CallVirtualInt32Method (method.SafeHandle); + if ((s & JavaModifiers.Static) != JavaModifiers.Static) { + method.Dispose (); + continue; + } + + var name = JniEnvironment.Strings.ToString (Method_getName.CallVirtualObjectMethod (method.SafeHandle), JniHandleOwnership.Transfer); + + List overloads; + if (!StaticMethods.TryGetValue (name, out overloads)) + StaticMethods.Add (name, overloads = new List ()); + + var rt = new JniType (Method_getReturnType.CallVirtualObjectMethod (method.SafeHandle), JniHandleOwnership.Transfer); + var m = new JavaMethodInvokeInfo (name, true, rt, method); + overloads.Add (m); + } + } + } + + void LookupFields () + { + if (StaticFields != null) + return; + + StaticFields = new Dictionary> (); + + using (var fields = new JavaObjectArray (Class_getFields.CallVirtualObjectMethod (members.JniPeerType.SafeHandle), JniHandleOwnership.Transfer)) { + foreach (var field in fields) { + var s = Member_getModifiers.CallVirtualInt32Method (field.SafeHandle); + if ((s & JavaModifiers.Static) != JavaModifiers.Static) { + field.Dispose (); + continue; + } + + var name = JniEnvironment.Strings.ToString (Field_getName.CallVirtualObjectMethod (field.SafeHandle), JniHandleOwnership.Transfer); + + HashSet overloads; + if (!StaticFields.TryGetValue (name, out overloads)) + StaticFields.Add (name, overloads = new HashSet ()); + + using (var type = new JniType (Field_getType.CallVirtualObjectMethod (field.SafeHandle), JniHandleOwnership.Transfer)) { + var info = JniEnvironment.Current.JavaVM.GetJniTypeInfoForJniTypeReference (type.Name); + overloads.Add (name + "\u0000" + info.JniTypeReference); + } + + field.Dispose (); + } + } + } + DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject (Expression parameter) { - return new MetaStaticMemberAccessObject (parameter, this); + if (members == null) + throw new ObjectDisposedException (nameof (DynamicJavaClass)); + return new MetaObject (parameter, this); } - internal bool TryInvokeStaticMember (InvokeMemberBinder binder, DynamicMetaObject[] args, Type returnType, out object value) + internal bool TryInvokeStaticMember (string name, DynamicMetaObject[] args, out object value) { - Debug.WriteLine ("# DynamicJavaClass({0}).invoke({1}) with args({2}) as {3}", - JniClassName, binder.Name, string.Join (", ", args.Select (a => a.Value)), returnType); value = null; var margs = (List) null; + List overloads; + if (!StaticMethods.TryGetValue (name, out overloads)) + throw new InvalidOperationException ("Should not have reached InvokeStaticMember when there is no overload found for method '" + name + "'!"); + + var jtypes = GetJniTypes (args); try { - var encoded = GetEncodedJniSignature (binder, args, returnType); + var matches = overloads.Where (o => o.CompatibleWith (jtypes, args)); + var invoke = matches.FirstOrDefault (); + if (invoke == null) + return false; + margs = args.Select (arg => new JniArgumentMarshalInfo (arg.Value, arg.LimitType)).ToList (); var jvalues = margs.Select (a => a.JValue).ToArray (); - value = members.StaticMethods.CallMethod (encoded, jvalues); + value = members.StaticMethods.CallMethod (invoke.Signature, jvalues); return true; } - catch (JavaException e) { - e.Dispose (); - } finally { for (int i = 0; margs != null && i < margs.Count; ++i) { margs [i].Cleanup (args [i]); } - } - return false; - } - - static string GetEncodedJniSignature (InvokeMemberBinder binder, DynamicMetaObject[] args, Type returnType) - { - var sb = new StringBuilder (); - - sb.Append (binder.Name); - sb.Append ("\u0000"); - sb.Append ("("); - foreach (var arg in args) { - var argType = arg.LimitType; - var typeInfo = JniEnvironment.Current.JavaVM.GetJniTypeInfoForType (argType); - sb.Append (typeInfo.ToString ()); - } - sb.Append (")"); - sb.Append (JniEnvironment.Current.JavaVM.GetJniTypeInfoForType (returnType).JniTypeReference); - - return sb.ToString (); - } - - internal bool TryGetStaticMemberValue (string fieldName, Type fieldType, out object value) - { - Debug.WriteLine ("# DynamicJavaClass({0}).field({1}) as {2}", JniClassName, fieldName, fieldType); - var typeInfo = JniEnvironment.Current.JavaVM.GetJniTypeInfoForType (fieldType); - var encoded = fieldName + "\u0000" + typeInfo.JniTypeReference; - try { - value = members.StaticFields.GetValue (encoded); - return true; - } - catch (JavaException e) { - value = null; - e.Dispose (); - return false; + for (int i = 0; i < jtypes.Count; ++i) { + if (jtypes [i] != null) + jtypes [i].Dispose (); + } } } - internal bool TrySetStaticMemberValue (string fieldName, Type fieldType, object value) + static List GetJniTypes (DynamicMetaObject[] args) { - Debug.WriteLine ("# DynamicJavaClass({0}).field({1}) as {2} = {3}", JniClassName, fieldName, fieldType, value); - var typeInfo = JniEnvironment.Current.JavaVM.GetJniTypeInfoForType (fieldType); - var encoded = fieldName + "\u0000" + typeInfo.JniTypeReference; - try { - members.StaticFields.SetValue (encoded, value); - return true; - } - catch (JavaException e) { - e.Dispose (); + var r = new List (args.Length); + var vm = JniEnvironment.Current.JavaVM; + foreach (var a in args) { + try { + var at = new JniType (vm.GetJniTypeInfoForType (a.LimitType).JniTypeReference); + r.Add (at); + } catch { + r.Add (null); + } } - return false; + return r; } #if false @@ -135,131 +209,192 @@ Type CreateManagedPeerType () return type.CreateType (); } #endif - } - class DynamicMetaObject : DynamicMetaObject { + class MetaObject : DynamicMetaObject + { + delegate bool TryInvokeStaticMember (string name, DynamicMetaObject[] args, out object value); - public new T Value { - get {return (T) base.Value;} - } + public MetaObject (Expression parameter, DynamicJavaClass value) + : base (parameter, value) + { + // Console.WriteLine ("# MyMetaObject..ctor: paramter={0} {1} {2}", parameter.ToCSharpCode (), parameter.GetType (), parameter.Type); + Debug.WriteLine ("# MyMetaObject..ctor: value={0} {1}", value, value.GetType ()); + } - public Expression ExpressionAsT { - get {return Expression.Convert (Expression, typeof (T));} - } + public override IEnumerable GetDynamicMemberNames () + { + return Value.StaticFields.Keys.Concat ( + Value.StaticMethods.Keys + ); + } - public DynamicMetaObject (Expression parameter, T value) - : base (parameter, BindingRestrictions.GetInstanceRestriction (parameter, value), value) - { + public override DynamicMetaObject BindGetMember (GetMemberBinder binder) + { + HashSet overloads = GetField (binder.Name); + if (overloads == null) + return binder.FallbackGetMember (this); + + Func getValue = Value.members.StaticFields.GetValue; + + var e = Expression.Call (Expression.Constant (Value.members.StaticFields), getValue.Method, Expression.Constant (overloads.First ())); + Debug.WriteLine ("# MetaObject.BindGetMember: e={0}", e.ToCSharpCode ()); + return new DynamicMetaObject (e, BindingRestrictions.GetInstanceRestriction (Expression, Value)); + } + + HashSet GetField (string name) + { + Value.LookupFields (); + + HashSet overloads; + if (Value.StaticFields.TryGetValue (name, out overloads)) + return overloads; + return null; + } + + public override DynamicMetaObject BindInvokeMember (InvokeMemberBinder binder, DynamicMetaObject[] args) + { + Value.LookupMethods (); + List overloads; + if (!Value.StaticMethods.TryGetValue (binder.Name, out overloads)) + return binder.FallbackInvokeMember (this, args); + + foreach (var m in overloads) + m.LookupArguments (); + + if (!overloads.Any (o => o.Arguments.Count == args.Length)) + return binder.FallbackInvokeMember (this, args); + + TryInvokeStaticMember invoke = Value.TryInvokeStaticMember; + var value = Expression.Parameter (typeof (object), "value"); + var fallback = binder.FallbackInvokeMember (this, args); + Debug.WriteLine ("DynamicJavaClass.MetaObject.BindConvert: Fallback={0}", fallback.Expression.ToCSharpCode ()); + var call = Expression.Block ( + new[]{value}, + Expression.Condition ( + test: Expression.Call (ExpressionAsT, invoke.Method, Expression.Constant (binder.Name), Expression.Constant (args), value), + ifTrue: value, + ifFalse: fallback.Expression) + ); + return new DynamicMetaObject (call, BindingRestrictions.GetInstanceRestriction (Expression, Value)); + } + + public override DynamicMetaObject BindSetMember (SetMemberBinder binder, DynamicMetaObject value) + { + HashSet overloads = GetField (binder.Name); + if (overloads == null) + return binder.FallbackSetMember (this, value); + + Action setValue = Value.members.StaticFields.SetValue; + var e = Expression.Block ( + Expression.Call (Expression.Constant (Value.members.StaticFields), setValue.Method, + Expression.Constant (overloads.First ()), Expression.Convert (value.Expression, typeof(object))), + ExpressionAsT); + Debug.WriteLine ("# MetaObject.BindSetMember: e={0}", e.ToCSharpCode ()); + return new DynamicMetaObject (e, BindingRestrictions.GetInstanceRestriction (Expression, Value)); + } } } - class MetaStaticMemberAccessObject : DynamicMetaObject - { - delegate bool TryGetStaticMemberValue (string fieldName, Type fieldType, out object value); - delegate bool TryInvokeStaticMember (InvokeMemberBinder binder, DynamicMetaObject[] args, Type returnType, out object value); + static class JavaModifiers { + public static readonly int Static; - public MetaStaticMemberAccessObject (Expression parameter, DynamicJavaClass value) - : base (parameter, value) + static JavaModifiers () { -// Console.WriteLine ("# MyMetaObject..ctor: paramter={0} {1} {2}", parameter.ToCSharpCode (), parameter.GetType (), parameter.Type); - Debug.WriteLine ("# MyMetaObject..ctor: value={0} {1}", value, value.GetType ()); + using (var t = new JniType ("java/lang/reflect/Modifier")) { + using (var s = t.GetStaticField ("STATIC", "I")) + Static = s.GetInt32Value (t.SafeHandle); + } } + } + + sealed class JavaMethodInvokeInfo : IDisposable { + + public string Name; + public JniType ReturnType; + public List Arguments; + public bool IsStatic; + public JavaObject Method; + + public string Signature; - public override DynamicMetaObject BindConvert(ConvertBinder binder) + public JavaMethodInvokeInfo (string name, bool isStatic, JniType returnType, JavaObject method) { -// Console.WriteLine ("Convert: Expression={0} [{1}]", Expression.ToCSharpCode (), Expression.Type); - throw new NotSupportedException ("How is this being invoked?!"); + Name = name; + IsStatic = isStatic; + ReturnType = returnType; + Method = method; } - public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) + public void Dispose () { - Debug.WriteLine ("## MetaStaticMemberAccessObject.BindInvokeMember of method={0}; ReturnType={1}; args={{{2}}}; CallInfo={3}", binder.Name, binder.ReturnType, - string.Join (", ", args.Select (a => string.Format ("{0} [{1}]", a.Expression.ToCSharpCode (), a.LimitType))), binder.CallInfo); - - TryInvokeStaticMember m = Value.TryInvokeStaticMember; - var call = new DeferredConvert { - Arguments = new[]{Expression.Constant (binder), Expression.Constant (args)}, - Instance = Value, - FallbackCreator = v => binder.FallbackInvokeMember (v, args), - Method = m.Method, - }; - var callE = Expression.Constant (call); - return new DynamicMetaObject (callE, BindingRestrictions.GetInstanceRestriction (callE, call)); + if (ReturnType == null) + return; + + Method.Dispose (); + ReturnType.Dispose (); + ReturnType = null; + for (int i = 0; i < Arguments.Count; ++i) + Arguments [i].Dispose (); + Arguments = null; } - public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) + public void LookupArguments () { - // Debug.WriteLine ("SetMember: Expression={0} [{1}]; property={2}; value.LimitType={3}; value.RuntimeType={4}; value.Value={5}", Expression.ToCSharpCode (), Expression.Type, binder.Name, value.LimitType, value.RuntimeType, value.Value); - var self = Value; - var fieldValue = value.Expression; - fieldValue = Expression.Convert (fieldValue, typeof (object)); - - Func sfv = self.TrySetStaticMemberValue; - var call = Expression.Condition ( - test: Expression.Call (ExpressionAsT, sfv.Method, Expression.Constant (binder.Name), Expression.Constant (value.LimitType), fieldValue), - ifTrue: Expression, - ifFalse: Expression.Block (new[] { - binder.FallbackSetMember (this, value).Expression, - Expression - })); - return new DynamicMetaObject (call, BindingRestrictions.GetInstanceRestriction (Expression, Value)); + if (Arguments != null) + return; + + var vm = JniEnvironment.Current.JavaVM; + var sb = new StringBuilder (); + sb.Append (Name).Append ("\u0000").Append ("("); + + Arguments = new List (); + using (var methodParams = new JavaObjectArray (DynamicJavaClass.Method_getParameterTypes.CallVirtualObjectMethod (Method.SafeHandle), JniHandleOwnership.Transfer)) { + foreach (var p in methodParams) { + var pt = new JniType (p.SafeHandle, JniHandleOwnership.DoNotTransfer); + Arguments.Add (pt); + sb.Append (vm.GetJniTypeInfoForJniTypeReference (pt.Name).JniTypeReference); + p.Dispose (); + } + } + sb.Append (")").Append (vm.GetJniTypeInfoForJniTypeReference (ReturnType.Name).JniTypeReference); + Signature = sb.ToString (); } - public override DynamicMetaObject BindGetMember(GetMemberBinder binder) + public bool CompatibleWith (List args, DynamicMetaObject[] dargs) { -// Console.WriteLine ("GetMember: Expression={0} [{1}]; property={2}", Expression.ToCSharpCode (), Expression.Type, binder.Name); - TryGetStaticMemberValue m = Value.TryGetStaticMemberValue; - var access = new DeferredConvert { - Arguments = new[]{Expression.Constant (binder.Name)}, - Instance = Value, - FallbackCreator = binder.FallbackGetMember, - Method = m.Method, - }; - var accessE = Expression.Constant (access); - return new DynamicMetaObject (accessE, BindingRestrictions.GetInstanceRestriction (accessE, access)); - } - } + LookupArguments (); - class DeferredConvert : IDynamicMetaObjectProvider { + if (args.Count != Arguments.Count) + return false; - public T Instance; - public MethodInfo Method; - public Expression[] Arguments; - public Func FallbackCreator; + var vm = JniEnvironment.Current.JavaVM; - public DynamicMetaObject GetMetaObject (Expression parameter) - { - return new DeferredConvertMetaObject (parameter, this); + for (int i = 0; i < Arguments.Count; ++i) { + if (args [i] == null) { + // Builtin type -- JNIEnv.FindClass("I") throws! + if (Arguments [i].Name != vm.GetJniTypeInfoForType (dargs [i].LimitType).JniTypeReference) + return false; + } + else if (!Arguments [i].IsAssignableFrom (args [i])) + return false; + } + return true; } } - class DeferredConvertMetaObject : DynamicMetaObject> { + class DynamicMetaObject : DynamicMetaObject { - public DeferredConvertMetaObject (Expression e, DeferredConvert value) - : base (e, value) - { - Debug.WriteLine ("DeferredConvertMetaObject<{0}>: e={1}", typeof (T).Name, e.ToCSharpCode ()); + public new T Value { + get {return (T) base.Value;} } - public override DynamicMetaObject BindConvert (ConvertBinder binder) + public Expression ExpressionAsT { + get {return Expression.Convert (Expression, typeof (T));} + } + + public DynamicMetaObject (Expression parameter, T value) + : base (parameter, BindingRestrictions.GetInstanceRestriction (parameter, value), value) { - Debug.WriteLine ("DeferredConvertMetaObject<{0}>.BindConvert: Expression='{1}'; Expression.Type={2}", typeof (T).Name, Expression.ToCSharpCode (), Expression.Type); - - var instance = Expression.Constant (Value.Instance); - var instanceMO = new DynamicMetaObject (instance, BindingRestrictions.GetInstanceRestriction (instance, Value.Instance)); - var value = Expression.Parameter (typeof (object), "value"); - Debug.WriteLine ("DeferredConvertMetaObject<{0}>.BindConvert: Fallback={1}", typeof (T).Name, Value.FallbackCreator (instanceMO).Expression.ToCSharpCode ()); - var call = Expression.Block ( - new[]{value}, - Expression.Condition ( - test: Expression.Call (instance, Value.Method, Value.Arguments.Concat (new Expression[]{Expression.Constant (binder.Type), value})), - ifTrue: Expression.Convert (value, binder.Type), - ifFalse: Expression.Convert (Value.FallbackCreator (instanceMO).Expression, binder.Type)) - ); - Debug.WriteLine ("DeferredConvertMetaObject<{0}>.BindConvert: call={1}", typeof (T).Name, call.ToCSharpCode ()); - return new DynamicMetaObject ( - call, - BindingRestrictions.GetInstanceRestriction (Expression, Value)); } } diff --git a/src/Java.Interop.Dynamic/Tests/Java.Interop.Dynamic/DynamicJavaClassTests.cs b/src/Java.Interop.Dynamic/Tests/Java.Interop.Dynamic/DynamicJavaClassTests.cs index b866d0376..e13c4b7ea 100644 --- a/src/Java.Interop.Dynamic/Tests/Java.Interop.Dynamic/DynamicJavaClassTests.cs +++ b/src/Java.Interop.Dynamic/Tests/Java.Interop.Dynamic/DynamicJavaClassTests.cs @@ -44,8 +44,7 @@ public void Constructor () public void JniClassName () { dynamic Arrays = new DynamicJavaClass (Arrays_class); - string name = Arrays.JniClassName; - Assert.AreEqual (Arrays_class, name); + Assert.AreEqual (Arrays_class, Arrays.JniClassName); } [Test]