动态创建列表类型(或不同类型的集合?)

时间:2016-04-12 16:35:27

标签: c# reflection collections

我正在写一个读取不同种类CSV文件的类。它根据Model类选取重要信息,其中模型类的属性是我想要获取的列名。例如,我可以拥有一个包含FromAddress和ToAddress列的OutlookModel。或者我可以拥有一个完全不同列的SalesforceModel。

当reader类解析行和列时,它会将单元格加载到模型类的实例中。在下面的代码中,参数className = OutlookModel。这里最相关的代码行是签名和返回...

    protected void MapColumns(string row, string className, List<OutlookModel> list)
    {
        string[] cols = row.Split(',');
        // create a model to save the important columns
        var model = Activator.CreateInstance(nameSpace, nameSpace + className);
        int j = 0;
        if (cols.Length > 0)
        {
            foreach (var c in cols)
            {
                // is this column index one of our important columns?
                if (Ordinals.ContainsKey(j))
                {
                    // this is a column we care about, so set the model property
                    model.GetType().GetProperty(Ordinals[j]).SetValue(model, c);
                }
                j++;
            }
        }
        list.Add(model);
    }

我遇到的问题是模型对象的集合。如果我将对象定义为List&lt; OutlookModel&gt;在参数中,该方法不可扩展。如果我将其定义为List&lt;对象&gt;,然后(我认为)我必须使用内部列表来使用我的属性,这些属性在模型之间都是不同的。

我是C#的新手。是否有更好的方法将这些不同的模型类型捕获到列表/数组/集合/中,以便我可以将逻辑应用于列表?

2 个答案:

答案 0 :(得分:1)

首先,我建议添加一个自定义属性来标记您想要从csv中读取的属性,这样当您以后必须添加某些内容时,您就不会遇到任何问题。必须依赖太多魔法弦。这是我的测试设置:

    class ReadFromCsvAttribute : Attribute { }

    class OutlookModel
    {
        public int DontSetThisValueFromCsv { get; set; }

        [ReadFromCsv]
        public string FromAddress { get; set; }

        [ReadFromCsv]
        public string ToAddress { get; set; }
    }

    class SalesForceModel
    {
        [ReadFromCsv]
        public string Name { get; set; }

        [ReadFromCsv]
        public string Age { get; set; }
    }

    static void Main(string[] args)
    {
        string outlookSample = "Id,FromAddress,ToAddress,Useless\r\n" +
                               "1,a@b.com,c@d.com,asdf\r\n" +
                               "3,y@z.com,foo@bar.com,baz";

        string salesForceSample = "Id,Name,Age\r\n" +
                                  "1,John,30\r\n" +
                                  "2,Doe,100";

        var outlook = ReadFromCsv<OutlookModel>(outlookSample);

        var salesForce = ReadFromCsv<SalesForceModel>(salesForceSample);

    }

我把这个通用方法放在一起,从数据中读取你想要的任何模型:

static List<T> ReadFromCsv<T>(string data)
{
    var objs = new List<T>();
    var rows = data.Split(new[] {"\r\n"}, StringSplitOptions.None);

    //create index, header dict
    var headers = rows[0].Split(',').Select((value, index) => new {value, index})
        .ToDictionary(pair => pair.index, pair => pair.value);

    //get properties to find and cache them for the moment
    var propertiesToFind = typeof (T).GetProperties().Where(x => x.GetCustomAttributes<ReadFromCsvAttribute>().Any());

    //create index, propertyinfo dict
    var indexToPropertyDict =
        headers.Where(kv => propertiesToFind.Select(x => x.Name).Contains(kv.Value))
            .ToDictionary(x => x.Key, x => propertiesToFind.Single(p => p.Name == x.Value));

    foreach (var row in rows.Skip(1))
    {
        var obj = (T)Activator.CreateInstance(typeof(T));

        var cells = row.Split(',');
        for (int i = 0; i < cells.Length; i++)
        {
            if (indexToPropertyDict.ContainsKey(i))
            {
                //set data
                indexToPropertyDict[i].SetValue(obj, cells[i]);
            }
        }
        objs.Add(obj);
    }

    return objs;
}

答案 1 :(得分:1)

这是另一个样本。由于你是c#的新手,我尽可能地避免了linq和扩展方法。只需将其复制到控制台应用程序中即可运行。

此外,我喜欢使用.net属性描述类的亨尼建议,但前提是您完全控制了您的生态系统。

public class Account
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class LastNameAccount
{
    public string LastName { get; set; }
    public string Address { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        Test1();
    }

    private static void Test1()
    {
        /*
         * defines the result of your CSV parsing. 
         */
        List<string> csvColumns = new List<string> { "FirstName", "LastName" };
        List<List<string>> csvRows = new List<List<string>>() {
            new List<string>(){"John","Doe"},
            new List<string>(){"Bill", "Nie"}
        };

        //Map the CSV files to Account type and output it
        var accounts = Map<Account>(csvColumns, csvRows);
        if (accounts != null)
        {
            foreach (var a in accounts)
            {
                Console.WriteLine("Account: {0} {1}", a.FirstName, a.LastName);
            }
        }

        //Map the CSV files to LastNameAccount type and output it
        var accounts2 = Map<LastNameAccount>(csvColumns, csvRows);
        if (accounts2 != null)
        {
            foreach (var a in accounts2)
            {
                Console.WriteLine("Last Name Account: {0} {1}", a.LastName, a.Address);
            }
        }
    }


    private static List<T> Map<T>(List<string> columns, List<List<string>> rows)
        where T : class, new()
    {
        //reflect the type once and get valid columns
        Type typeT = typeof(T);
        Dictionary<int, PropertyInfo> validColumns = new Dictionary<int, PropertyInfo>();
        for (int columnIndex = 0; columnIndex < columns.Count; columnIndex++)
        {
            var propertyInfo = typeT.GetProperty(columns[columnIndex]);
            if (propertyInfo != null)
            {
                validColumns.Add(columnIndex, propertyInfo);
            }
        }

        //start mapping to T 
        List<T> output = null;
        if (validColumns.Count > 0)
        {
            output = new List<T>();
            foreach (var row in rows)
            {
                //create new T
                var tempT = new T();

                //populate T's properties
                foreach (var col in validColumns)
                {
                    var propertyInfo = col.Value;
                    var columnIndex = col.Key;

                    propertyInfo.SetValue(tempT, row[columnIndex]);
                }

                //add it
                output.Add(tempT);
            }
        }

        return output;
    }
}