locked
Using reflections to do Add on List<T> C# RRS feed

  • Question

  • User74064462 posted

    I am reading a file which is like

    HDR …
    LIN …
    LIN …
    LIN …
    HDR …
    LIN …
    LIN …

    Now LIN corresponds to line items and belong to the HDR above.

    Also, mapping for the data comes from xml which has already been deserilized. This has the mappings like which property to store the data in Header or LineItems class, how many chars to pick, if it is required or not etc.

    The model to store data looks like

    public class Header
    {
        public string Id { get; set; }
        public string Description { get; set; }
        public List<Item> LineItems { get; set; }
    }

    I have a generic function which reads this data into List We have different files each having different mapping. For this reason need to use a generic function to load data before processing it.

    I need help with

    • #1 in the below function - is it correct way to make sure that property LineItems is not null before adding the LineItem to it?

    • How to do #2 in below function, using reflection to add lineItem to data[headindex-1].LineItems.Add(...)?

    public static List<H> ReadFile<H, I>(ConfigTypeUploadXml uploadConfig, string fileNamePath, ref string errMsg) where H : new() where I : new()
    {
        // we'll use reflections to add LineItems to data
        var properties = typeof(H).GetProperties();
    
        // read the file line by line and add to data. 
        var data = new List<H>();
        var headIndex = 0;
        var lineIndex = 1;
    
        foreach (string line in File.ReadAllLines(fileNamePath))
        {
    
            // read HDR line
            if (line.StartsWith("HDR"))
            {
                var header = ReadHeaderLine<H>(uploadConfig, line, errMsg);
                data.Add(header);
                headIndex += 1;
            }
    
            // read LIN line
            if (line.StartsWith("LIN"))
            {
                var lineItem = ReadItemLine<I>(uploadConfig, line, errMsg);
    
                foreach (PropertyInfo p in properties)
                {
                    if (p.Name != "LineItems")
                        continue;
    
                    //1) if items is null then create the object
                    List<I> items = p.GetValue(data[headIndex - 1], null);
                    if (items == null)
                        p.SetValue(data[headIndex - 1], new List<I>());
    					
    		//2) add line item to data[headIndex - 1]
    				
                }
            }
    
            lineIndex += 1;
        }
    
        return data;
    }

    Tuesday, June 11, 2019 11:08 PM

Answers

  • User74064462 posted

    Hi @Yongqing Yu,

    I am reading text file line by line so i don't have the list of LineItems. I have only have lineItem that i need to add to the data[headIndex - 1].LineItems.Add(lineItem). 

    So had changed the function a little bit... This isn't tested since i am waiting on some additional info. The actual app is in vb.net so converting the code didn't follow the best practices so plz ignore that. For simplicity, i have removed validations as well.

    public static List<THeader> ReadUpload850File<THeader, TItem>(ConfigTypeUploadXml uploadConfig, string fileNamePath, ref string errMsg)
         where THeader : new()
         where TItem : new()
    {
        
        // we'll use reflections to add LineItems to data
        PropertyInfo[] properties = typeof(THeader).GetProperties();
    
        // read the file line by line
        List<THeader> data = new List<THeader>();
        int headIndex = 0;
        int lineIndex = 1;
    
        foreach (string line in File.ReadAllLines(fileNamePath))
        {
            // read HDR line
            if (line.StartsWith("HDR"))
            {
                THeader header = ReadHeaderLine<THeader>(uploadConfig, line, errMsg);
                data.Add(header);
                headIndex += 1;
            }
    
            // read LIN line
            if (line.StartsWith("LIN"))
            {
                TItem lineItem = ReadItemLine<TItem>(uploadConfig, line, errMsg);
                THeader header = data[data.Count - 1];
                PropertyInfo lineItemsProperty = header.GetType().GetProperty("LineItems");
                // Dim items As Object = lineItemsProperty.GetValue(header)
                // If items Is Nothing Then lineItemsProperty.SetValue(header, Nothing)
                IList lineItems = lineItemsProperty.GetValue(header) as IList;
    if(lineItems == null) lineItems = new List<TItem>(); lineItems.Add(lineItem);
    lineItemProperty.SetValue(header, lineItems) } lineIndex += 1; } return data; }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, June 12, 2019 3:56 PM

