8000 Wrap returned objects in interface if method return type is interface · pythonnet/pythonnet@10158f4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 10158f4

Browse files
committed
Wrap returned objects in interface if method return type is interface
This allows callers to call all methods of an interface, regardless of whether the method was implemented implicitly or explicitly. Before this change, you had to make an explicit cast to the interface to be able to call the explicitly implemented method. Consider the following code: ```C# namespace Python.Test { public interface ITestInterface { void Foo(); void Bar(); } public class TestImpl : ITestInterface { public void Foo() { }; public void ITestInterface.Bar() { }; public void Baz() { }; public static ITestInterface GetInterface() { return new TestImpl(); } } } ``` And the following Python code, demonstrating the behavior before this change: ```python from Python.Test import TestImpl, ITestInterface test = TestImpl.GetInterface() test.Foo() # works test.Bar() # AttributeError: 'TestImpl' object has no attribute 'Bar' test.Baz() # works! - baz ``` After this change, the behavior is as follows: ``` test = TestImpl.GetInterface() test.Foo() # works test.Bar() # works test.Baz() # AttributeError: 'ITestInterface' object has no attribute 'Baz' ``` This is a breaking change due to that `Baz` is no longer visible in Python.
1 parent 451fae6 commit 10158f4

File tree

7 files changed

+92
-14
lines changed

7 files changed

+92
-14
lines changed

src/runtime/methodbinder.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
735735
var n = 0;
736736

737737
IntPtr t = Runtime.PyTuple_New(binding.outs + 1);
738-
IntPtr v = Converter.ToPython(result, mi.ReturnType);
738+
IntPtr v = MakeReturnValue(result, mi.ReturnType);
739739
Runtime.PyTuple_SetItem(t, n, v);
740740
n++;
741741

@@ -744,7 +744,7 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
744744
Type pt = pi[i].ParameterType;
745745
if (pi[i].IsOut || pt.IsByRef)
746746
{
747-
v = Converter.ToPython(binding.args[i], pt);
747+
v = MakeReturnValue(binding.args[i], pt.GetElementType());
748748
Runtime.PyTuple_SetItem(t, n, v);
749749
n++;
750750
}
@@ -761,7 +761,24 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
761761
return t;
762762
}
763763

764-
return Converter.ToPython(result, mi.ReturnType);
764+
return MakeReturnValue(result, mi.ReturnType);
765+
}
766+
767+
/// <summary>
768+
/// Takes care of wrapping an object in an interface object
769+
/// in case the method returns a value of a interface type.
770+
/// </summary>
771+
private IntPtr MakeReturnValue(object retVal, Type retType)
772+
{
773+
if (retVal != null && retType.IsInterface)
774+
{
775+
var ifaceObj = (InterfaceObject)ClassManager.GetClass(retType);
776+
return CLRObject.GetInstHandle(retVal, ifaceObj.pyHandle);
777+
}
778+
else
779+
{
780+
return Converter.ToPython(retVal, retType);
781+
}
765782
}
766783
}
767784

src/testing/interfacetest.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ internal interface IInternalInterface
1111
{
1212
}
1313

14-
1514
public interface ISayHello1
1615
{
1716
string SayHello();
@@ -43,6 +42,22 @@ string ISayHello2.SayHello()
4342
return "hello 2";
4443
}
4544

45+
public ISayHello1 GetISayHello1()
46+
{
47+
return this;
48+
}
49+
50+
public void GetISayHello2(out ISayHello2 hello2)
51+
{
52+
hello2 = this;
53+
}
54+
55+
public ISayHello1 GetNoSayHello(out ISayHello2 hello2)
56+
{
57+
hello2 = null;
58+
return null;
59+
}
60+
4661
public interface IPublic
4762
{
4863
}

src/testing/subclasstest.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,24 @@ public static string test_bar(IInterfaceTest x, string s, int i)
8989
}
9090

9191
// test instances can be constructed in managed code
92-
public static IInterfaceTest create_instance(Type t)
92+
public static SubClassTest create_instance(Type t)
93+
{
94+
return (SubClassTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { });
95+
}
96+
97+
public static IInterfaceTest create_instance_interface(Type t)
9398
{
9499
return (IInterfaceTest)t.GetConstructor(new Type[] { }).Invoke(new object[] { });
95100
}
96101

