Как найти свойства и поля для типа, а затем прочитать их из коллекции этого типа? - PullRequest
0 голосов
/ 19 апреля 2019

У меня есть список T, и мне нужно создать строку, соединенную "\ t" с несколькими свойствами этого типа.

string str = "1,2,3;2,3,4;4,5,6";
var arr = str
    .Split(';')
    .Select(x => x.Split(','))
    .Select(x => new
    {
        first = x[0],
        second = x[1],
        third = x[2]
    })
    .ToList();

Теперь обр:

Enumerable.WhereSelectArrayIterator<string, <>f__AnonymousType0#10<string, string, string>> { \{ first = "1", second = "2", third = "3" }, \{ first = "2", second = "3", third = "4" }, \{ first = "4", second = "5", third = "6" } }

сейчас, если мне нужно получить

List<string>(3) { "1\t2\t3", "2\t3\t4", "4\t5\t6" }

я должен сделать это

arr.Select(x => x.first + "\t" + x.second + "\t" + x.third).ToList();

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

arr.Select(x => x.first, x.second, x.third).ToList();

и получите тот же результат.

У меня есть метод расширения, который соединяет по вкладке все поля объекта

public static IEnumerable<string> JoinByTab<T>(this IEnumerable<T> list) where T : class
{
    PropertyInfo[] properties = list.FirstOrDefault().GetType().
        GetProperties(BindingFlags.Public | BindingFlags.Instance);

    var listStr = list
        .Select(x => String.Join("\t", properties.Select(y => y.GetValue(x, null).ToString())));

    return listStr;
}

но часто мне нужно указывать поля.

Ответы [ 3 ]

0 голосов
/ 19 апреля 2019

но часто мне нужно указывать поля.

Вам часто нужно объединять поля объекта с разделителем табуляции? У вас даже есть метод расширения для этого? Вау, это интересный проект.

Вы можете просто создать массив из вашего объекта и использовать обычную строку. Join

arr.Select(x => string.Join("\t", new[] { x.first, x.second, x.third }).ToList();

метод расширения не требуется

0 голосов
/ 19 апреля 2019

Похоже, вы пытаетесь получить все свойства и поля string типа T. Затем для каждого элемента в наборе T вы хотите прочитать все эти строковые свойства и затем соединить эти строки. Вы начинаете со списка T и заканчиваете списком string.

Это станет проще, если вы отделите часть кода, которая выполняет отражение (находит свойства и поля), от части, которая читает список объектов.

Поскольку свойства и поля одинаковы для всех объектов в списке (поскольку они все одного типа), вы пытаетесь обнаружить свойства и поля один раз, а затем повторно использовать их. Это хорошо.

Итак, вопрос в том, как это сделать. Вот два способа.

Первый принимает тип (T') and returns all of the public instance properties and fields that return строка . PropertyInfo and MemberInfo both inherit from MemberInfo , so we're returning a set of MemberInfo`.

public static IEnumerable<MemberInfo> GetStringMembers<T>()
{
    var result = new List<MemberInfo>();
    result.AddRange(
            typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(property => property.PropertyType == typeof(string)));
    result.AddRange(
        typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance)
            .Where(field => field.FieldType == typeof(string)));
    return result;
}

Проблема в том, что теперь каждый элемент в списке может быть PropertyInfo или FieldInfo, поэтому, когда мы пытаемся получить каждое значение из каждого экземпляра класса, нам нужно посмотреть на каждого члена и посмотреть, является ли это свойством или поле, потому что это разные методы для чтения каждого. Это грязно. (Я знаю, потому что я написал этот точный код.)

Лучшим подходом было бы создание списка функций, которые принимают T и возвращают строку. Внутри каждой функции мы можем проверять свойство или поле. Это будет выглядеть так Я сделал это без LINQ, чтобы было легче читать:

private static IEnumerable<Func<T, string>> GetStringMemberFunctions<T>()
{
    var result = new List<Func<T, string>>();
    var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    foreach (var property in properties)
    {
        result.Add(item => (string)property.GetValue(item));
    }
    var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
    foreach (var field in fields)
    {
        result.Add(item => (string)field.GetValue(item));
    }
    return result;
}

То, что мы возвращаем, это список Func<T, string>. Это список функций, которые принимают T и возвращают string. Каждая из этих функций проверяет либо свойство, либо поле. (Resharper может преобразовать это в выражения LINQ для меня, но я не публикую его, потому что его труднее читать, поэтому я не думаю, что это помогает.)

Теперь ваш оригинальный метод не содержит всего этого отражения. Это позволяет другому методу обработать это, и это выглядит так:

public static IEnumerable<string> JoinByTab<T>(this IEnumerable<T> list) where T : class
{
    var functions = GetStringMemberFunctions<T>();

    var listStr = list
        .Select(x => String.Join("\t", functions.Select(function => function(x))));

    return listStr;
}

Это точно так же, кроме

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

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

public static Func<T, string[]> GetExtractStringValuesFunction<T>()
{
    var memberFunctions = GetStringMemberFunctions<T>();
    return item => memberFunctions.Select(function => function(item)).ToArray();
}

public static IEnumerable<string> JoinByTab<T>(this IEnumerable<T> list) where T : class
{
    var stringValuesFunction = GetExtractStringValuesFunction<T>();

    var listStr = list
        .Select(x => String.Join("\t", stringValuesFunction(x)));

    return listStr;
}

Он вызывает GetStringMemberFunctions, чтобы получить все функции для чтения членов, а затем объединяет их все в один Func<T, string[]>. Теперь у нас есть одна функция, которая принимает экземпляр T и возвращает все строки из свойств и полей.

Это небольшое улучшение только для иллюстрации. Если предыдущая версия проста для понимания, это не дает большого преимущества.


Примечание:

Делать это, чтобы получить список свойств и полей, небезопасно и может вызвать исключение:

list.FirstOrDefault().GetType()

Все элементы в вашем списке имеют тип T. Но гипотетически любой отдельный элемент в этом списке также может наследоваться от T. Так что же произойдет, если первый элемент в списке - это тип, который наследуется от T и имеет свойства или поля, которых нет у других объектов в списке?

GetType() вернет конкретный тип объекта. Это может быть точно T, но это может быть некоторый унаследованный тип. Вы можете создать список свойств и полей на основе этого типа, но затем получить исключение, потому что вы пытаетесь прочитать их из объектов, которые не имеют этих свойств или полей.

Поэтому, если у вас есть List<T> и вы хотите получить тип, безопаснее использовать typeof(T).

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

0 голосов
/ 19 апреля 2019

Вы можете передать кучу лямбд в метод объединения, например:

public static IEnumerable<string> Join<T>(this IEnumerable<T> list,
    string separator,
    params Func<T, string>[] properties) where T : class
{
    foreach (var element in list)
    {
        yield return string.Join(separator, properties.Select(p => p(element)));
    }
}

И используйте это так:

var result = arr.Join("\t", 
        x => x.first, 
        x => x.second, 
        x => x.third)
    .ToList();
...