Отражение .NET: определение возможности преобразования массива T в другой тип - PullRequest
1 голос
/ 30 октября 2009

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

Вот функция, которую я написал с начальной реализацией:

    // is "toType" some sort of sequence that would be satisfied
    // by an array of type T? If so, what is the type of T?
    // e.g.
    // getArrayType(typeof(string[]) == typeof(string)
    // getArrayType(typeof(IEnumerable<int>)) == typeof(int)
    // getArrayType(typeof(List<string>)) == null // NOTE - Array<string> does not convert to List<string>
    // etc.
    private static Type getArrayType(Type toType)
    {
        if (toType.IsArray)
        {
            if (toType.GetArrayRank() != 1)
                return null;
            return toType.GetElementType();
        }
        // Look for IEnumerable<T>, and if so, return the type of T
        if (toType.IsGenericType && toType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            return toType.GetGenericArguments()[0];

        return null;
    }

Может ли быть лучше и обрабатывать больше дел? например В настоящее время

getType(typeof(ICollection<string>)) == null 

но строка [] конвертируется в ICollection

Обратите внимание, я не знаю заранее, что такое тип "element".

Контекст таков: я пишу привязку отражения к языку сценариев и хочу, чтобы он «просто работал», если вы передаете объект [] какому-либо методу, ожидающему IEnumerable (он конвертирует каждый из элементов в этом случае входной массив в строку).

Итак, чтобы уточнить, скажем, у меня есть некоторая сигнатура метода:

void WriteCSV(ICollection<string> fields);

и мой интерпретатор сценариев имеет массив объектов, которые могут быть преобразованы в строку:

object[] fields = new object[] { "one", 2, "three" };

Тогда мой интерпретатор сценариев должен выяснить, что в данном случае действительно нужен массив строк.

Принимая во внимание, что мой переводчик сценариев должен отказаться, скажем:

void WriteCSV(IRecord record);

, хотя IRecord может даже реализовать некоторые IEnumerable:

interface IRecord : IEnumerable<string>
{
    void OtherMethods();
}

Я никак не могу построить IRecord из массива чего-либо.

Так что просто выяснить, что IEnumerables реализует тип, не то, что мне нужно. Я сказал это было тонко, не так ли?

Ответы [ 3 ]

1 голос
/ 31 октября 2009

Это можно сделать, создав массив универсального аргумента и посмотрев, реализует ли он целевой тип. Пример кода проверяет только интерфейсы, поскольку array обеспечивает реализацию только интерфейсов (IList, ICollection, IEnumerable). Кроме того, я возвратил из примера тип массива (просто чтобы прояснить это), в вашем коде вы захотите вернуть универсальный аргумент, как в примере кода, который вы опубликовали.

private static Type GetArrayType(Type toType)
{
    if (toType.IsArray)
    {
        if (toType.GetArrayRank() != 1)
            return null;
        return toType;
    }

    // Verifies the type is generic and has only one generic argument
    if (toType.IsGenericType && toType.GetGenericArguments().Length == 1)
    {
        // Creates an array of the generic argument, e.g. IList<int> -> int[]
        var arrayOfType = toType.GetGenericArguments()[0].MakeArrayType();

        // Checks if the toType is an interface that the array implements
        if (arrayOfType.GetInterfaces().Contains(toType))
        {
            return arrayOfType    // arrayOfType.GetGenericArguments()[0];
        }
    }    
    return null;
}

public static void Test()
{
    Assert.AreEqual(typeof(int[]), GetArrayType(typeof(IList<int>)));
    Assert.AreEqual(typeof(int[]), GetArrayType(typeof(ICollection<int>)));
    Assert.AreEqual(typeof(int[]), GetArrayType(typeof(IEnumerable<int>)));

    Assert.IsNull(GetArrayType(typeof(List<int>)));
    Assert.IsNull(GetArrayType(typeof(Dictionary<int, string>)));
}

Надеюсь, это поможет.

0 голосов
/ 30 октября 2009

Случай массива покрывается IEnumerable<T> как одномерные массивы (те, которые вы ищете, используя GetArrayrank) реализуют IList<T>, который, в свою очередь, получен из IEnumerable<T>. Имеет смысл поискать IEnumerable next и рассматривать тип элемента как объект - так конструкция foreach обрабатывает коллекции.

Еще один момент, который необходимо учитывать, - это случай, когда предоставленный тип реализует IEnumerable<T> несколько раз (например, IEnumerable<object> и IEnumerable<string>). Например, DataContractSerializer в этом случае не рассматривает тип как тип коллекции.

0 голосов
/ 30 октября 2009

На основании моего ответа на ваш последний вопрос:

Вы можете использовать этот фрагмент кода, чтобы получить все реализации интерфейса IEnumberable<T> для определенного типа, а затем извлечь их универсальный параметр.

Type type = typeof(ICollection<string>);

IEnumerable<Type> elementTypes = type.GetInterfaces()
    .Where(i => i.IsGenericType 
        && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
    .Select(i => i.GetGenericArguments()[0]);

В этом примере elementTypes будет единственным элементом, содержащим тип для System.String.

Что вы делаете с типами элементов, зависит от вас. Поскольку интерфейс может быть реализован несколько раз, возможно, вы захотите изменить запрос, чтобы получить единственную реализацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...