Как привести общий тип во время выполнения в C # - PullRequest
8 голосов
/ 06 октября 2011

Мне нужно создать IEnumerable<IEnumerable<T>>, когда я знаю T только во время выполнения.

Я собрал свою коллекцию так:

new List<List<object>>() 

где все объекты во внутреннем списке являются T

Однако из-за со / контравариантности (никогда не могу вспомнить, что это такое!) Мои List из List s не являются IEnumerable из IEnumerable s.

Что я могу с этим поделать?

Я пытался использовать Convert.ChangeType, но стонет, что List не IConvertible

Подсказка: Прочитайте вопрос. Снова. Я сказал, что знаю только T во время выполнения.

Ответы [ 7 ]

15 голосов
/ 07 октября 2011

ОК, исходя из ответа Мастера Морали, я придумал это. Шокирующе просто.

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. Об этом пишут здесь: Приведение перечислимого, когда внутренний тип известен только во время выполнения

3 голосов
/ 06 октября 2011

У меня были похожие проблемы с TinyIoC, и вместо «конвертации» самое «чистое» решение, которое я нашел, - это сделать ваш метод универсальным (чтобы общедоступным IEnumerable'T DoStuff'T ()), а затем вызвать егоиспользуя MakeGenericMethod, используя ваш тип времени выполнения.Он остается «чистым», потому что ваш реальный метод, который создает список, просто работает так, как если бы он был обычным универсальным методом, поэтому он не загромождается приведением типов и т. Д.

Не видя ваш код, трудно понять,это соответствует требованиям - вот соответствующие биты для создания универсального метода из 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;
            }
        }
    }
}

, который вызывается следующим образом:

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 });
}

И ResolveAll определяется как:

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

Надеюсь, это имеет смысл:)

3 голосов
/ 06 октября 2011
  1. Используйте его без указания типа IEnumerable<IEnumerable>
  2. Используйте рефлексию для вызова функции, которая принимает IEnumerable<IEnumerable<T>> с соответствующим T
  3. используйте оператор switch для приведения к соответствующему типу
  4. использовать dynamic


Примеры
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
}
2 голосов
/ 06 октября 2011

Редактировать: Если вы знаете только T во время выполнения, вы можете сделать это, построив выражение.и компилировать это.например так:

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);

вы можете опционально кэшировать lambdax2.Compile() где-нибудь для каждого типа среды выполнения, производительности.

0 голосов
/ 06 октября 2011

На основании вашего комментария,

не совсем, но спасибо, я мог бы создать внутренний список прав типа, но тогда я могу вставить объекты в него, и у меня все еще есть проблема дисперсии

Я понял, что у вас возникает проблема дисперсии объектов, которые вы добавляете во внешний список, хотя вы можете создавать внутренний список.

Основываясь на этой ссылке , я понимаю, что вы можете использовать обходной путь для создания экземпляра внешнего списка,

// 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 голосов
/ 06 октября 2011

Я полагаю, что ответ "вы не можете" - хотя я, вероятно, могу ошибаться с каким-то супер-хакерским кодом, который использует тонну отражений или напрямую генерирует IL или что-то в этом роде.

Компилятор и JIT'er должны знать типы объектов для всего, что нужно для установки стека, правильного распределения памяти и т. Д.

Возможно, каждый тип T может реализовывать некоторый интерфейс маркера или основываться на общей базе? Различные варианты поведения могут быть реализованы виртуально. Если вы можете немного прокомментировать то, что пытается сделать ваша программа, возможно, люди могли бы придумать хороший дизайн.

0 голосов
/ 06 октября 2011
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;
}

, но если у вас уже есть инициализированный List<List<T>>, вам просто нужен литой:

list.Cast<IEnumerable<T>>();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...