How to use a generic type at runtime in C #

I need to create an IEnumerable<IEnumerable<T>> when I know only T at runtime.

I created my collection like this:

 new List<List<object>>() 

where all the objects in the internal list are T

However, due to co / contravariance (I never remember what it is!) My List of List is not IEnumerable of IEnumerable s.

What can i do with this?

I tried using Convert.ChangeType but it groans that List not IConvertible

Hint: Read the question. Again. I said that I only know T at runtime.

+6
source share
7 answers

Well, based on the answer of the Master of Morals, I came up with this. Awfully simple.

 public static IEnumerable Cast(this IEnumerable self, Type innerType) { var methodInfo = typeof (Enumerable).GetMethod("Cast"); var genericMethod = methodInfo.MakeGenericMethod(innerType); return genericMethod.Invoke(null, new [] {self}) as IEnumerable; } 

Simple This is reported here: Listing enumerated when an internal type is known only at run time

+11
source
  • Use it as net as IEnumerable<IEnumerable>
  • Use reflection to call a function that takes IEnumerable<IEnumerable<T>> with the corresponding T
  • use the switch statement to cast to the appropriate type
  • use dynamic


<b> Examples
 static IEnumerable<IEnumerable<T>> castList<T>(List<List<object>> list) { return list.Select(x => x.Cast<T>()); } void DoSomething(Type myT, List<List<object>> list) { object untyped = typeof(MyClass).GetMethod("castList") .MakeGenericMethod(myT) .Invoke(null, new[] { list }); // untyped is an IEnumerable<IEnumerable<myT>> at runtime, // but obviously you don't know that at compile time. // what can you do with untyped? // 1: use it like an untyped container var option1 = (IEnumerable<IEnumerable>)untyped; foreach(var inner in option1) foreach(object item in inner) Console.WriteLine(object); // 2: pass it to a function that you reflect on using // the above makeGenericMethod strategy typeof(MyClass).GetMethod("Process") .MakeGenericMethod(myT) .Invoke(null, new[] { untyped }); // 3: Cast it conditionally switch(Type.GetTypeCode(myT)) { case TypeCode.Int32: Process((IEnumerable<IEnumerable<int>>)untyped); break; case TypeCode.Single: Process((IEnumerable<IEnumerable<float>>)untyped); break; } // 4: make it a dynamic dynamic dyn = untyped; Process(dyn); } static void Process<T>(IEnumerable<IEnumerable<T>> ienumerable) { Console.WriteLine("Processing type: {0}", typeof(T).Name); foreach(var inner in ienumerable) foreach(T item in inner) DoSomething(item); // item is now type T } 
+3
source

I had similar problems with TinyIoC, and instead of “converting”, the “cleanest” solution I found was to make your method general (so public IEnumerable'T DoStuff'T ()) and then call this using MakeGenericMethod using your runtime type. It stays “clean” because your actual method that creates the list only works as if it were the usual universal method, so it doesn't clutter castings, etc.

Without seeing your code, it's hard to see if this matches the bill - here are the relevant bits to make a general method from TinyIoc:

 public static class TypeExtensions { private static SafeDictionary<GenericMethodCacheKey, MethodInfo> _genericMethodCache; static TypeExtensions() { _genericMethodCache = new SafeDictionary<GenericMethodCacheKey, MethodInfo>(); } /// <summary> /// Gets a generic method from a type given the method name, binding flags, generic types and parameter types /// </summary> /// <param name="sourceType">Source type</param> /// <param name="bindingFlags">Binding flags</param> /// <param name="methodName">Name of the method</param> /// <param name="genericTypes">Generic types to use to make the method generic</param> /// <param name="parameterTypes">Method parameters</param> /// <returns>MethodInfo or null if no matches found</returns> /// <exception cref="System.Reflection.AmbiguousMatchException"/> /// <exception cref="System.ArgumentException"/> public static MethodInfo GetGenericMethod(this Type sourceType, System.Reflection.BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes) { MethodInfo method; var cacheKey = new GenericMethodCacheKey(sourceType, methodName, genericTypes, parameterTypes); // Shouldn't need any additional locking // we don't care if we do the method info generation // more than once before it gets cached. if (!_genericMethodCache.TryGetValue(cacheKey, out method)) { method = GetMethod(sourceType, bindingFlags, methodName, genericTypes, parameterTypes); _genericMethodCache[cacheKey] = method; } return method; } private static MethodInfo GetMethod(Type sourceType, BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes) { var methods = sourceType.GetMethods(bindingFlags).Where( mi => string.Equals(methodName, mi.Name, StringComparison.InvariantCulture)).Where( mi => mi.ContainsGenericParameters).Where(mi => mi.GetGenericArguments().Length == genericTypes.Length). Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select( mi => mi.MakeGenericMethod(genericTypes)).Where( mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList(); if (methods.Count > 1) { throw new AmbiguousMatchException(); } return methods.FirstOrDefault(); } private sealed class GenericMethodCacheKey { private readonly Type _sourceType; private readonly string _methodName; private readonly Type[] _genericTypes; private readonly Type[] _parameterTypes; private readonly int _hashCode; public GenericMethodCacheKey(Type sourceType, string methodName, Type[] genericTypes, Type[] parameterTypes) { _sourceType = sourceType; _methodName = methodName; _genericTypes = genericTypes; _parameterTypes = parameterTypes; _hashCode = GenerateHashCode(); } public override bool Equals(object obj) { var cacheKey = obj as GenericMethodCacheKey; if (cacheKey == null) return false; if (_sourceType != cacheKey._sourceType) return false; if (!String.Equals(_methodName, cacheKey._methodName, StringComparison.InvariantCulture)) return false; if (_genericTypes.Length != cacheKey._genericTypes.Length) return false; if (_parameterTypes.Length != cacheKey._parameterTypes.Length) return false; for (int i = 0; i < _genericTypes.Length; ++i) { if (_genericTypes[i] != cacheKey._genericTypes[i]) return false; } for (int i = 0; i < _parameterTypes.Length; ++i) { if (_parameterTypes[i] != cacheKey._parameterTypes[i]) return false; } return true; } public override int GetHashCode() { return _hashCode; } private int GenerateHashCode() { unchecked { var result = _sourceType.GetHashCode(); result = (result * 397) ^ _methodName.GetHashCode(); for (int i = 0; i < _genericTypes.Length; ++i) { result = (result * 397) ^ _genericTypes[i].GetHashCode(); } for (int i = 0; i < _parameterTypes.Length; ++i) { result = (result * 397) ^ _parameterTypes[i].GetHashCode(); } return result; } } } } 

This is called the following:

 private object GetIEnumerableRequest(Type type) { var genericResolveAllMethod = this.GetType().GetGenericMethod(BindingFlags.Public | BindingFlags.Instance, "ResolveAll", type.GetGenericArguments(), new[] { typeof(bool) }); return genericResolveAllMethod.Invoke(this, new object[] { false }); } 

And ResolveAll is defined as:

 public IEnumerable<ResolveType> ResolveAll<ResolveType>() where ResolveType : class { return ResolveAll<ResolveType>(true); } 

Hope this makes sense :)

+3
source

Edit: if you know T only at run time, you can do this by building an expression. and compile it. So:

 var listOfLists = new List<List<object>>(); //... do list building... //types var runTimeType = typeof(MyRuntimeType); var innerListType = typeof(List<>) .MakeGenericType(typeof(object)); var innerEnumerableType = typeof(IEnumerable<>) .MakeGenericType(runTimeType); var outerListType = typeof(List<>) .MakeGenericType(innerListType); //methods var castm = typeof(Enumerable).GetMethod("Cast") .MakeGenericMethod(runTimeType); var selectm = typeof(Enumerable).GetMethods() .Where(x => x.Name == "Select").First() .MakeGenericMethod(innerListType, innerEnumerableType); //expressions (parameters) var innerParamx = Expression.Parameter(innerListType); var outerParamx = Expression.Parameter(outerListType); // listOfLists.Select(x => x.Cast<T>()); // as an expression var castx = Expression.Call(castm, innerParamx); var lambdax = Expression.Lambda(castx, innerParamx); var selectx = Expression.Call(selectm, outerParamx, lambdax); var lambdax2 = Expression.Lambda(selectx, outerParamx); var result = lambdax2.Compile().DynamicInvoke(listOfLists); 

you could cache lambdax2.Compile() somewhere for each type of execution, performance.

+2
source

I believe the answer is “you can’t”, although I could probably be wrong with some kind of super-hacker code that uses a ton of reflection or emits IL directly or something like that.

The compiler and JIT'er must know the types of objects for everyone to configure the stack, allocate memory correctly, etc.

Perhaps each type T can implement some kind of marker interface or get a common base? In practice, various types of behavior can be implemented. If you can comment a little on what your program is trying to do, maybe people can come up with a good design.

0
source

Based on your comment,

not really, but thanks, I could create an internal list on the right of the type, but then I can insert objects into it, and I still have a problem with deviations

I'm going to understand that you are getting a deviation problem for objects that you add to the external list, although you can use the internal list.

Based on this link , I understand that you can use a workaround to create an external list,

 // Simple workaround for single method // Variance in one direction only public static void Add<S, D>(List<S> source, List<D> destination) where S : D { foreach (S sourceElement in source) { destination.Add(sourceElement); } } 
0
source
 public IEnumerable<IEnumerable<T>> void Test<T>() { // Create a top IEnumeranble instance you should specify list element type var result = new List<IEnumerable<T>>(); // Add an internal IEnumerable<T> result.Add(new List<T>()); return result; } 

but if you already have an initialized List<List<T>> , you just need the cast:

 list.Cast<IEnumerable<T>>(); 
-2
source

Source: https://habr.com/ru/post/898777/


All Articles