我正在尝试创建一个接受任何通用列表的用户控件,以便我可以遍历它并创建CSV导出。是否可以公开可以接受任何类型的公共财产? (即List<Product>
,List<Customer>
等。如果是,怎么做?
public IEnumerable<T> AnyList { get; set; }
就我的实用方法而言,这就是我所拥有的:
public static byte[] ToCsv<T>(string separator, IEnumerable<T> objectlist)
{
//Type t = typeof(T); Deleted this line.
//Here's the line of code updated.
PropertyInfo[] propertyNames = objectlist.First().GetType().GetProperties();
string header = String.Join(separator, propertyNames.Select(f => f.Name).ToArray());
StringBuilder csvdata = new StringBuilder();
csvdata.AppendLine(header);
foreach (var o in objectlist)
csvdata.AppendLine(ToCsvFields(separator, propertyNames, o));
return Encoding.ASCII.GetBytes(csvdata.ToString());
}
public static string ToCsvFields(string separator, PropertyInfo[] fields, object o)
{
StringBuilder linie = new StringBuilder();
foreach (var f in fields)
{
if (linie.Length > 0)
linie.Append(separator);
var x = f.GetValue(o, null);
if (x != null)
linie.Append(x.ToString());
}
return linie.ToString();
}
答案 0 :(得分:16)
如果您想要从对象列表中“创建CSV导出”,您应该使用反射来计算列。
将分隔符默认设置为逗号更有意义,并且有助于使初始标题行的输出可选。现在,它还使用Concat
支持字段和简单属性:
public static IEnumerable<string> ToCsv<T>(IEnumerable<T> objectlist, string separator = ",", bool header = true)
{
FieldInfo[] fields = typeof(T).GetFields();
PropertyInfo[] properties = typeof(T).GetProperties();
if (header)
{
yield return String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p=>p.Name)).ToArray());
}
foreach (var o in objectlist)
{
yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString())
.Concat(properties.Select(p=>(p.GetValue(o,null) ?? "").ToString())).ToArray());
}
}
所以你可以像这样用逗号分隔:
foreach (var line in ToCsv(objects))
{
Console.WriteLine(line);
}
或类似于另一个分隔符(例如TAB):
foreach (var line in ToCsv(objects, "\t"))
{
Console.WriteLine(line);
}
将列表写入以逗号分隔的CSV文件
using (TextWriter tw = File.CreateText("C:\testoutput.csv"))
{
foreach (var line in ToCsv(objects))
{
tw.WriteLine(line);
}
}
或以制表符分隔
using (TextWriter tw = File.CreateText("C:\testoutput.txt"))
{
foreach (var line in ToCsv(objects, "\t"))
{
tw.WriteLine(line);
}
}
如果您有复杂的字段/属性,则需要将它们从select子句中过滤掉。
如果您更喜欢通用解决方案(这可确保对象属于同一类型):
public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> objectlist)
{
FieldInfo[] fields = typeof(T).GetFields();
PropertyInfo[] properties = typeof(T).GetProperties();
yield return String.Join(separator, fields.Select(f => f.Name).Union(properties.Select(p=>p.Name)).ToArray());
foreach (var o in objectlist)
{
yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString())
.Union(properties.Select(p=>(p.GetValue(o,null) ?? "").ToString())).ToArray());
}
}
这包括公共领域和公共财产。
通常使用反射,您不需要知道列表中对象的类型(您必须假设它们都是相同的类型)。
你可以使用:
public IEnumerable<object> AnyList { get; set; }
您想要做的事情的基本过程如下:
GetType()
)。您可以使用相同的算法生成2D数组(即,如果您希望控件以表格/网格形式显示CSV)。
您遇到的唯一问题可能是从IEnumerables /特定类型的列表转换为IEnumerable。在这些情况下,只需在特定类型的枚举上使用.Cast<object>
。
您需要对其代码进行以下更改:
// Make it a simple extension method for a list of object
public static string GetCSV(this List<object> list)
{
StringBuilder sb = new StringBuilder();
//Get the properties from the first object in the list for the headers
PropertyInfo[] propInfos = list.First().GetType().GetProperties();
如果您想支持一个空列表,请添加第二个参数(例如Type type
),这是您期望的 对象的类型,并使用它而不是list.First()。GetType ()。
注意:我没有在其代码中看到引用T的其他任何地方,但如果我错过了它,编译器会找到它:)
public static IEnumerable<string> ToCsv(string separator, IEnumerable<object> objectlist)
{
if (objectlist.Any())
{
Type type = objectlist.First().GetType();
FieldInfo[] fields = type.GetFields();
yield return String.Join(separator, fields.Select(f => f.Name).ToArray());
foreach (var o in objectlist)
{
yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString()).ToArray());
}
}
}
这样做的好处是内存开销很低,因为它会在结果发生时产生结果,而不是创建一个庞大的字符串。您可以像使用它一样使用它:
foreach (var line in ToCsv(",", objects))
{
Console.WriteLine(line);
}
我更喜欢实践中的通用解决方案,所以首先放置它。
答案 1 :(得分:2)
你必须做这样的事情:
public class MyUserControl<T> : UserControl
{
public IEnumerable<T> AnyList { get; set; }
}
从C#视图来看,这非常好。但是,如果您希望能够在ASPX标记中声明用户控件,则用户控件不能包含类级别类型参数。这不可能:
<controls:MyUserControl<Customer> runat="server" ID="Foo" />
现在,如果您的用户控件只会在代码隐藏中创建,而不是在标记中创建,那么这很好:
this.SomePlaceHolder.Controls.Add(new MyUserControl<Customer>());
如果您需要/希望能够在ASPX标记中声明您的用户控件,那么您可以做的最好的事情是创建IEnumerable<object>
属性。
答案 2 :(得分:1)
我从here
找到了一个很好的解决方案public static class LinqToCSV
{
public static string ToCsv<T>(this IEnumerable<T> items)
where T : class
{
var csvBuilder = new StringBuilder();
var properties = typeof(T).GetProperties();
foreach (T item in items)
{
string line = string.Join(",",properties.Select(p => p.GetValue(item, null).ToCsvValue()).ToArray());
csvBuilder.AppendLine(line);
}
return csvBuilder.ToString();
}
private static string ToCsvValue<T>(this T item)
{
if(item == null) return "\"\"";
if (item is string)
{
return string.Format("\"{0}\"", item.ToString().Replace("\"", "\\\""));
}
double dummy;
if (double.TryParse(item.ToString(), out dummy))
{
return string.Format("{0}", item);
}
return string.Format("\"{0}\"", item);
}
}
显然它处理逗号和双引号。我要用它。
答案 3 :(得分:0)
要拥有通用属性,该类也必须是通用的:
public class MyClass<T>
{
public IEnumerable<T> AnyList { get; set; }
}
答案 4 :(得分:0)
由于IEnumerable
是IEnumerable<T>
的基类,因此任何正确编写的IEnumerable<T>
类都可以传递给IEnumerable
类型的属性。因为听起来你只对“ToString()部分”感兴趣,而不是“T部分”,IEnumerable
应该可以正常工作。
答案 5 :(得分:0)
这对我有用。
using System.Reflection;
using System.Text;
//***
public void ToCSV<T>(IEnumerable<T> items, string filePath)
{
var dataTable = new DataTable(typeof(T).Name);
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
dataTable.Columns.Add(prop.Name, prop.PropertyType);
foreach (var item in items)
{
var values = new object[props.Length];
for (var i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
StringBuilder fileContent = new StringBuilder();
foreach (var col in dataTable.Columns)
fileContent.Append(col.ToString() + ",");
fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
foreach (DataRow dr in dataTable.Rows)
{
foreach (var column in dr.ItemArray)
fileContent.Append("\"" + column.ToString() + "\",");
fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
}
try
{
System.IO.File.WriteAllText(filePath, fileContent.ToString());
}
catch (Exception)
{
throw;
}
}