繁体   English   中英

反射属性.SetValue(

[英]Reflection Property.SetValue(

我有以下类可以从分隔行创建对象:

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, ','));
    } 
}

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

更新:刚刚意识到我的班级不是班级,而是实际上是一个结构体。

T是值类型时,为了使您的Mapper<T>工作,您需要在装箱为 object 时设置其属性。 使用非通用的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;
    }
}

样品小提琴

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

GetProperties 方法不以特定顺序返回属性,例如字母顺序或声明顺序。 您的代码不得依赖于返回属性的顺序,因为该顺序会有所不同。

因此,我修改了您的代码以按名称排序。 您可以选择另一种策略,例如对数据协定类型使用数据成员顺序

最后,您可能想重新考虑使用可变结构的设计,请参阅为什么可变结构是“邪恶的”? 出于某些原因。 要将您的Mapper<T>限制为仅适用于引用类型,您可以添加以下where约束:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM