extensions/Worker.Extensions.Shared/Reflection/ParameterBinder.cs (84 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; namespace Microsoft.Azure.Functions.Worker.Extensions { /// <summary> /// Helpers for performing parameter binding. /// </summary> internal static class ParameterBinder { /// <summary> /// Read only property that contains all of the generic interfaces implemented by <see cref="List{T}"/>. /// </summary> /// <remarks>This property calls ToList at the end to force the resolution of the LINQ methods that leverage deferred execution.</remarks> private static readonly HashSet<Type> validListInterfaceTypes = new(typeof(List<>).GetInterfaces().Where(t => t.IsGenericType).Select(t => t.GetGenericTypeDefinition())); private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; /// <summary> /// Binds to a collection type. /// </summary> /// <param name="factory">Factory for producing an enumerable given the element type.</param> /// <param name="collectionType">The collection type to bind to.</param> /// <returns>The instantiated and populated collection.</returns> public static async Task<object> BindCollectionAsync(Func<Type, IAsyncEnumerable<object>> factory, Type collectionType) { if (factory is null) { throw new ArgumentNullException(nameof(factory)); } if (collectionType is null) { throw new ArgumentNullException(nameof(collectionType)); } if (!collectionType.TryGetCollectionElementType(out Type? elementType)) { throw new ArgumentException($"Type '{collectionType}' is not a collection type.", nameof(collectionType)); } object? collection = null; if (collectionType.IsConcreteType() && !collectionType.IsArray) { collection = Activator.CreateInstance(collectionType)!; } else if (IsListInterface(collectionType)) { collection = Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType!))!; } else if (collectionType.IsArray) { collection = Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType!))!; await BindCollectionAsync(factory(elementType!), collection); IList list = (IList)collection; Array arrayResult = Array.CreateInstance(elementType!, list.Count); list.CopyTo(arrayResult, 0); return arrayResult; } else { throw new ArgumentException($"Collection type '{collectionType}' is not supported for parameter binding.", nameof(collectionType)); } await BindCollectionAsync(factory(elementType!), collection); return collection; } /// <summary> /// Binds to a collection. /// </summary> /// <param name="pageable">The pageable containing the items to populate the collection with.</param> /// <param name="collection">The collection to populate.</param> /// <returns>A task that completes when the collection has been populated.</returns> public static async Task BindCollectionAsync(IAsyncEnumerable<object> pageable, object collection) { if (pageable is null) { throw new ArgumentNullException(nameof(pageable)); } if (collection is null) { throw new ArgumentNullException(nameof(collection)); } Action<object> add = GetAddMethod(collection); await foreach (object item in pageable) { add(item); } } /// <summary> /// A method that determines if a Type is a generic interface of the <see cref="List{T}"/> concrete class. /// </summary> /// <param name="type">A generic interface type to be tested against the types available on the generic <see cref="List{T}"/> type.</param> /// <returns>True if the type is a generic type and it's an interface of the generic <see cref="List{T}"/> type otherwise false.</returns> private static bool IsListInterface(Type type) { return type.IsGenericType && validListInterfaceTypes.Contains(type.GetGenericTypeDefinition()); } private static Action<object> GetAddMethod(object collection) { if (collection is IList list) { return e => list.Add(e); } MethodInfo method = collection.GetType().GetMethod("Add", DeclaredOnlyLookup) ?? throw new InvalidOperationException($"Could not find an 'Add' method on collection type '{collection.GetType()}'."); return e => method.Invoke(collection, new[] { e }); } } }