diff --git a/src/Tests/FunctionalTests/Collections/Program.cs b/src/Tests/FunctionalTests/Collections/Program.cs index 082f64168..a53fdb5df 100644 --- a/src/Tests/FunctionalTests/Collections/Program.cs +++ b/src/Tests/FunctionalTests/Collections/Program.cs @@ -150,6 +150,84 @@ } } +// Test 'Keys'/'Values' on a directly constructed runtime class that's projected via +// cswinrt's static_abi_methods path (uses the interop generator's '{Map}Methods.Keys/Values' +// statically resolved via 'UnsafeAccessor'). 'Windows.Foundation.Collections.StringMap' is a +// runtime class projected as 'IDictionary'; this exercises the trim/AOT path +// for both the cswinrt-generated 'Keys'/'Values' property and the interop-emitted static method. +var stringMapForKeysValues = new Windows.Foundation.Collections.StringMap +{ + ["one"] = "1", + ["two"] = "2", + ["three"] = "3" +}; + +System.Collections.Generic.ICollection stringMapKeys = stringMapForKeysValues.Keys; +System.Collections.Generic.ICollection stringMapValues = stringMapForKeysValues.Values; +if (stringMapKeys.Count != 3 || stringMapValues.Count != 3) +{ + return 101; +} +if (!stringMapKeys.Contains("one") || !stringMapKeys.Contains("two") || !stringMapKeys.Contains("three")) +{ + return 101; +} +if (!stringMapValues.Contains("1") || !stringMapValues.Contains("2") || !stringMapValues.Contains("3")) +{ + return 101; +} + +// Same scenario for 'PropertySet'/'ValueSet': different generic instantiation +// ('IDictionary') and so a different generated 'Methods' type. +var newPropertySet = new Windows.Foundation.Collections.PropertySet +{ + ["alpha"] = 1, + ["beta"] = "two" +}; + +System.Collections.Generic.ICollection newPropertySetKeys = newPropertySet.Keys; +System.Collections.Generic.ICollection newPropertySetValues = newPropertySet.Values; +if (newPropertySetKeys.Count != 2 || newPropertySetValues.Count != 2) +{ + return 101; +} +if (!newPropertySetKeys.Contains("alpha") || !newPropertySetKeys.Contains("beta")) +{ + return 101; +} + +var newValueSet = new Windows.Foundation.Collections.ValueSet +{ + ["x"] = 1.0, + ["y"] = 2.0 +}; + +if (newValueSet.Keys.Count != 2 || newValueSet.Values.Count != 2) +{ + return 101; +} + +// Test the parallel 'IReadOnlyDictionary' path through a runtime class that's projected +// as 'IReadOnlyDictionary'. This exercises the cswinrt-emitted projection +// (with the corrected 'IEnumerable' UnsafeAccessor return type) bound to the interop- +// emitted 'IReadOnlyDictionary'2Methods.Keys'/'Values' static methods, +// under trimming/AOT. +var customReadOnlyDictionary = new TestComponentCSharp.CustomReadOnlyDictionaryTest(); +System.Collections.Generic.IEnumerable customDictionaryKeys = customReadOnlyDictionary.Keys; +System.Collections.Generic.IEnumerable customDictionaryValues = customReadOnlyDictionary.Values; +if (customDictionaryKeys.Count() != 3 || customDictionaryValues.Count() != 3) +{ + return 101; +} +if (!customDictionaryKeys.Contains("apples") || !customDictionaryKeys.Contains("oranges") || !customDictionaryKeys.Contains("pears")) +{ + return 101; +} +if (!customDictionaryValues.Contains("1") || !customDictionaryValues.Contains("2") || !customDictionaryValues.Contains("3")) +{ + return 101; +} + var propertySet = Class.PropertySet; if (propertySet["beta"] is not string str || str != "second") { diff --git a/src/Tests/TestComponentCSharp/CustomReadOnlyDictionaryTest.cpp b/src/Tests/TestComponentCSharp/CustomReadOnlyDictionaryTest.cpp new file mode 100644 index 000000000..caf2cacce --- /dev/null +++ b/src/Tests/TestComponentCSharp/CustomReadOnlyDictionaryTest.cpp @@ -0,0 +1,49 @@ +#include "pch.h" +#include "CustomReadOnlyDictionaryTest.h" +#include "CustomReadOnlyDictionaryTest.g.cpp" + +namespace winrt::TestComponentCSharp::implementation +{ + CustomReadOnlyDictionaryTest::CustomReadOnlyDictionaryTest() + { + // Default contents: a small set of string -> string entries suitable for 'Keys'/'Values' tests. + std::map initial{ + { L"apples", L"1" }, + { L"oranges", L"2" }, + { L"pears", L"3" } + }; + _mapView = winrt::single_threaded_map_view(std::move(initial)); + } + + CustomReadOnlyDictionaryTest::CustomReadOnlyDictionaryTest(winrt::Windows::Foundation::Collections::IMapView const& mapView) + { + _mapView = mapView; + } + + winrt::hstring CustomReadOnlyDictionaryTest::Lookup(winrt::hstring const& key) + { + return _mapView.Lookup(key); + } + + uint32_t CustomReadOnlyDictionaryTest::Size() + { + return _mapView.Size(); + } + + bool CustomReadOnlyDictionaryTest::HasKey(winrt::hstring const& key) + { + return _mapView.HasKey(key); + } + + void CustomReadOnlyDictionaryTest::Split( + winrt::Windows::Foundation::Collections::IMapView& first, + winrt::Windows::Foundation::Collections::IMapView& second) + { + _mapView.Split(first, second); + } + + winrt::Windows::Foundation::Collections::IIterator> CustomReadOnlyDictionaryTest::First() + { + return _mapView.First(); + } +} diff --git a/src/Tests/TestComponentCSharp/CustomReadOnlyDictionaryTest.h b/src/Tests/TestComponentCSharp/CustomReadOnlyDictionaryTest.h new file mode 100644 index 000000000..d062da13a --- /dev/null +++ b/src/Tests/TestComponentCSharp/CustomReadOnlyDictionaryTest.h @@ -0,0 +1,28 @@ +#pragma once +#include "CustomReadOnlyDictionaryTest.g.h" + +namespace winrt::TestComponentCSharp::implementation +{ + struct CustomReadOnlyDictionaryTest : CustomReadOnlyDictionaryTestT + { + CustomReadOnlyDictionaryTest(); + CustomReadOnlyDictionaryTest(winrt::Windows::Foundation::Collections::IMapView const& mapView); + + winrt::hstring Lookup(winrt::hstring const& key); + uint32_t Size(); + bool HasKey(winrt::hstring const& key); + void Split( + winrt::Windows::Foundation::Collections::IMapView& first, + winrt::Windows::Foundation::Collections::IMapView& second); + winrt::Windows::Foundation::Collections::IIterator> First(); + + winrt::Windows::Foundation::Collections::IMapView _mapView; + }; +} + +namespace winrt::TestComponentCSharp::factory_implementation +{ + struct CustomReadOnlyDictionaryTest : CustomReadOnlyDictionaryTestT + { + }; +} diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.idl b/src/Tests/TestComponentCSharp/TestComponentCSharp.idl index 9645668d4..3c93d8434 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.idl +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.idl @@ -601,6 +601,13 @@ namespace TestComponentCSharp static CustomIterableTest CreateWithCustomIterator(); } + [default_interface] + runtimeclass CustomReadOnlyDictionaryTest : Windows.Foundation.Collections.IMapView + { + CustomReadOnlyDictionaryTest(); + CustomReadOnlyDictionaryTest(Windows.Foundation.Collections.IMapView mapView); + } + // SupportedOSPlatform warning tests [contract(Windows.Foundation.UniversalApiContract, 10)] [attributeusage(target_all)] diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj index 2013e28d1..180eb5b9d 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj @@ -89,6 +89,7 @@ + @@ -111,6 +112,7 @@ + Create diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters index a2df1ce81..489731f49 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters @@ -1,55 +1,57 @@ - - - - - accd3aa8-1ba0-4223-9bbe-0c431709210b - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + accd3aa8-1ba0-4223-9bbe-0c431709210b + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs index 2e4ee4737..1a6b1dda1 100644 --- a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs +++ b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs @@ -1662,6 +1662,314 @@ public void TestValueSetArrays() } } + [TestMethod] + public void TestStringMapKeysAndValues() + { + var stringMap = new Windows.Foundation.Collections.StringMap + { + ["foo"] = "bar", + ["hello"] = "world" + }; + + ICollection keys = stringMap.Keys; + ICollection values = stringMap.Values; + + Assert.IsNotNull(keys); + Assert.IsNotNull(values); + Assert.AreEqual(2, keys.Count); + Assert.AreEqual(2, values.Count); + + CollectionAssert.AreEquivalent(new[] { "foo", "hello" }, keys.ToArray()); + CollectionAssert.AreEquivalent(new[] { "bar", "world" }, values.ToArray()); + + Assert.IsTrue(keys.Contains("foo")); + Assert.IsTrue(keys.Contains("hello")); + Assert.IsFalse(keys.Contains("missing")); + + Assert.IsTrue(values.Contains("bar")); + Assert.IsTrue(values.Contains("world")); + Assert.IsFalse(values.Contains("missing")); + + // 'Keys'/'Values' on a key/value collection are read-only views + Assert.IsTrue(keys.IsReadOnly); + Assert.IsTrue(values.IsReadOnly); + Assert.ThrowsExactly(() => keys.Add("nope")); + Assert.ThrowsExactly(() => values.Add("nope")); + Assert.ThrowsExactly(() => keys.Clear()); + Assert.ThrowsExactly(() => values.Clear()); + Assert.ThrowsExactly(() => keys.Remove("foo")); + Assert.ThrowsExactly(() => values.Remove("bar")); + + // 'CopyTo' on the key/value collections should produce the same set of items + string[] keyBuffer = new string[2]; + string[] valueBuffer = new string[2]; + keys.CopyTo(keyBuffer, 0); + values.CopyTo(valueBuffer, 0); + CollectionAssert.AreEquivalent(new[] { "foo", "hello" }, keyBuffer); + CollectionAssert.AreEquivalent(new[] { "bar", "world" }, valueBuffer); + } + + [TestMethod] + public void TestStringMapKeysAndValuesReflectMutation() + { + var stringMap = new Windows.Foundation.Collections.StringMap + { + ["foo"] = "bar" + }; + + ICollection keys = stringMap.Keys; + ICollection values = stringMap.Values; + + Assert.AreEqual(1, keys.Count); + Assert.AreEqual(1, values.Count); + + // Add new items + stringMap["hello"] = "world"; + + // The 'Keys'/'Values' collections are views over the underlying dictionary, + // so they should immediately reflect the mutation without needing a new lookup. + Assert.AreEqual(2, keys.Count); + Assert.AreEqual(2, values.Count); + Assert.IsTrue(keys.Contains("hello")); + Assert.IsTrue(values.Contains("world")); + + // Remove an existing item + Assert.IsTrue(stringMap.Remove("foo")); + + Assert.AreEqual(1, keys.Count); + Assert.AreEqual(1, values.Count); + Assert.IsFalse(keys.Contains("foo")); + Assert.IsFalse(values.Contains("bar")); + } + + [TestMethod] + public void TestStringMapKeysAndValuesRepeatedAccess() + { + var stringMap = new Windows.Foundation.Collections.StringMap + { + ["foo"] = "bar" + }; + + // The 'Keys' and 'Values' properties cache the wrapper collection, + // so repeated access returns the same instance, matching the caching + // behavior of the dictionary native object in 'WinRT.Runtime'. + ICollection keys1 = stringMap.Keys; + ICollection keys2 = stringMap.Keys; + ICollection values1 = stringMap.Values; + ICollection values2 = stringMap.Values; + + Assert.AreEqual(1, keys1.Count); + Assert.AreEqual(1, values1.Count); + Assert.AreSame(keys1, keys2); + Assert.AreSame(values1, values2); + } + + [TestMethod] + public void TestPropertySetKeysAndValues() + { + var propertySet = new Windows.Foundation.Collections.PropertySet + { + ["foo"] = "bar", + ["hello"] = 42 + }; + + ICollection keys = propertySet.Keys; + ICollection values = propertySet.Values; + + Assert.IsNotNull(keys); + Assert.IsNotNull(values); + Assert.AreEqual(2, keys.Count); + Assert.AreEqual(2, values.Count); + + CollectionAssert.AreEquivalent(new[] { "foo", "hello" }, keys.ToArray()); + CollectionAssert.AreEquivalent(new object[] { "bar", 42 }, values.ToArray()); + + // Cross-check that 'Keys' values can be used to index back into the dictionary + foreach (string key in keys) + { + Assert.IsTrue(propertySet.ContainsKey(key)); + Assert.IsTrue(values.Contains(propertySet[key])); + } + } + + [TestMethod] + public void TestPropertySetKeysAndValuesViaIDictionaryInterface() + { + // Cast to 'IDictionary' to force calls through the explicit + // interface implementation rather than directly on the projected runtime class. + IDictionary propertySet = new Windows.Foundation.Collections.PropertySet + { + ["alpha"] = 1L, + ["beta"] = "two", + ["gamma"] = true + }; + + ICollection keys = propertySet.Keys; + ICollection values = propertySet.Values; + + Assert.AreEqual(3, keys.Count); + Assert.AreEqual(3, values.Count); + + CollectionAssert.AreEquivalent(new[] { "alpha", "beta", "gamma" }, keys.ToArray()); + CollectionAssert.AreEquivalent(new object[] { 1L, "two", true }, values.ToArray()); + } + + [TestMethod] + public void TestValueSetKeysAndValues() + { + var valueSet = new Windows.Foundation.Collections.ValueSet + { + ["foo"] = "bar", + ["hello"] = 42 + }; + + ICollection keys = valueSet.Keys; + ICollection values = valueSet.Values; + + Assert.IsNotNull(keys); + Assert.IsNotNull(values); + Assert.AreEqual(2, keys.Count); + Assert.AreEqual(2, values.Count); + + CollectionAssert.AreEquivalent(new[] { "foo", "hello" }, keys.ToArray()); + CollectionAssert.AreEquivalent(new object[] { "bar", 42 }, values.ToArray()); + } + + [TestMethod] + public void TestValueSetKeysAndValuesAfterClear() + { + var valueSet = new Windows.Foundation.Collections.ValueSet + { + ["foo"] = "bar", + ["hello"] = 42 + }; + + ICollection keys = valueSet.Keys; + ICollection values = valueSet.Values; + + Assert.AreEqual(2, keys.Count); + Assert.AreEqual(2, values.Count); + + valueSet.Clear(); + + Assert.AreEqual(0, valueSet.Count); + Assert.AreEqual(0, keys.Count); + Assert.AreEqual(0, values.Count); + + using (IEnumerator e = keys.GetEnumerator()) + { + Assert.IsFalse(e.MoveNext()); + } + + using (IEnumerator e = values.GetEnumerator()) + { + Assert.IsFalse(e.MoveNext()); + } + } + + [TestMethod] + public void TestStringMapKeysAndValuesEmpty() + { + var stringMap = new Windows.Foundation.Collections.StringMap(); + + ICollection keys = stringMap.Keys; + ICollection values = stringMap.Values; + + Assert.IsNotNull(keys); + Assert.IsNotNull(values); + Assert.AreEqual(0, keys.Count); + Assert.AreEqual(0, values.Count); + + // Enumeration should produce nothing + Assert.IsFalse(keys.Any()); + Assert.IsFalse(values.Any()); + + // 'CopyTo' to a zero-length array should be a no-op (not throw) + keys.CopyTo([], 0); + values.CopyTo([], 0); + } + + [TestMethod] + public void TestCustomReadOnlyDictionaryKeysAndValues() + { + // 'CustomReadOnlyDictionaryTest' is a Windows Runtime class projected as + // 'IReadOnlyDictionary'. This exercises the cswinrt-generated + // 'Keys'/'Values' projection that uses the 'UnsafeAccessor' stub to call into + // the interop-emitted 'ABI.System.Collections.Generic.<#corlib>IReadOnlyDictionary'2Methods.Keys' + // (and 'Values') static methods. Unlike 'IDictionary', the 'IReadOnlyDictionary' + // 'Keys'/'Values' return 'IEnumerable' (not 'ICollection'); this also validates + // that the cswinrt-emitted 'UnsafeAccessor' return type matches the interop-emitted method. + var dictionary = new TestComponentCSharp.CustomReadOnlyDictionaryTest(); + + IEnumerable keys = dictionary.Keys; + IEnumerable values = dictionary.Values; + + Assert.IsNotNull(keys); + Assert.IsNotNull(values); + + CollectionAssert.AreEquivalent(new[] { "apples", "oranges", "pears" }, keys.ToArray()); + CollectionAssert.AreEquivalent(new[] { "1", "2", "3" }, values.ToArray()); + + // Verify keys can be used to index back into the dictionary + foreach (string key in keys) + { + Assert.IsTrue(dictionary.ContainsKey(key)); + Assert.IsTrue(values.Contains(dictionary[key])); + } + } + + [TestMethod] + public void TestCustomReadOnlyDictionaryKeysAndValuesViaIReadOnlyDictionaryInterface() + { + // Cast to 'IReadOnlyDictionary' to force calls through the explicit + // interface implementation rather than directly on the projected runtime class. + IReadOnlyDictionary dictionary = new TestComponentCSharp.CustomReadOnlyDictionaryTest(); + + IEnumerable keys = dictionary.Keys; + IEnumerable values = dictionary.Values; + + Assert.AreEqual(3, keys.Count()); + Assert.AreEqual(3, values.Count()); + + CollectionAssert.AreEquivalent(new[] { "apples", "oranges", "pears" }, keys.ToArray()); + CollectionAssert.AreEquivalent(new[] { "1", "2", "3" }, values.ToArray()); + } + + [TestMethod] + public void TestCustomReadOnlyDictionaryKeysAndValuesEnumerationOnly() + { + // 'IReadOnlyDictionary.Keys' and '.Values' are 'IEnumerable' (not + // 'ICollection'), so they should not implicitly satisfy 'ICollection' callers. + // This pins down that contract — the cswinrt-emitted 'UnsafeAccessor' previously + // declared 'ICollection' as the return type by mistake, which would have caused + // a signature mismatch with the interop-emitted method and a 'MissingMethodException'. + var dictionary = new TestComponentCSharp.CustomReadOnlyDictionaryTest(); + + object keys = dictionary.Keys; + object values = dictionary.Values; + + Assert.IsNotNull(keys); + Assert.IsNotNull(values); + Assert.IsInstanceOfType>(keys); + Assert.IsInstanceOfType>(values); + Assert.IsNotInstanceOfType>(keys); + Assert.IsNotInstanceOfType>(values); + } + + [TestMethod] + public void TestCustomReadOnlyDictionaryKeysAndValuesEmpty() + { + // Construct with an empty backing map view so the projection is exercised + // with a zero-element dictionary. + var emptyDictionary = new Dictionary(); + IReadOnlyDictionary empty = emptyDictionary; + var dictionary = new TestComponentCSharp.CustomReadOnlyDictionaryTest(empty); + + Assert.AreEqual(0, dictionary.Count); + Assert.IsFalse(dictionary.Keys.Any()); + Assert.IsFalse(dictionary.Values.Any()); + } + [TestMethod] public void TestFactories() { @@ -4150,39 +4458,39 @@ private void TestSupportedOSPlatformWarnings() WarningStatic.WarningEvent += (object s, Int32 v) => { }; // warning CA1416 } - [TestMethod] - public void TestOverridable() - { - var obj = new OverridableTestClass(); - - // Test overridable property round-trip through native overrides interface - Assert.AreEqual(42, obj.CallOverridablePropertyGetter()); - obj.CallOverridablePropertySetter(99); - Assert.AreEqual(99, obj.CallOverridablePropertyGetter()); - - // Test overridable method round-trip through native overrides interface - Assert.IsFalse(obj.MethodWasCalled); - obj.CallOverridableMethod(); - Assert.IsTrue(obj.MethodWasCalled); - } - - class OverridableTestClass : WarningClass - { - private int _value = 42; - public bool MethodWasCalled { get; private set; } - - protected override int WarningOverridableProperty - { - get => _value; - set => _value = value; - } - - protected override void WarningOverridableMethod() - { - MethodWasCalled = true; - } - } - + [TestMethod] + public void TestOverridable() + { + var obj = new OverridableTestClass(); + + // Test overridable property round-trip through native overrides interface + Assert.AreEqual(42, obj.CallOverridablePropertyGetter()); + obj.CallOverridablePropertySetter(99); + Assert.AreEqual(99, obj.CallOverridablePropertyGetter()); + + // Test overridable method round-trip through native overrides interface + Assert.IsFalse(obj.MethodWasCalled); + obj.CallOverridableMethod(); + Assert.IsTrue(obj.MethodWasCalled); + } + + class OverridableTestClass : WarningClass + { + private int _value = 42; + public bool MethodWasCalled { get; private set; } + + protected override int WarningOverridableProperty + { + get => _value; + set => _value = value; + } + + protected override void WarningOverridableMethod() + { + MethodWasCalled = true; + } + } + [TestMethod] public void TestObjectFunctions() { diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IDictionary2.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IDictionary2.cs index 5f6b3c5f3..220ec2d23 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IDictionary2.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IDictionary2.cs @@ -414,6 +414,51 @@ public static void Methods( dictionaryMethodsType.Methods.Add(countMethod); + // Define the 'Keys' method as follows: + // + // public static ICollection<> Keys(WindowsRuntimeObject thisObject) + // + // The runtime instance passed in is the projected runtime class itself, which directly implements + // 'IDictionary'. This is strictly part of the contract, and we don't need to validate + // it at runtime (with a cast), in the same way as we don't do additional 'QueryInterface' calls on + // the various object references being passed around as arguments to interop methods. + MethodDefinition keysMethod = new( + name: "Keys"u8, + attributes: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: interopReferences.ICollection1.MakeGenericReferenceType([keyType]), + parameterTypes: [interopReferences.WindowsRuntimeObject.ToReferenceTypeSignature()])) + { + CilInstructions = + { + { Ldarg_0 }, + { Newobj, interopReferences.DictionaryKeyCollection2_ctor(keyType, valueType) }, + { Ret } + } + }; + + dictionaryMethodsType.Methods.Add(keysMethod); + + // Define the 'Values' method as follows: + // + // public static ICollection<> Values(WindowsRuntimeObject thisObject) + MethodDefinition valuesMethod = new( + name: "Values"u8, + attributes: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: interopReferences.ICollection1.MakeGenericReferenceType([valueType]), + parameterTypes: [interopReferences.WindowsRuntimeObject.ToReferenceTypeSignature()])) + { + CilInstructions = + { + { Ldarg_0 }, + { Newobj, interopReferences.DictionaryValueCollection2_ctor(keyType, valueType) }, + { Ret } + } + }; + + dictionaryMethodsType.Methods.Add(valuesMethod); + // Define the 'ContainsKey' method as follows: // // public static bool ContainsKey(WindowsRuntimeObjectReference thisReference, key) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyDictionary2.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyDictionary2.cs index 063619b50..031deed43 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyDictionary2.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyDictionary2.cs @@ -220,6 +220,48 @@ public static void Methods( } }; + // Define the 'Keys' method as follows: + // + // public static IEnumerable<> Keys(WindowsRuntimeObject thisObject) + // + // See additional notes in 'InteropTypeDefinitionBuilder.IDictionary2.Methods' (analogous logic). + MethodDefinition keysMethod = new( + name: "Keys"u8, + attributes: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: interopReferences.IEnumerable1.MakeGenericReferenceType([keyType]), + parameterTypes: [interopReferences.WindowsRuntimeObject.ToReferenceTypeSignature()])) + { + CilInstructions = + { + { Ldarg_0 }, + { Newobj, interopReferences.ReadOnlyDictionaryKeyCollection2_ctor(keyType, valueType) }, + { Ret } + } + }; + + readOnlyDictionaryMethodsType.Methods.Add(keysMethod); + + // Define the 'Values' method as follows: + // + // public static IEnumerable<> Values(WindowsRuntimeObject thisObject) + MethodDefinition valuesMethod = new( + name: "Values"u8, + attributes: MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: interopReferences.IEnumerable1.MakeGenericReferenceType([valueType]), + parameterTypes: [interopReferences.WindowsRuntimeObject.ToReferenceTypeSignature()])) + { + CilInstructions = + { + { Ldarg_0 }, + { Newobj, interopReferences.ReadOnlyDictionaryValueCollection2_ctor(keyType, valueType) }, + { Ret } + } + }; + + readOnlyDictionaryMethodsType.Methods.Add(valuesMethod); + // Define the 'ContainsKey' method as follows: // // public static bool ContainsKey(WindowsRuntimeObjectReference thisReference, key) diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index a780db203..29b4b13b1 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -3530,10 +3530,10 @@ visibility, element, self, interop_method_name_prefix, objref_name); w.write(R"( [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Keys")] -static extern ICollection<%> %Keys([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IReadOnlyDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObjectReference objRef); +static extern IEnumerable<%> %Keys([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IReadOnlyDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObject windowsRuntimeObject); [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Values")] -static extern ICollection<%> %Values([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IReadOnlyDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObjectReference objRef); +static extern IEnumerable<%> %Values([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IReadOnlyDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObject windowsRuntimeObject); [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Count")] static extern int %Count([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IReadOnlyDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObjectReference objRef); @@ -3556,15 +3556,15 @@ interop_method_name_prefix, key_interop_type_name, value_interop_type_name, key, ); w.write(R"( -%IEnumerable<%> %Keys => %Keys(null, %); -%IEnumerable<%> %Values => %Values(null, %); +%IEnumerable<%> %Keys => field ??= %Keys(null, this); +%IEnumerable<%> %Values => field ??= %Values(null, this); %int %Count => %Count(null, %); %% %this[% key] => %Item(null, %, key); %bool %ContainsKey(% key) => %ContainsKey(null, %, key); %bool %TryGetValue(% key, out % value) => %TryGetValue(null, %, key, out value); )", -visibility, key, self, interop_method_name_prefix, objref_name, -visibility, value, self, interop_method_name_prefix, objref_name, +visibility, key, self, interop_method_name_prefix, +visibility, value, self, interop_method_name_prefix, visibility, ireadonlycollection, interop_method_name_prefix, objref_name, visibility, value, self, key, interop_method_name_prefix, objref_name, visibility, self, key, interop_method_name_prefix, objref_name, @@ -3616,10 +3616,10 @@ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); w.write(R"( [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Keys")] -static extern ICollection<%> %Keys([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObjectReference objRef); +static extern ICollection<%> %Keys([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObject windowsRuntimeObject); [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Values")] -static extern ICollection<%> %Values([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObjectReference objRef); +static extern ICollection<%> %Values([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObject windowsRuntimeObject); [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Count")] static extern int %Count([UnsafeAccessorType("ABI.System.Collections.Generic.<#corlib>IDictionary'2<%|%>Methods, WinRT.Interop")] object _, WindowsRuntimeObjectReference objRef); @@ -3674,8 +3674,8 @@ interop_method_name_prefix, key_interop_type_name, value_interop_type_name, key, ); w.write(R"( -%ICollection<%> %Keys => %Keys(null, %); -%ICollection<%> %Values => %Values(null, %); +%ICollection<%> %Keys => field ??= %Keys(null, this); +%ICollection<%> %Values => field ??= %Values(null, this); %int %Count => %Count(null, %); %bool %IsReadOnly => false; %% %this[% key] @@ -3693,8 +3693,8 @@ set => %Item(null, %, key, value); %void %CopyTo(KeyValuePair<%, %>[] array, int arrayIndex) => %CopyTo(null, %, %, array, arrayIndex); bool ICollection>.Remove(KeyValuePair<%, %> item) => %Remove(null, %, item); )", -visibility, key, self, interop_method_name_prefix, objref_name, //Keys -visibility, value, self, interop_method_name_prefix, objref_name, // Values +visibility, key, self, interop_method_name_prefix, //Keys +visibility, value, self, interop_method_name_prefix, // Values visibility, icollection, interop_method_name_prefix, objref_name, // Count visibility, icollection, // IsReadOnly visibility, value, self, key, interop_method_name_prefix, objref_name, interop_method_name_prefix, objref_name, // Indexer