97-
// test instances pass through managed code unchanged
98-
public static IInterfaceTest pass_through(IInterfaceTest s)
102+
// test instances pass through managed code unchanged ...
103+
public static SubClassTest pass_through(SubClassTest s)
104+
{
105+
return s;
106+
}
107+
108+
// ... but the return type is an interface type, objects get wrapped
109+
public static IInterfaceTest pass_through_interface(IInterfaceTest s)
99110
{
100111
return s;
101112
}

src/tests/test_generic.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ def test_generic_method_type_handling():
319319
assert_generic_method_by_type(ShortEnum, ShortEnum.Zero)
320320
assert_generic_method_by_type(System.Object, InterfaceTest())
321321
assert_generic_method_by_type(InterfaceTest, InterfaceTest(), 1)
322-
assert_generic_method_by_type(ISayHello1, InterfaceTest(), 1)
323322

324323

325324
def test_correct_overload_selection():

src/tests/test_interface.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,35 @@ def test_explicit_cast_to_interface():
6767
assert i2.SayHello() == 'hello 2'
6868
assert hasattr(i2, 'SayHello')
6969
assert not hasattr(i2, 'HelloProperty')
70+
71+
72+
def test_interface_object_returned_through_method():
73+
"""Test interface type is used if method return type is interface"""
74+
from Python.Test import InterfaceTest
75+
76+
ob = InterfaceTest()
77+
hello1 = ob.GetISayHello1()
78+
assert type(hello1).__name__ == 'ISayHello1'
79+
80+
assert hello1.SayHello() == 'hello 1'
81+
82+
83+
def test_interface_object_returned_through_out_param():
84+
"""Test interface type is used for out parameters of interface types"""
85+
from Python.Test import InterfaceTest
86+
87+
ob = InterfaceTest()
88+
hello2 = ob.GetISayHello2(None)
89+
assert type(hello2).__name__ == 'ISayHello2'
90+
91+
assert hello2.SayHello() == 'hello 2'
92+
93+
94+
def test_null_interface_object_returned():
95+
"""Test None is used also for methods with interface return types"""
96+
from Python.Test import InterfaceTest
97+
98+
ob = InterfaceTest()
99+
hello1, hello2 = ob.GetNoSayHello(None)
100+
assert hello1 is None
101+
assert hello2 is None

src/tests/test_method.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,10 @@ def test_explicit_overload_selection():
564564
value = MethodTest.Overloaded.__overloads__[InterfaceTest](inst)
565565
assert value.__class__ == inst.__class__
566566

567+
iface_class = ISayHello1(InterfaceTest()).__class__
567568
value = MethodTest.Overloaded.__overloads__[ISayHello1](inst)
568-
assert value.__class__ == inst.__class__
569+
assert value.__class__ != inst.__class__
570+
assert value.__class__ == iface_class
569571

570572
atype = Array[System.Object]
571573
value = MethodTest.Overloaded.__overloads__[str, int, atype](

src/tests/test_subclass.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ def test_interface():
104104
assert ob.bar("bar", 2) == "bar/bar"
105105
assert FunctionsTest.test_bar(ob, "bar", 2) == "bar/bar"
106106

107-
x = FunctionsTest.pass_through(ob)
108-
assert id(x) == id(ob)
107+
# pass_through will convert from InterfaceTestClass -> IInterfaceTest,
108+
# causing a new wrapper object to be created. Hence id will differ.
109+
x = FunctionsTest.pass_through_interface(ob)
110+
assert id(x) != id(ob)
109111

110112

111113
def test_derived_class():
@@ -173,14 +175,14 @@ def test_create_instance():
173175
assert id(x) == id(ob)
174176

175177
InterfaceTestClass = interface_test_class_fixture(test_create_instance.__name__)
176-
ob2 = FunctionsTest.create_instance(InterfaceTestClass)
178+
ob2 = FunctionsTest.create_instance_interface(InterfaceTestClass)
177179
assert ob2.foo() == "InterfaceTestClass"
178180
assert FunctionsTest.test_foo(ob2) == "InterfaceTestClass"
179181
assert ob2.bar("bar", 2) == "bar/bar"
180182
assert FunctionsTest.test_bar(ob2, "bar", 2) == "bar/bar"
181183

182-
y = FunctionsTest.pass_through(ob2)
183-
assert id(y) == id(ob2)
184+
y = FunctionsTest.pass_through_interface(ob2)
185+
assert id(y) != id(ob2)
184186

185187

186188
def test_events():

0 commit comments

Comments
 (0)
0