Nullable: System.Runtime.InteropServices.CustomMarshalers/WindowsRuntime (#23930)
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / src / System / Runtime / InteropServices / WindowsRuntime / IteratorToEnumeratorAdapter.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 #nullable enable
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using Internal.Runtime.CompilerServices;
10
11 namespace System.Runtime.InteropServices.WindowsRuntime
12 {
13     internal delegate IEnumerator<T> GetEnumerator_Delegate<out T>();
14
15     // This is a set of stub methods implementing the support for the IEnumerable`1 interface on WinRT
16     // objects that implement IIterable`1. Used by the interop mashaling infrastructure.
17     //
18     // The methods on this class must be written VERY carefully to avoid introducing security holes.
19     // That's because they are invoked with special "this"! The "this" object
20     // for all of these methods are not IterableToEnumerableAdapter objects. Rather, they are of type
21     // IIterable<T>. No actual IterableToEnumerableAdapter object is ever instantiated. Thus, you will
22     // see a lot of expressions that cast "this" to "IIterable<T>". 
23     internal sealed class IterableToEnumerableAdapter
24     {
25         private IterableToEnumerableAdapter()
26         {
27             Debug.Fail("This class is never instantiated");
28         }
29
30         // This method is invoked when GetEnumerator is called on a WinRT-backed implementation of IEnumerable<T>.
31         internal IEnumerator<T> GetEnumerator_Stub<T>()
32         {
33             IIterable<T> _this = Unsafe.As<IIterable<T>>(this);
34             return new IteratorToEnumeratorAdapter<T>(_this.First());
35         }
36
37         // This method is invoked when GetEnumerator is called on a WinRT-backed implementation of IEnumerable<T>
38         // and it is possible that the implementation supports IEnumerable<Type>/IEnumerable<string>/IEnumerable<Exception>/
39         // IEnumerable<array>/IEnumerable<delegate> rather than IEnumerable<T> because T is assignable from Type/string/
40         // Exception/array/delegate via co-variance.
41         internal IEnumerator<T> GetEnumerator_Variance_Stub<T>() where T : class
42         {
43             bool fUseString;
44             Delegate target = System.StubHelpers.StubHelpers.GetTargetForAmbiguousVariantCall(
45                 this,
46                 typeof(IEnumerable<T>).TypeHandle.Value,
47                 out fUseString);
48
49             if (target != null)
50             {
51                 return (Unsafe.As<GetEnumerator_Delegate<T>>(target))();
52             }
53
54             if (fUseString)
55             {
56                 return Unsafe.As<IEnumerator<T>>(GetEnumerator_Stub<string>());
57             }
58
59             return GetEnumerator_Stub<T>();
60         }
61     }
62
63     internal sealed class BindableIterableToEnumerableAdapter
64     {
65         private BindableIterableToEnumerableAdapter()
66         {
67             Debug.Fail("This class is never instantiated");
68         }
69
70         private sealed class NonGenericToGenericIterator : IIterator<object?>
71         {
72             private IBindableIterator iterator;
73
74             public NonGenericToGenericIterator(IBindableIterator iterator)
75             { this.iterator = iterator; }
76
77             public object? Current { get { return iterator.Current; } }
78             public bool HasCurrent { get { return iterator.HasCurrent; } }
79             public bool MoveNext() { return iterator.MoveNext(); }
80             public int GetMany(object?[] items) { throw new NotSupportedException(); }
81         }
82
83         // This method is invoked when GetEnumerator is called on a WinRT-backed implementation of IEnumerable.
84         internal IEnumerator GetEnumerator_Stub()
85         {
86             IBindableIterable _this = Unsafe.As<IBindableIterable>(this);
87             return new IteratorToEnumeratorAdapter<object?>(new NonGenericToGenericIterator(_this.First()));
88         }
89     }
90
91     // Adapter class which holds a Windows Runtime IIterator<T>, exposing it as a managed IEnumerator<T>
92
93
94     // There are a few implementation differences between the Iterator and IEnumerator which need to be 
95     // addressed. Iterator starts at index 0 while IEnumerator starts at index -1 as a result of which 
96     // the first call to IEnumerator.Current is correct only after calling MoveNext(). 
97     // Also IEnumerator throws an exception when we call Current after reaching the end of collection.
98     internal sealed class IteratorToEnumeratorAdapter<T> : IEnumerator<T>
99     {
100         private IIterator<T> m_iterator;
101         private bool m_hadCurrent;
102         private T m_current = default!; // TODO-NULLABLE-GENERIC
103         private bool m_isInitialized;
104
105         internal IteratorToEnumeratorAdapter(IIterator<T> iterator)
106         {
107             Debug.Assert(iterator != null);
108             m_iterator = iterator;
109             m_hadCurrent = true;
110             m_isInitialized = false;
111         }
112
113         public T Current
114         {
115             get
116             {
117                 // The enumerator has not been advanced to the first element yet.
118                 if (!m_isInitialized)
119                     ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumNotStarted();
120                 // The enumerator has reached the end of the collection
121                 if (!m_hadCurrent)
122                     ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumEnded();
123                 return m_current;
124             }
125         }
126
127         object? IEnumerator.Current
128         {
129             get
130             {
131                 // The enumerator has not been advanced to the first element yet.
132                 if (!m_isInitialized)
133                     ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumNotStarted();
134                 // The enumerator has reached the end of the collection
135                 if (!m_hadCurrent)
136                     ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumEnded();
137                 return m_current;
138             }
139         }
140
141         public bool MoveNext()
142         {
143             // If we've passed the end of the iteration, IEnumerable<T> should return false, while
144             // IIterable will fail the interface call
145             if (!m_hadCurrent)
146             {
147                 return false;
148             }
149
150             // IIterators start at index 0, rather than -1.  If this is the first call, we need to just
151             // check HasCurrent rather than actually moving to the next element
152             try
153             {
154                 if (!m_isInitialized)
155                 {
156                     m_hadCurrent = m_iterator.HasCurrent;
157                     m_isInitialized = true;
158                 }
159                 else
160                 {
161                     m_hadCurrent = m_iterator.MoveNext();
162                 }
163
164                 // We want to save away the current value for two reasons:
165                 //  1. Accessing .Current is cheap on other iterators, so having it be a property which is a
166                 //     simple field access preserves the expected performance characteristics (as opposed to
167                 //     triggering a COM call every time the property is accessed)
168                 //
169                 //  2. This allows us to preserve the same semantics as generic collection iteration when iterating
170                 //     beyond the end of the collection - namely that Current continues to return the last value
171                 //     of the collection
172                 if (m_hadCurrent)
173                 {
174                     m_current = m_iterator.Current;
175                 }
176             }
177             catch (Exception e)
178             {
179                 // Translate E_CHANGED_STATE into an InvalidOperationException for an updated enumeration
180                 if (Marshal.GetHRForException(e) == HResults.E_CHANGED_STATE)
181                 {
182                     ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
183                 }
184                 else
185                 {
186                     throw;
187                 }
188             }
189
190             return m_hadCurrent;
191         }
192
193         public void Reset()
194         {
195             throw new NotSupportedException();
196         }
197
198         public void Dispose()
199         {
200         }
201     }
202 }