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