diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs
index 593d5640a..618452e37 100644
--- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs
+++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs
@@ -58,22 +58,23 @@ public static void TryTrackGenericTypeInstance(
if (typeSignature.IsWindowsRuntimeType(interopReferences))
{
TryTrackWindowsRuntimeGenericTypeInstance(
- typeDefinition,
- typeSignature,
- args,
- discoveryState,
- interopReferences,
- module);
+ typeDefinition: typeDefinition,
+ typeSignature: typeSignature,
+ args: args,
+ discoveryState: discoveryState,
+ interopReferences: interopReferences,
+ module: module);
}
else
{
// Otherwise, try to track information for some constructed managed type
TryTrackManagedGenericTypeInstance(
- typeDefinition,
- typeSignature,
- args,
- discoveryState,
- interopReferences);
+ typeDefinition: typeDefinition,
+ typeSignature: typeSignature,
+ args: args,
+ discoveryState: discoveryState,
+ interopReferences: interopReferences,
+ module: module);
}
}
@@ -165,7 +166,12 @@ private static void TryTrackWindowsRuntimeGenericTypeInstance(
// Track all projected Windows Runtime generic interfaces
if (typeDefinition.IsInterface)
{
- discoveryState.TrackGenericInterfaceType(typeSignature, interopReferences);
+ TryTrackWindowsRuntimeGenericInterfaceTypeInstance(
+ typeSignature: typeSignature,
+ args: args,
+ discoveryState,
+ interopReferences: interopReferences,
+ module: module);
// We also want to crawl base interfaces
foreach (TypeSignature interfaceSignature in typeSignature.EnumerateAllInterfaces())
@@ -191,11 +197,155 @@ private static void TryTrackWindowsRuntimeGenericTypeInstance(
continue;
}
- discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences);
+ TryTrackWindowsRuntimeGenericInterfaceTypeInstance(
+ typeSignature: constructedSignature,
+ args: args,
+ discoveryState,
+ interopReferences: interopReferences,
+ module: module);
}
}
}
+ ///
+ /// Tries to track a constructed generic Windows Runtime interface type.
+ ///
+ /// The for the constructed type to analyze.
+ /// The arguments for this invocation.
+ /// The discovery state for this invocation.
+ /// The instance to use.
+ /// The module currently being analyzed.
+ private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance(
+ GenericInstanceTypeSignature typeSignature,
+ InteropGeneratorArgs args,
+ InteropGeneratorDiscoveryState discoveryState,
+ InteropReferences interopReferences,
+ ModuleDefinition module)
+ {
+ if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerator1))
+ {
+ discoveryState.TrackIEnumerator1Type(typeSignature);
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerable1))
+ {
+ discoveryState.TrackIEnumerable1Type(typeSignature);
+
+ // We need special handling for 'IEnumerator' types whenever we discover any constructed 'IEnumerable'
+ // type. This ensures that we're never missing any 'IEnumerator' instantiation, which we might depend on
+ // from other generated code, or projections. This special handling is needed because unlike with the other
+ // interfaces, 'IEnumerator' will not show up as a base interface for other collection interface types.
+ discoveryState.TrackIEnumerator1Type(interopReferences.IEnumerator1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IList1))
+ {
+ discoveryState.TrackIList1Type(typeSignature);
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyList1))
+ {
+ discoveryState.TrackIReadOnlyList1Type(typeSignature);
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IDictionary2))
+ {
+ discoveryState.TrackIDictionary2Type(typeSignature);
+
+ // When discovering dictionary types, make sure to also track 'KeyValuePair' types. Those will
+ // be needed when generating code for 'IEnumerator>' types, which will be discovered
+ // automatically. However, the same is not true the constructed 'KeyValuePair' types themselves.
+ // This is for the same reason why we need the other special cases in this method: members are not analyzed.
+ discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments]));
+
+ // When we discover a constructed 'IDictionary' instantiation, we'll be generating a native object type during
+ // the emit phase, which is used to marshal anonymous objects. This derives from 'WindowsRuntimeDictionary'.
+ // For the 'Keys' and 'Values' properties, that base type will return instances of the 'DictionaryKeyCollection'
+ // and 'DictionaryValueCollection' types, respectively. Those instantiations will not be seen by 'cswinrtgen',
+ // because they will only exist in the final 'WinRT.Interop.dll' assembly being generated, and not in any input assemblies.
+ // So to ensure that we can still correctly marshal those to native, if needed, we manually track them as if we had seen them.
+ TryTrackGenericTypeInstance(
+ typeSignature: interopReferences.DictionaryKeyCollection2.MakeGenericReferenceType([.. typeSignature.TypeArguments]),
+ args: args,
+ discoveryState: discoveryState,
+ interopReferences: interopReferences,
+ module: module);
+
+ // Handle 'DictionaryValueCollection' as well
+ TryTrackGenericTypeInstance(
+ typeSignature: interopReferences.DictionaryValueCollection2.MakeGenericReferenceType([.. typeSignature.TypeArguments]),
+ args: args,
+ discoveryState: discoveryState,
+ interopReferences: interopReferences,
+ module: module);
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyDictionary2))
+ {
+ discoveryState.TrackIReadOnlyDictionary2Type(typeSignature);
+
+ // Same handling as above for constructed 'KeyValuePair' types
+ discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments]));
+
+ // Handle 'ReadOnlyDictionaryKeyCollection' as above
+ TryTrackGenericTypeInstance(
+ typeSignature: interopReferences.ReadOnlyDictionaryKeyCollection2.MakeGenericReferenceType([.. typeSignature.TypeArguments]),
+ args: args,
+ discoveryState: discoveryState,
+ interopReferences: interopReferences,
+ module: module);
+
+ // Handle 'ReadOnlyDictionaryValueCollection' as well
+ TryTrackGenericTypeInstance(
+ typeSignature: interopReferences.ReadOnlyDictionaryValueCollection2.MakeGenericReferenceType([.. typeSignature.TypeArguments]),
+ args: args,
+ discoveryState: discoveryState,
+ interopReferences: interopReferences,
+ module: module);
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableVector1))
+ {
+ discoveryState.TrackIObservableVector1Type(typeSignature);
+
+ // We need special handling for constructed 'VectorChangedEventHandler' types, as those are required for each
+ // discovered 'IObservableVector' type. These are not necessarily discovered in the same way, as while we are
+ // recursively constructing interfaces, we don't have the same logic for delegate types (or for types used in
+ // any signature of interface members). Because we only need this delegate type and the one below, we can just
+ // special case it. That is, we manually construct it every time we discover a constructed 'IObservableVector'.
+ discoveryState.TrackGenericDelegateType(interopReferences.VectorChangedEventHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableMap2))
+ {
+ discoveryState.TrackIObservableMap2Type(typeSignature);
+
+ // Same handling as above for 'MapChangedEventHandler' types
+ discoveryState.TrackGenericDelegateType(interopReferences.MapChangedEventHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IMapChangedEventArgs1))
+ {
+ discoveryState.TrackIMapChangedEventArgs1Type(typeSignature);
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncActionWithProgress1))
+ {
+ discoveryState.TrackIAsyncActionWithProgress1Type(typeSignature);
+
+ // Ensure that the delegate types for this instantiation of 'IAsyncActionWithProgress' are also tracked.
+ // Same rationale as above for the other special cased types. Same below as well for the other async info types.
+ discoveryState.TrackGenericDelegateType(interopReferences.AsyncActionProgressHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
+ discoveryState.TrackGenericDelegateType(interopReferences.AsyncActionWithProgressCompletedHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncOperation1))
+ {
+ discoveryState.TrackIAsyncOperation1Type(typeSignature);
+
+ // Same handling as above for 'AsyncOperationCompletedHandler'
+ discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationCompletedHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
+ }
+ else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncOperationWithProgress2))
+ {
+ discoveryState.TrackIAsyncOperationWithProgress2Type(typeSignature);
+
+ // Same handling as above for 'AsyncOperationProgressHandler' and 'AsyncOperationWithProgressCompletedHandler'
+ discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationProgressHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
+ discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationWithProgressCompletedHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
+ }
+ }
+
///
/// Tries to track a constructed generic user-defined type.
///
@@ -204,12 +354,14 @@ private static void TryTrackWindowsRuntimeGenericTypeInstance(
/// The arguments for this invocation.
/// The discovery state for this invocation.
/// The instance to use.
+ /// The module currently being analyzed.
private static void TryTrackManagedGenericTypeInstance(
TypeDefinition typeDefinition,
GenericInstanceTypeSignature typeSignature,
InteropGeneratorArgs args,
InteropGeneratorDiscoveryState discoveryState,
- InteropReferences interopReferences)
+ InteropReferences interopReferences,
+ ModuleDefinition module)
{
// Check for all '[ReadOnly]Span' types in particular, and track them as SZ array types.
// This is because "pass-array" and "fill-array" parameters are projected using spans, but
@@ -227,10 +379,11 @@ private static void TryTrackManagedGenericTypeInstance(
// Otherwise, try to track a constructed user-defined type
TryTrackExposedUserDefinedType(
- typeDefinition,
- typeSignature,
- args,
- discoveryState,
- interopReferences);
+ typeDefinition: typeDefinition,
+ typeSignature: typeSignature,
+ args: args,
+ discoveryState: discoveryState,
+ interopReferences: interopReferences,
+ module: module);
}
}
\ No newline at end of file
diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs
index 3a6f8b9a9..94ab9b7bb 100644
--- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs
+++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Concurrent;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
using WindowsRuntime.InteropGenerator.Errors;
@@ -18,10 +19,9 @@ namespace WindowsRuntime.InteropGenerator.Discovery;
internal static partial class InteropTypeDiscovery
{
///
- /// A thread-local instance that can be reused by discovery logic.
+ /// A pool of instances that can be reused by discovery logic.
///
- [ThreadStatic]
- private static TypeSignatureEquatableSet.Builder? TypeSignatures;
+ private static readonly ConcurrentBag TypeSignatureBuilderPool = [];
///
/// Tries to track a given composable Windows Runtime type.
@@ -69,6 +69,7 @@ public static void TryTrackTypeHierarchyType(
/// The arguments for this invocation.
/// The discovery state for this invocation.
/// The instance to use.
+ /// The module currently being analyzed.
///
/// This method expects to either be non-generic, or
/// to have be a fully constructed signature for it.
@@ -78,7 +79,8 @@ public static void TryTrackExposedUserDefinedType(
TypeSignature typeSignature,
InteropGeneratorArgs args,
InteropGeneratorDiscoveryState discoveryState,
- InteropReferences interopReferences)
+ InteropReferences interopReferences,
+ ModuleDefinition module)
{
// Ignore types that should explicitly be excluded
if (TypeExclusions.IsExcluded(typeDefinition, interopReferences))
@@ -114,8 +116,17 @@ public static void TryTrackExposedUserDefinedType(
return;
}
- // Reuse the thread-local builder to track all implemented interfaces for the current type
- TypeSignatureEquatableSet.Builder interfaces = TypeSignatures ??= new TypeSignatureEquatableSet.Builder();
+ // Reuse a builder to track all implemented interfaces for the current type, or create a new one.
+ // Note, we can't just have a single thread-local builder that we reuse here, as this method might
+ // be called recursively in some scenarios. For instance, consider a type that implements a generic
+ // interface such as 'IList' (some constructed instantiation of it). As part of the interface
+ // discovery logic, we'll also be tracking the additional 'ReadOnlyCollection' type, as that's
+ // needed from the 'IListAdapter.GetView' method. That type will itself be analyzed here just
+ // like any other user-define type, and so on. So the pool is needed to avoid creating conflicts.
+ if (!TypeSignatureBuilderPool.TryTake(out TypeSignatureEquatableSet.Builder? interfaces))
+ {
+ interfaces = new TypeSignatureEquatableSet.Builder();
+ }
// Since we're reusing the builder for all types, make sure to clear it first
interfaces.Clear();
@@ -158,7 +169,12 @@ public static void TryTrackExposedUserDefinedType(
// So the discovery logic for generic instantiations below would otherwise miss it.
if (interfaceSignature is GenericInstanceTypeSignature constructedSignature)
{
- discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences);
+ TryTrackWindowsRuntimeGenericInterfaceTypeInstance(
+ typeSignature: constructedSignature,
+ args: args,
+ discoveryState,
+ interopReferences: interopReferences,
+ module: module);
}
}
else if (interfaceDefinition.IsGeneratedComInterfaceType)
@@ -202,5 +218,8 @@ public static void TryTrackExposedUserDefinedType(
{
discoveryState.TrackUserDefinedType(typeSignature, interfaces.ToEquatableSet());
}
+
+ // Return the builder to the pool for reuse
+ TypeSignatureBuilderPool.Add(interfaces);
}
}
\ No newline at end of file
diff --git a/src/WinRT.Interop.Generator/Extensions/InteropGeneratorDiscoveryStateExtensions.cs b/src/WinRT.Interop.Generator/Extensions/InteropGeneratorDiscoveryStateExtensions.cs
deleted file mode 100644
index eea7e77ed..000000000
--- a/src/WinRT.Interop.Generator/Extensions/InteropGeneratorDiscoveryStateExtensions.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using AsmResolver.DotNet.Signatures;
-using WindowsRuntime.InteropGenerator.Generation;
-using WindowsRuntime.InteropGenerator.References;
-
-namespace WindowsRuntime.InteropGenerator;
-
-///
-/// Extensions for .
-///
-internal static class InteropGeneratorDiscoveryStateExtensions
-{
- ///
- /// Tracks a generic interface type of any projected or custom-mapped type.
- ///
- /// The current instance.
- /// The generic interface type.
- /// The instance to use.
- public static void TrackGenericInterfaceType(
- this InteropGeneratorDiscoveryState discoveryState,
- GenericInstanceTypeSignature typeSignature,
- InteropReferences interopReferences)
- {
- if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerator1))
- {
- discoveryState.TrackIEnumerator1Type(typeSignature);
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerable1))
- {
- discoveryState.TrackIEnumerable1Type(typeSignature);
-
- // We need special handling for 'IEnumerator' types whenever we discover any constructed 'IEnumerable'
- // type. This ensures that we're never missing any 'IEnumerator' instantiation, which we might depend on
- // from other generated code, or projections. This special handling is needed because unlike with the other
- // interfaces, 'IEnumerator' will not show up as a base interface for other collection interface types.
- discoveryState.TrackIEnumerator1Type(interopReferences.IEnumerator1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IList1))
- {
- discoveryState.TrackIList1Type(typeSignature);
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyList1))
- {
- discoveryState.TrackIReadOnlyList1Type(typeSignature);
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IDictionary2))
- {
- discoveryState.TrackIDictionary2Type(typeSignature);
-
- // When discovering dictionary types, make sure to also track 'KeyValuePair' types. Those will
- // be needed when generating code for 'IEnumerator>' types, which will be discovered
- // automatically. However, the same is not true the constructed 'KeyValuePair' types themselves.
- // This is for the same reason why we need the other special cases in this method: members are not analyzed.
- discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments]));
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyDictionary2))
- {
- discoveryState.TrackIReadOnlyDictionary2Type(typeSignature);
-
- // Same handling as above for constructed 'KeyValuePair' types
- discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments]));
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableVector1))
- {
- discoveryState.TrackIObservableVector1Type(typeSignature);
-
- // We need special handling for constructed 'VectorChangedEventHandler' types, as those are required for each
- // discovered 'IObservableVector' type. These are not necessarily discovered in the same way, as while we are
- // recursively constructing interfaces, we don't have the same logic for delegate types (or for types used in
- // any signature of interface members). Because we only need this delegate type and the one below, we can just
- // special case it. That is, we manually construct it every time we discover a constructed 'IObservableVector'.
- discoveryState.TrackGenericDelegateType(interopReferences.VectorChangedEventHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableMap2))
- {
- discoveryState.TrackIObservableMap2Type(typeSignature);
-
- // Same handling as above for 'MapChangedEventHandler' types
- discoveryState.TrackGenericDelegateType(interopReferences.MapChangedEventHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IMapChangedEventArgs1))
- {
- discoveryState.TrackIMapChangedEventArgs1Type(typeSignature);
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncActionWithProgress1))
- {
- discoveryState.TrackIAsyncActionWithProgress1Type(typeSignature);
-
- // Ensure that the delegate types for this instantiation of 'IAsyncActionWithProgress' are also tracked.
- // Same rationale as above for the other special cased types. Same below as well for the other async info types.
- discoveryState.TrackGenericDelegateType(interopReferences.AsyncActionProgressHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
- discoveryState.TrackGenericDelegateType(interopReferences.AsyncActionWithProgressCompletedHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncOperation1))
- {
- discoveryState.TrackIAsyncOperation1Type(typeSignature);
-
- discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationCompletedHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
- }
- else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncOperationWithProgress2))
- {
- discoveryState.TrackIAsyncOperationWithProgress2Type(typeSignature);
-
- discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationProgressHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
- discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationWithProgressCompletedHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments]));
- }
- }
-}
\ No newline at end of file
diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs
index 688968d69..b8bb3f0e8 100644
--- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs
+++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs
@@ -192,7 +192,8 @@ private static void DiscoverExposedUserDefinedTypes(
typeSignature: type.ToTypeSignature(),
args: args,
discoveryState: discoveryState,
- interopReferences: interopReferences);
+ interopReferences: interopReferences,
+ module: module);
}
}
catch (Exception e)
diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs
index 1f6a55a3b..93cd2f752 100644
--- a/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs
+++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs
@@ -90,15 +90,23 @@ WindowsRuntimeObjectReference InitializeIIterableObjectReference()
protected internal sealed override bool HasUnwrappableNativeObjectReference => true;
///
+ ///
+ /// The resulting object will be of type .
+ ///
public ICollection Keys => _keys ??= new DictionaryKeyCollection(this);
///
+ ///
IEnumerable IReadOnlyDictionary.Keys => Keys;
///
+ ///
+ /// The resulting object will be of type .
+ ///
public ICollection Values => _values ??= new DictionaryValueCollection(this);
///
+ ///
IEnumerable IReadOnlyDictionary.Values => Values;
///
diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs
index efa2a4a95..8842509ea 100644
--- a/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs
+++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs
@@ -124,15 +124,23 @@ public event MapChangedEventHandler? MapChanged
protected internal sealed override bool HasUnwrappableNativeObjectReference => true;
///
+ ///
+ /// The resulting object will be of type .
+ ///
public ICollection Keys => _keys ??= new DictionaryKeyCollection(this);
///
+ ///
IEnumerable IReadOnlyDictionary.Keys => Keys;
///
+ ///
+ /// The resulting object will be of type .
+ ///
public ICollection Values => _values ??= new DictionaryValueCollection(this);
///
+ ///
IEnumerable IReadOnlyDictionary.Values => Values;
///
diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs
index df74e42c4..aab1e0e28 100644
--- a/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs
+++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs
@@ -89,9 +89,15 @@ WindowsRuntimeObjectReference InitializeIIterableObjectReference()
protected internal sealed override bool HasUnwrappableNativeObjectReference => true;
///
+ ///
+ /// The resulting object will be of type .
+ ///
public IEnumerable Keys => _keys ??= new ReadOnlyDictionaryKeyCollection(this);
///
+ ///
+ /// The resulting object will be of type .
+ ///
public IEnumerable Values => _values ??= new ReadOnlyDictionaryValueCollection(this);
///