Сначала пара бесполезных проблем:
- Тип первого элемента в перечислимом может быть подклассом T, который определяет свойства, которые могут отсутствовать в других элементах. Чтобы избежать проблем, которые могут возникнуть, используйте тип T в качестве источника списка свойств.
- Тип может иметь свойства, которые либо не имеют получателя, либо имеют индексированный получатель. Ваш код не должен пытаться прочитать их значения.
Что касается совершенства, я вижу потенциальные улучшения как в аспектах отражения, так и в загрузке таблицы данных:
- Кэшируйте получатели свойств и вызывайте их напрямую.
- Избегайте доступа к столбцам строк данных по имени для установки значений строк.
- Поместить таблицу данных в режим «загрузки данных» при добавлении строк.
С этими модами вы получите что-то вроде следующего:
public static DataTable AsDataTable<T>(this IEnumerable<T> enumerable)
{
if (enumerable == null)
{
throw new ArgumentNullException("enumerable");
}
DataTable table = new DataTable();
if (enumerable.Any())
{
IList<PropertyInfo> properties = typeof(T)
.GetProperties()
.Where(p => p.CanRead && (p.GetIndexParameters().Length == 0))
.ToList();
foreach (PropertyInfo property in properties)
{
table.Columns.Add(property.Name, property.PropertyType);
}
IList<MethodInfo> getters = properties.Select(p => p.GetGetMethod()).ToList();
table.BeginLoadData();
try
{
object[] values = new object[properties.Count];
foreach (T item in enumerable)
{
for (int i = 0; i < getters.Count; i++)
{
values[i] = getters[i].Invoke(item, BindingFlags.Default, null, null, CultureInfo.InvariantCulture);
}
table.Rows.Add(values);
}
}
finally
{
table.EndLoadData();
}
}
return table;
}