You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何将PropertyDescriptor转为List<Tuple<string,string>>并在WriteToCsv<T>中遍历指定属性

嘿,我来帮你搞定这两个CSV转换的问题,咱们逐个击破!


问题1:将PropertyDescriptor转换为List<Tuple<string, string>>

首先,你可以利用TypeDescriptor获取目标类型的属性描述符集合,再通过LINQ将每个PropertyDescriptor转换成你需要的Tuple<string, string>。具体分两种场景:

场景A:转换属性的元数据(比如属性名+显示名/描述)

如果是要提取属性的名称和显示名这类元数据,代码如下:

using System.ComponentModel;
using System.Linq;

public List<Tuple<string, string>> ConvertPropDescriptorsToTuples(Type targetType)
{
    // 获取目标类型的所有属性描述符
    var propDescriptors = TypeDescriptor.GetProperties(targetType);
    
    // 转换为Tuple列表,这里用Name和DisplayName作为Tuple的两个元素,你可以换成Description等
    return propDescriptors.Cast<PropertyDescriptor>()
                          .Select(p => Tuple.Create(p.Name, p.DisplayName))
                          .ToList();
}

场景B:转换实例的属性名+属性值

如果是要结合具体实例,提取属性名和对应的属性值,代码调整为:

public List<Tuple<string, string>> ConvertPropDescriptorsToTuples(object instance)
{
    if (instance == null) throw new ArgumentNullException(nameof(instance));
    
    var propDescriptors = TypeDescriptor.GetProperties(instance);
    return propDescriptors.Cast<PropertyDescriptor>()
                          .Select(p => Tuple.Create(
                              p.Name, 
                              p.GetValue(instance)?.ToString() ?? string.Empty))
                          .ToList();
}

问题2:在WriteToCsv中遍历Hdd和OpticDriver属性调整CSV输出

假设你的Cmp类包含HddOpticDriver这两个嵌套复杂属性(各自有自己的子属性,比如Hdd.ModelOpticDriver.Type等),要把这些嵌套属性展开到CSV的列中,核心思路是扁平化嵌套属性,递归遍历所有层级的属性,生成类似Hdd_Model的列名,再写入对应的值。

第一步:先定义你的类结构(参考)

public class Cmp
{
    public int Id { get; set; }
    public string DeviceName { get; set; }
    public Hdd Hdd { get; set; }
    public OpticDriver OpticDriver { get; set; }
}

public class Hdd
{
    public string Model { get; set; }
    public int CapacityGB { get; set; }
}

public class OpticDriver
{
    public string DriveType { get; set; }
    public int ReadSpeed { get; set; }
}

第二步:实现支持嵌套属性的WriteToCsv方法

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

public void WriteToCsv<T>(List<T> items, string outputPath)
{
    // 获取所有扁平化的属性路径和对应的PropertyInfo
    var flatProperties = GetFlatProperties(typeof(T)).ToList();
    
    using (var writer = new StreamWriter(outputPath))
    {
        // 写入CSV表头:用扁平化的属性路径作为列名
        var headers = flatProperties.Select(fp => fp.PropertyPath).ToArray();
        writer.WriteLine(string.Join(",", headers));
        
        // 写入每行数据
        foreach (var item in items)
        {
            var values = flatProperties.Select(fp => 
            {
                var value = fp.PropertyInfo.GetValue(item);
                // 处理空值,以及包含逗号的字符串(用引号包裹)
                var strValue = value?.ToString() ?? string.Empty;
                return strValue.Contains(",") ? $"\"{strValue}\"" : strValue;
            }).ToArray();
            
            writer.WriteLine(string.Join(",", values));
        }
    }
}

// 辅助方法:递归获取所有扁平化的属性路径和PropertyInfo
private IEnumerable<(string PropertyPath, PropertyInfo PropertyInfo)> GetFlatProperties(Type type, string parentPath = "")
{
    foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
    {
        // 如果是值类型或字符串,直接加入扁平化列表
        if (prop.PropertyType.IsValueType || prop.PropertyType == typeof(string))
        {
            var fullPath = string.IsNullOrEmpty(parentPath) 
                ? prop.Name 
                : $"{parentPath}_{prop.Name}";
            yield return (fullPath, prop);
        }
        // 否则递归处理嵌套属性
        else
        {
            var nestedParentPath = string.IsNullOrEmpty(parentPath) 
                ? prop.Name 
                : $"{parentPath}_{prop.Name}";
            foreach (var nestedProp in GetFlatProperties(prop.PropertyType, nestedParentPath))
            {
                yield return nestedProp;
            }
        }
    }
}

效果说明

这样处理后,CSV的表头会是:Id,DeviceName,Hdd_Model,Hdd_CapacityGB,OpticDriver_DriveType,OpticDriver_ReadSpeed,每行数据会对应展开所有嵌套属性的值,完全符合你要的预期样式。

如果你的项目中更倾向于用PropertyDescriptor而不是反射,只需要把GetFlatProperties方法改成基于TypeDescriptor的实现即可,逻辑完全一致:

private IEnumerable<(string PropertyPath, PropertyDescriptor PropDescriptor)> GetFlatPropDescriptors(Type type, string parentPath = "")
{
    var propDescriptors = TypeDescriptor.GetProperties(type);
    foreach (PropertyDescriptor prop in propDescriptors)
    {
        if (prop.PropertyType.IsValueType || prop.PropertyType == typeof(string))
        {
            var fullPath = string.IsNullOrEmpty(parentPath) ? prop.Name : $"{parentPath}_{prop.Name}";
            yield return (fullPath, prop);
        }
        else
        {
            var nestedParentPath = string.IsNullOrEmpty(parentPath) ? prop.Name : $"{parentPath}_{prop.Name}";
            foreach (var nestedProp in GetFlatPropDescriptors(prop.PropertyType, nestedParentPath))
            {
                yield return nestedProp;
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Sirwan Afifi

火山引擎 最新活动