Hi All,
I am getting a .NET error whilst simply trying to get access to the Controls property of a TableLayoutPanel in System.Windows.Forms.
I have included a little test application below which demonstrates the issue.
I am using Lispworks 5.0 and RDNZL 0.12.0. I am running Visual Studio 2008 so I think I've got version 3.5 of .NET.
I've tried to have a look at it but don't understand enough about the ffi to come up with a possible cause. I did look at the System.Windows.Form assembly through the Visual Studio object viewer and I can't see any other definition of Controls. So I really can't see where the ambiguity would come from.
Regards,
Matthew
;; Bug ;; .NET error (System.Reflection.AmbiguousMatchException): Ambiguous match found.
;; NOTE - Make this point to your RDNZL implementation. (load "../RDNZL/rdnzl-0.12.0/load.lisp")
(rdnzl:enable-rdnzl-syntax)
(rdnzl:import-types "System.Windows.Forms" "TableLayoutPanel") (rdnzl:use-namespace "System.Windows.Forms")
(setf tlp (rdnzl:new "TableLayoutPanel")) (rdnzl:property tlp "Controls")
I can't see any other definition of Controls. So I really can't see where the ambiguity would come from.
There is something weird going on here, if you do this:
(pprint (mapcar (lambda (x) (invoke x "ToString")) (rdnzl-array-to-list (invoke (invoke tlp "GetType") "GetProperties"))))
the first entries are
"System.Windows.Forms.TableLayoutControlCollection Controls" "ControlCollection Controls"
so there is actually an ambiguity here, where the first property is the one documented. I don't understand what the second one is. Of course, these two properties are only differentiated by their return-value which I didn't think was actually possible. And look at this:
RDNZL-USER(202): (invoke tlp "get_Controls") #<RDNZL::CONTAINER System.Windows.Forms.TableLayoutControlCollection #x5f2ca208>
That's the expected result, and there are actually two get_Controls-method as well, so that shouldn't really have worked either.
I think that's supposed to be wrong.
.. or possibly not. This error can be reproduced with a pair of classes like this: public class TestPropertiesA { public int thisthing = 1; public int MyProperty { get { return thisthing; } set { thisthing = value; } } }
public class TestPropertiesB : TestPropertiesA { public String thisstring = "hello"; public new String MyProperty { get { return thisstring; } set { thisstring = value; } } }
Notice the "new" keyword in there. When instantiated, a TestPropertiesB-object will have both properties, and GetProperty will indeed report an ambiguos match even though the first property was supposed to be 'hidden'. Pretty silly. GetMethod does the correct thing wrt the accessor method, which is why 'get_PropertyName' works.
For this case, Type.GetProperty has an overloaded form that takes the expected return argument so probably this could be accounted for in that way.
Browsing through MSDN a bit more, I find this in Type.GetProperty:
===> Situations in which AmbiguousMatchException occurs include the following:
* A type contains two indexed properties that have the same name but different numbers of parameters. To resolve the ambiguity, use an overload of the GetProperty method that specifies parameter types. * A derived type declares a property that hides an inherited property with the same name, by using the new modifier (Shadows in Visual Basic). To resolve the ambiguity, use the GetProperty(String, BindingFlags) method overload and include BindingFlags..::.DeclaredOnly to restrict the search to members that are not inherited. ===>
The last situation is what we have ('new' being used because the return-value is more specific), and it appears specifying the return type is then unneccessary. This works:
(INVOKE (INVOKE TLP "GetType") "GetProperty" "Controls" (OR-ENUMS (FIELD "BindingFlags" "Instance") (FIELD "BindingFlags" "Public") (FIELD "BindingFlags" "DeclaredOnly")))
perhaps adding this flag in an exception-handler for this case would be ok, as the first kind of ambiguity should not be possible as all parameter types are passed. Otherwise, one would need to either add the return-type somehow or write a specialized Binder-subclass.
It's kind of strange that this seems to only affect properties; for instance both get_Controls methods were present, but the default GetMethod-method had no problem with that.
perhaps adding this flag in an exception-handler for this case would be ok, as the first kind of ambiguity should not be possible as all parameter types are passed. Otherwise, one would need to either add the return-type somehow or write a specialized Binder-subclass.
Come to think of it, both those properties could be inherited, so the advice in GetProperty doesn't actually help.
Your direct invocation of the "get_Controls" method gets me going again.
I'm glad that works, but reflecting on this it seems that one pretty much *have* to offer the return-type to PROPERTY/GetProperty when there are ambiguities of this sort, which there will be. That's a bit iffy syntactically, even though the implementation is easy enough. And of course the error-message would still be pretty confusing.
C#'s type inference mechanism tells me that in the example, if foo has type TestPropertiesB, then bar in
var bar = foo.MyProperty();
has type String, this being an error:
var bar = (int) foo.MyProperty();
whereas in
var bar = ((TestPropertiesA) foo).MyProperty();
bar is an int.
I guess one could write a custom Binder that would implement a 'most specific property' thing, but that does kind of feel like overkill.
Regards, Iver