All replies

  • User665608656 posted

    Hi Learning,

    According to the two issues you raised, the first one which is to make sure that the LineItems attribute is not Null before adding lineItem is correct.

    Second, when you confirm that LineItems are not null, you need to add lineItem to the LineItems in data[headIndex- 1].

    To achieve this function, I recommend that you still use SetValue Method.

    For the use of SetValue for Reflection, you could refer to the following links: 

    https://docs.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo.setvalue?view=netframework-4.8#System_Reflection_PropertyInfo_SetValue_System_Object_System_Object_

    The code you could change like below:

    Since your code is not comprehensive, the following attributes and data are my fiction

     if (line.StartsWith("LIN"))
            {
                var lineItem = ReadItemLine<I>(uploadConfig, line, errMsg);
    
                foreach (PropertyInfo p in properties)
                {
                    if (p.Name != "LineItems")
                        continue;
    
                    //1) if items is null then create the object
                    List<I> items = p.GetValue(data[headIndex - 1], null);
                    if (items == null)
    {
    //2) add line item to data[headIndex - 1]
    Item dd = new Item();
    dd.ItemID = "1";
    dd.ItemName = "item1";
    List<Item> itemlist = new List<Item>();
    itemlist.Add(dd);
    p.SetValue(data[headIndex - 1], itemlist);
    } } }

    Here is the debug picture of the data[headIndex - 1]:

    Best Regards,

    YongQing.

    Wednesday, June 12, 2019 5:36 AM
  • User74064462 posted

    Hi @Yongqing Yu,

    I am reading text file line by line so i don't have the list of LineItems. I have only have lineItem that i need to add to the data[headIndex - 1].LineItems.Add(lineItem). 

    So had changed the function a little bit... This isn't tested since i am waiting on some additional info. The actual app is in vb.net so converting the code didn't follow the best practices so plz ignore that. For simplicity, i have removed validations as well.

    public static List<THeader> ReadUpload850File<THeader, TItem>(ConfigTypeUploadXml uploadConfig, string fileNamePath, ref string errMsg)
         where THeader : new()
         where TItem : new()
    {
        
        // we'll use reflections to add LineItems to data
        PropertyInfo[] properties = typeof(THeader).GetProperties();
    
        // read the file line by line
        List<THeader> data = new List<THeader>();
        int headIndex = 0;
        int lineIndex = 1;
    
        foreach (string line in File.ReadAllLines(fileNamePath))
        {
            // read HDR line
            if (line.StartsWith("HDR"))
            {
                THeader header = ReadHeaderLine<THeader>(uploadConfig, line, errMsg);
                data.Add(header);
                headIndex += 1;
            }
    
            // read LIN line
            if (line.StartsWith("LIN"))
            {
                TItem lineItem = ReadItemLine<TItem>(uploadConfig, line, errMsg);
                THeader header = data[data.Count - 1];
                PropertyInfo lineItemsProperty = header.GetType().GetProperty("LineItems");
                // Dim items As Object = lineItemsProperty.GetValue(header)
                // If items Is Nothing Then lineItemsProperty.SetValue(header, Nothing)
                IList lineItems = lineItemsProperty.GetValue(header) as IList;
    if(lineItems == null) lineItems = new List<TItem>(); lineItems.Add(lineItem);
    lineItemProperty.SetValue(header, lineItems) } lineIndex += 1; } return data; }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Wednesday, June 12, 2019 3:56 PM
  • User665608656 posted

    Hi Learning,

    According to your description,I'm not clear about your requirements.

    Based on your code,

    IList lineItems = lineItemsProperty.GetValue(header) as IList;

    What's the definition of IList in this line? It will report errors in my test.

    So I change IList to List<TItem>,I guess IList refers to List<TItem>,right?

    After change this code I test your codes successfully,then I convert them to vb.net code as you expected below:

    Public Function ReadUpload850File(Of THeader As New, TItem As New)(ByVal fileNamePath As String, ByRef errMsg As String) As List(Of THeader)
        Dim properties As PropertyInfo() = GetType(THeader).GetProperties()
        Dim data As List(Of THeader) = New List(Of THeader)()
        Dim headIndex As Integer = 0
        Dim lineIndex As Integer = 1
    
        For Each line As String In File.ReadAllLines(fileNamePath)
    
            If line.StartsWith("HDR") Then
                Dim header As THeader = ReadHeaderLine(Of THeader)(line, errMsg)
                data.Add(header)
                headIndex += 1
            End If
    
            If line.StartsWith("LIN") Then
                Dim lineItem As TItem = ReadHeaderLinesd(Of TItem)(line, errMsg)
                Dim header As THeader = data(data.Count - 1)
                Dim lineItemsProperty As PropertyInfo = header.[GetType]().GetProperty("LineItems")
                Dim lineItems As List(Of TItem) = TryCast(lineItemsProperty.GetValue(header), List(Of TItem))
                If lineItems Is Nothing Then lineItems = New List(Of TItem)()
                lineItems.Add(lineItem)
                lineItemsProperty.SetValue(header, lineItems)
            End If
    
            lineIndex += 1
        Next
    
        Return data
    End Function

    Best Regards,

    YongQing.

    Thursday, June 13, 2019 7:15 AM
  • User74064462 posted

    List<T> implements the IList interface. When you call IList.Add(), the value is cast to the correct type and then passed to List<T>.Add().

    I successfully loaded the data through my code. Don't know why you are getting an error, i can' replicate the error.

    Thursday, June 13, 2019 3:41 PM