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); ///