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.
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using Internal.Runtime.CompilerServices;
11 namespace System.Runtime.InteropServices.WindowsRuntime
13 internal delegate IEnumerator<T> GetEnumerator_Delegate<out T>();
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.
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
25 private IterableToEnumerableAdapter()
27 Debug.Fail("This class is never instantiated");
30 // This method is invoked when GetEnumerator is called on a WinRT-backed implementation of IEnumerable<T>.
31 internal IEnumerator<T> GetEnumerator_Stub<T>()
33 IIterable<T> _this = Unsafe.As<IIterable<T>>(this);
34 return new IteratorToEnumeratorAdapter<T>(_this.First());
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
44 Delegate target = System.StubHelpers.StubHelpers.GetTargetForAmbiguousVariantCall(
46 typeof(IEnumerable<T>).TypeHandle.Value,
51 return (Unsafe.As<GetEnumerator_Delegate<T>>(target))();
56 return Unsafe.As<IEnumerator<T>>(GetEnumerator_Stub<string>());
59 return GetEnumerator_Stub<T>();
63 internal sealed class BindableIterableToEnumerableAdapter
65 private BindableIterableToEnumerableAdapter()
67 Debug.Fail("This class is never instantiated");
70 private sealed class NonGenericToGenericIterator : IIterator<object?>
72 private IBindableIterator iterator;
74 public NonGenericToGenericIterator(IBindableIterator iterator)
75 { this.iterator = iterator; }
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(); }
83 // This method is invoked when GetEnumerator is called on a WinRT-backed implementation of IEnumerable.
84 internal IEnumerator GetEnumerator_Stub()
86 IBindableIterable _this = Unsafe.As<IBindableIterable>(this);
87 return new IteratorToEnumeratorAdapter<object?>(new NonGenericToGenericIterator(_this.First()));
91 // Adapter class which holds a Windows Runtime IIterator<T>, exposing it as a managed IEnumerator<T>
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>
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;
105 internal IteratorToEnumeratorAdapter(IIterator<T> iterator)
107 Debug.Assert(iterator != null);
108 m_iterator = iterator;
110 m_isInitialized = false;
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
122 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumEnded();
127 object? IEnumerator.Current
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
136 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumEnded();
141 public bool MoveNext()
143 // If we've passed the end of the iteration, IEnumerable<T> should return false, while
144 // IIterable will fail the interface call
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
154 if (!m_isInitialized)
156 m_hadCurrent = m_iterator.HasCurrent;
157 m_isInitialized = true;
161 m_hadCurrent = m_iterator.MoveNext();
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)
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
174 m_current = m_iterator.Current;
179 // Translate E_CHANGED_STATE into an InvalidOperationException for an updated enumeration
180 if (Marshal.GetHRForException(e) == HResults.E_CHANGED_STATE)
182 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
195 throw new NotSupportedException();
198 public void Dispose()