Skip to content

Commit 34fa08a

Browse files
committed
Add __len__ and __contains__ to IDictionary that defines ContainsKey
1 parent 60e9e86 commit 34fa08a

File tree

4 files changed

+167
-0
lines changed

4 files changed

+167
-0
lines changed

src/embed_tests/ClassManagerTests.cs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Reflection;
@@ -1083,6 +1084,132 @@ def is_enum_value_defined():
10831084
Assert.Throws<PythonException>(() => module.InvokeMethod("is_enum_value_defined"));
10841085
}
10851086
}
1087+
1088+
private static TestCaseData[] IDictionaryContainsTestCases => new TestCaseData[]
1089+
{
1090+
new(typeof(TestDictionary<string, string>)),
1091+
new(typeof(Dictionary<string, string>)),
1092+
};
1093+
1094+
[TestCaseSource(nameof(IDictionaryContainsTestCases))]
1095+
public void IDictionaryContainsMethodIsBound(Type dictType)
1096+
{
1097+
using var _ = Py.GIL();
1098+
1099+
var module = PyModule.FromString("IDictionaryContainsMethodIsBound", $@"
1100+
from clr import AddReference
1101+
AddReference(""Python.EmbeddingTest"")
1102+
1103+
from Python.EmbeddingTest import *
1104+
1105+
def contains(dictionary, key):
1106+
return key in dictionary
1107+
");
1108+
1109+
using var contains = module.GetAttr("contains");
1110+
1111+
var dictionary = (Activator.CreateInstance(dictType) as IDictionary)!;
1112+
var key1 = "key1";
1113+
dictionary.Add(key1, "value1");
1114+
1115+
using var pyDictionary = dictionary.ToPython();
1116+
using var pyKey1 = key1.ToPython();
1117+
1118+
var result = contains.Invoke(pyDictionary, pyKey1).As<bool>();
1119+
Assert.IsTrue(result);
1120+
1121+
using var pyKey2 = "key2".ToPython();
1122+
result = contains.Invoke(pyDictionary, pyKey2).As<bool>();
1123+
Assert.IsFalse(result);
1124+
}
1125+
1126+
[TestCaseSource(nameof(IDictionaryContainsTestCases))]
1127+
public void CanCheckIfNoneIsInDictionary(Type dictType)
1128+
{
1129+
using var _ = Py.GIL();
1130+
1131+
var module = PyModule.FromString("CanCheckIfNoneIsInDictionary", $@"
1132+
from clr import AddReference
1133+
AddReference(""Python.EmbeddingTest"")
1134+
1135+
from Python.EmbeddingTest import *
1136+
1137+
def contains(dictionary, key):
1138+
return key in dictionary
1139+
");
1140+
1141+
using var contains = module.GetAttr("contains");
1142+
1143+
var dictionary = (Activator.CreateInstance(dictType) as IDictionary)!;
1144+
dictionary.Add("key1", "value1");
1145+
1146+
using var pyDictionary = dictionary.ToPython();
1147+
1148+
var result = false;
1149+
Assert.DoesNotThrow(() => result = contains.Invoke(pyDictionary, PyObject.None).As<bool>());
1150+
Assert.IsFalse(result);
1151+
}
1152+
1153+
public class TestDictionary<TValue, TKey> : IDictionary
1154+
{
1155+
private readonly Dictionary<TValue, TKey> _data = new();
1156+
1157+
public object this[object key] { get => ((IDictionary)_data)[key]; set => ((IDictionary)_data)[key] = value; }
1158+
1159+
public bool IsFixedSize => ((IDictionary)_data).IsFixedSize;
1160+
1161+
public bool IsReadOnly => ((IDictionary)_data).IsReadOnly;
1162+
1163+
public ICollection Keys => ((IDictionary)_data).Keys;
1164+
1165+
public ICollection Values => ((IDictionary)_data).Values;
1166+
1167+
public int Count => ((ICollection)_data).Count;
1168+
1169+
public bool IsSynchronized => ((ICollection)_data).IsSynchronized;
1170+
1171+
public object SyncRoot => ((ICollection)_data).SyncRoot;
1172+
1173+
public void Add(object key, object value)
1174+
{
1175+
((IDictionary)_data).Add(key, value);
1176+
}
1177+
1178+
public void Clear()
1179+
{
1180+
((IDictionary)_data).Clear();
1181+
}
1182+
1183+
public bool Contains(object key)
1184+
{
1185+
return ((IDictionary)_data).Contains(key);
1186+
}
1187+
1188+
public void CopyTo(Array array, int index)
1189+
{
1190+
((ICollection)_data).CopyTo(array, index);
1191+
}
1192+
1193+
public IDictionaryEnumerator GetEnumerator()
1194+
{
1195+
return ((IDictionary)_data).GetEnumerator();
1196+
}
1197+
1198+
public void Remove(object key)
1199+
{
1200+
((IDictionary)_data).Remove(key);
1201+
}
1202+
1203+
IEnumerator IEnumerable.GetEnumerator()
1204+
{
1205+
return ((IEnumerable)_data).GetEnumerator();
1206+
}
1207+
1208+
public bool ContainsKey(TKey key)
1209+
{
1210+
return Contains(key);
1211+
}
1212+
}
10861213
}
10871214

10881215
public class NestedTestParent

src/runtime/ClassManager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ internal static ClassBase CreateClass(Type type)
180180
impl = new ArrayObject(type);
181181
}
182182

183+
else if (type.IsDictionary())
184+
{
185+
impl = new DictionaryObject(type);
186+
}
187+
183188
else if (type.IsKeyValuePairEnumerable())
184189
{
185190
impl = new KeyValuePairEnumerableObject(type);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Collections;
3+
using System.Linq;
4+
5+
namespace Python.Runtime
6+
{
7+
/// <summary>
8+
/// Implements a Python type for managed IDictionary (dictionaries).
9+
/// This type is essentially the same as a ClassObject, except that it provides
10+
/// sequence semantics to support natural dictionary usage (__contains__ and __len__)
11+
/// from Python.
12+
/// </summary>
13+
internal class DictionaryObject : KeyValuePairEnumerableObject
14+
{
15+
internal DictionaryObject(Type tp) : base(tp)
16+
{
17+
}
18+
}
19+
20+
public static class DictionaryObjectExtension
21+
{
22+
public static bool IsDictionary(this Type type)
23+
{
24+
var iDictionaryType = typeof(IDictionary);
25+
return type.GetInterfaces().Contains(iDictionaryType) && DictionaryObject.VerifyMethodRequirements(type);
26+
}
27+
}
28+
}

src/runtime/Types/KeyValuePairEnumerableObject.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ public static int sq_contains(BorrowedReference ob, BorrowedReference v)
7777
$"invalid parameter type for sq_contains: should be {Converter.GetTypeByAlias(v)}, found {parameters[0].ParameterType}");
7878
}
7979

80+
// If the argument is None, we return false. Python allows using None as key,
81+
// but C# doesn't and will throw, so we shortcut here
82+
if (arg == null)
83+
{
84+
return 0;
85+
}
86+
8087
return (bool)methodInfo.Invoke(self, new[] { arg }) ? 1 : 0;
8188
}
8289
}

0 commit comments

Comments
 (0)