简体   繁体   中英

C# - Return generic type as IEnumerable

I'm trying to resolve a generic type T with a dictionary of types to instances. When T is IEnumerable<> , I get a LINQ Select query with all instances from that dictionary. However, when I try to return that query, I cannot cast it back to T . I get the following exception:

Additional information: Unable to cast object of type 'WhereSelectListIterator`2[System.Func`2[DiContainer.IServicesContainer,System.Object],System.Object]' to type 'System.Collections.Generic.IEnumerable`1[Tester.IService]'.

code:

  public T Resolve<T>()
        {
            Type typeToResolve = typeof(T);

            if (m_TypeToConcrete.ContainsKey(typeToResolve))
            {
                return (T)m_TypeToConcrete[typeToResolve].GetSingle();
            }

            if (DetermineIfExactlyIEnumerable(typeToResolve))
            {
                Type underlyingType = typeToResolve.GetGenericArguments().First();
                if (m_TypeToConcrete.ContainsKey(underlyingType))
                {
                    // Throws invalid cast exception
                    return (T)m_TypeToConcrete[underlyingType].GetEnumerable();
                }
            }
        }


 public class FactoryMethodsForType
    {
        private List<Func<IServicesContainer, object>> m_FactoryMethods;
        private IServicesContainer m_Container;

        public FactoryMethodsForType(IServicesContainer container)
        {
            m_Container = container;
            m_FactoryMethods = new List<Func<IServicesContainer, object>>();
        }

        public void AddFactoryMethod(Func<IServicesContainer, object> method)
        {
            m_FactoryMethods.Add(method);
        }

        public object GetSingle()
        {
            return m_FactoryMethods.Last().Invoke(m_Container);
        }

        public IEnumerable<object> GetEnumerable()
        {
            // Lazy
            return m_FactoryMethods.Select(m => m.Invoke(m_Container));
        }
    }

I used this trick for a similar problem.

public static class GenericCast
    {
        //This is the only way to create a real thread safe dictionary might not be required in you case you can make convertors during init of just not care for multi thread. The normal dictionary will work fine it might only call the compile a few times.
        public static ConcurrentDictionary<Type, Lazy<Func<IEnumerable<object>, object>>> creators = new ConcurrentDictionary<Type, Lazy<Func<IEnumerable<object>, object>>>();

        public static T CastAs<T>(this IEnumerable<object> data)
        {
            var dataType = typeof(T).GenericTypeArguments.First();

            var creator = creators.GetOrAdd(dataType, new Lazy<Func<IEnumerable<object>, object>>(() => {
                var source = Expression.Parameter(
                    typeof(IEnumerable<object>));

                var call = Expression.Call(
                    typeof(Enumerable), "Cast", new Type[] { dataType }, source);

                var cast = Expression.Convert(call, typeof(object));

                var exp = Expression.Lambda<Func<IEnumerable<object>, object>>(cast, source);

                return exp.Compile();
            }));

            return (T)creator.Value(data);
        }
    }

This method will have a cost in the first call but every other time you will have little perf impact. You can use it in you code like this

m_TypeToConcrete[underlyingType].GetEnumerable().CastAs<T>();

Inspired by Filip's solution, I used a solution using reflection, although with poor performance at the moment, to cast the IEnumerable<object> to IEnumerable<myType> and cast it to T :

  Type underlyingType = typeToResolve.GetGenericArguments().First();
                if (m_TypeToConcrete.ContainsKey(underlyingType))
                {
                    MethodInfo castMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.Cast));
                    IEnumerable<object> instances = m_TypeToConcrete[underlyingType].GetEnumerable();

                    return (T)castMethod.MakeGenericMethod(underlyingType).Invoke(instances, new object[] { instances });
                }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM