反射 Property.SetValue(

Reflection Property.SetValue(

我有以下 class 从分隔线创建对象:

public class Mapper<T>
{
    public T Map(string line, char delimiter)
    {
        if(String.IsNullOrEmpty(line))
            throw new ArgumentNullException(nameof(line));

        if (Char.IsWhiteSpace(delimiter))
            throw new ArgumentException(nameof(delimiter));

        var splitString =  line.Split(delimiter);

        var properties = typeof(T).GetProperties();

        if(properties.Count() != splitString.Count())
            throw new InvalidOperationException($"Row has {splitString.Count()} columns but object has {properties.Count()}.");

        var obj = Activator.CreateInstance<T>();

        for (var i = 0; i < splitString.Count(); i++)
        {
            var prop = properties[i];
            var propType = prop.PropertyType;
            var valType = Convert.ChangeType(splitString[i], propType);
            prop.SetValue(obj, valType);
        }

        return (T)obj;
    }
}

如果我使用分隔字符串调用 map 方法,它将使用行中的分隔值填充对象的所有属性。

然而,当我从以下调用它时:

public class CsvStreamReader<T>
{
    private readonly Mapper<T> _mapper;
    public CsvStreamReader(Mapper<T> mapper)
    {
        _mapper = mapper;
    } 



    public IEnumerable<T> ReadCsvFile(string filePath, bool hasHeader)
    {
        if(hasHeader)
            return File.ReadAllLines(filePath)
                       .Skip(1)
                       .Select(x => _mapper.Map(x, ','));

        return File.ReadAllLines(filePath)
                   .Select(x => _mapper.Map(x, ','));
    } 
}

它会 return 一个 T 列表,但所有属性都将为 null 并且不会被设置。

更新:刚刚意识到我的 class 不是 class 但实际上是一个结构。

为了让您的 Mapper<T>T 是值类型时工作,您需要设置它的属性 while boxed as an object。使用 non-generic Activator.CreateInstance(typeof(T))obj 创建为 object,使用反射设置其属性,然后在返回时最终将其转换为所需的类型:

public class Mapper<T>
{
    readonly List<PropertyInfo> properties = typeof(T).GetProperties().OrderBy(p => p.Name).ToList();
    
    public T Map(string line, char delimiter)
    {
        if (String.IsNullOrEmpty(line))
            throw new ArgumentNullException("line");

        if (Char.IsWhiteSpace(delimiter))
            throw new ArgumentException("delimiter");

        var splitString = line.Split(delimiter);

        if (properties.Count() != splitString.Count())
            throw new InvalidOperationException(string.Format("Row has {0} columns but object has {1}", splitString.Count(), properties.Count()));

        // Create as a reference (boxed if a value type).
        object obj = Activator.CreateInstance(typeof(T));

        // Set the property values on the object reference.
        for (var i = 0; i < splitString.Count(); i++)
        {
            var prop = properties[i];
            var propType = prop.PropertyType;
            var valType = Convert.ChangeType(splitString[i], propType);
            prop.SetValue(obj, valType);
        }

        // Cast to the return type unboxing if required.
        return (T)obj;
    }
}

样本fiddle.

请注意,您的代码不应依赖于 Type.GetProperties() 返回的属性的顺序。来自 docs:

The GetProperties method does not return properties in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which properties are returned, because that order varies.

因此我修改了您的代码以按名称排序。您可以选择 select 其他策略,例如使用 data member order for data contract types.

最后,您可能想重新考虑使用可变结构的设计,请参阅 Why are mutable structs “evil”? 了解一些原因。要限制您的 Mapper<T> 仅适用于引用类型,您可以添加以下 where 约束:

public class Mapper<T> where T : class
{
}