Visual Studio Developer Center > Visual C# Forums > Visual C# General > How do you populate a List<string> from a class of different types? (string, int and boolean)

Answered How do you populate a List<string> from a class of different types? (string, int and boolean)

  • Thursday, December 11, 2008 3:23 PM
     
     
    How do you populate a List<string> from a class of different types? (string, int and boolean)
    Ideally I would like to make it generic so I can pass any class like TFileFilter and get a List<string> returned.
    Thanks!

    My class example:

    public class TFileFilter
    {

    private int fileType_cod;

    private bool includeSubDir;

    private string startDate;

    private string endDate;

    private string pathRoot;

    }

Answers

  • Friday, December 12, 2008 12:06 AM
    Moderator
     
     Answered Has Code

    This isn't going to answer your question directly, so I should say this at the top of my post, but I'm afraid your desired approach has several issues.

    Firstly, you could do this using reflection.  For the type above, you would do something like this:

    public List<string> ConvertToStringList(object value)  
    {  
        List<string> returnStringValues = new List<string>();  
     
        Type type = value.GetType();  
        BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;  
        FieldInfo[] fields = type.GetFields(bindingFlags).OrderBy(f => f.MetadataToken).ToArray();  
        foreach (FieldInfo field in fields)  
        {  
            object fieldValue = field.GetValue(value);  
            if (fieldValue == null)  
                returnStringValues.Add("NULL");  
            else 
                returnStringValues.Add(fieldValue.ToString());  
        }  
     
        return returnStringValues;  

    Now, for the drawbacks of your approach.

    1. You're doing this with private fields.  Do you want to do it with public properties as well?  If so, you'll have to run through the reflection again, only to fetch properties.
    2. What if the properties you're trying to serialize are arrays, or what if the types represented by the fields or properties use the default .ToString() method.  If that's the case, you'll only be able to get the type name, and no useful information converted to string, without making some kind of very elaborate reflection method to handle enumerable values.
    3. Performance is going to be a problem.  Reflection isn't very fast at all.
    4. Do you want to deserialize and create an object based on the values stored in the memo field?  If so, you're going to have to create a method to reverse what you've done in order to make it work properly.
    5. Storing several fields within one text field is simply bad practice.  It's going to be very hard to support later on.
    6. There are already implementations that will allow you to convert your values to a string representation.  You may want to look into the XmlSerializer class instead.  This would be a much better option, if you insist on serializing your whole object out to string.

    Now, with these drawbacks, I strongly recommend you find another way to serialize your object out to string.  The method you're trying to do is going to cause you nothing but a headache later on, and is liable to be completely misunderstood by later developers (including yourself).  Do yourself a favor.  Create a new table in the database.  Store each field's value in the database one column per field.  This is the proper way to handle something like this.
    David Morton - http://blog.davemorton.net/
    • Marked As Answer by d_vm23 Friday, December 12, 2008 6:35 PM
    •  

All Replies

  • Thursday, December 11, 2008 3:26 PM
     
     
    What do you want the List<string> to contain?  The string member of the different class?  Or the ToString() value of the different class?  Or something else?
    If this answers your question, please mark the question as answered.
  • Thursday, December 11, 2008 3:33 PM
     
     

    Each string in the the List<string> would contain a string representation of the type.  I have 5 types in the class so the list would contain 5 lines.  So following the example above, the fileType_cod, would be converted from integer to a string and be placed in the first line.  The includeSubDir bool would be converted to a string on the 2nd line in the list...

    In this way I can insert the class into a memo field in a MS Access table.

  • Friday, December 12, 2008 12:06 AM
    Moderator
     
     Answered Has Code

    This isn't going to answer your question directly, so I should say this at the top of my post, but I'm afraid your desired approach has several issues.

    Firstly, you could do this using reflection.  For the type above, you would do something like this:

    public List<string> ConvertToStringList(object value)  
    {  
        List<string> returnStringValues = new List<string>();  
     
        Type type = value.GetType();  
        BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;  
        FieldInfo[] fields = type.GetFields(bindingFlags).OrderBy(f => f.MetadataToken).ToArray();  
        foreach (FieldInfo field in fields)  
        {  
            object fieldValue = field.GetValue(value);  
            if (fieldValue == null)  
                returnStringValues.Add("NULL");  
            else 
                returnStringValues.Add(fieldValue.ToString());  
        }  
     
        return returnStringValues;  

    Now, for the drawbacks of your approach.

    1. You're doing this with private fields.  Do you want to do it with public properties as well?  If so, you'll have to run through the reflection again, only to fetch properties.
    2. What if the properties you're trying to serialize are arrays, or what if the types represented by the fields or properties use the default .ToString() method.  If that's the case, you'll only be able to get the type name, and no useful information converted to string, without making some kind of very elaborate reflection method to handle enumerable values.
    3. Performance is going to be a problem.  Reflection isn't very fast at all.
    4. Do you want to deserialize and create an object based on the values stored in the memo field?  If so, you're going to have to create a method to reverse what you've done in order to make it work properly.
    5. Storing several fields within one text field is simply bad practice.  It's going to be very hard to support later on.
    6. There are already implementations that will allow you to convert your values to a string representation.  You may want to look into the XmlSerializer class instead.  This would be a much better option, if you insist on serializing your whole object out to string.

    Now, with these drawbacks, I strongly recommend you find another way to serialize your object out to string.  The method you're trying to do is going to cause you nothing but a headache later on, and is liable to be completely misunderstood by later developers (including yourself).  Do yourself a favor.  Create a new table in the database.  Store each field's value in the database one column per field.  This is the proper way to handle something like this.
    David Morton - http://blog.davemorton.net/
    • Marked As Answer by d_vm23 Friday, December 12, 2008 6:35 PM
    •  
  • Friday, December 12, 2008 4:47 PM
     
      Has Code
    Hi David,
    Thanks for helping me again!

    I am having trouble compiling this part:
         OrderBy(f => f.MetadataToken).ToArray();  


    In fact code Intillisense does not find "OrderBy" (C# 2.0, .NET 2.1)

    Am I missing a using clause?
    The IEnumerable I am passing in is a public class in this case, but yes, either would be best.

    In fact IEnumerable is a list of classes (in my case 12 items are in IEnumerable).
    Each class in the list has 8 fields (like: int fileType, bool includeSubDir, string pathRoot...)


    This is what I have so far.

            public static List<string> EnumerableToList<T>(IEnumerable<T> enumerable)  
            // converts a list or collection to a list of strings (List<String>).   
            // E.g.: Calling procedure:  BillingNo = EnumerableToList(lsBilling);  
            {  
                List<string> list = new List<string>();  
     
                if (enumerable != null)  
                {  
                    using (IEnumerator<T> enumerator = enumerable.GetEnumerator())  // loop through passed enumerator  
                    {  
                        while (enumerator.MoveNext())  
                        {  
                            // Get all fields from a class (private or public)  
                            Type t = enumerator.GetType();  
                            BindingFlags bFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;  
                            FieldInfo[] fi = t.GetFields(bFlags);  
                            foreach (FieldInfo field in fi)  
                            {  
                                object fieldfieldValue = field.GetValue(enumerable);  
                                if (fieldValue == null)  
                                    list.Add("Field Name=" + field.Name + ";");  
                                else  
                                    list.Add("Field Name=" + field.Name + ";" + fieldValue.ToString());  
                            }  
                        }  
                    } 
                }
           }
  • Friday, December 12, 2008 5:05 PM
    Moderator
     
      Has Code
    You can omit the OrderBy if you wish, as well as the ToArray().  It's a 3.5 LINQ construct, and what it does is order the fields by the MetaDataToken, so they're guaranteed to be in the same order as you wrote them into the code, however, chances are your call to get the FieldInfo will already.  If you need a 2.0 friendly method that will sort your FieldInfo result, pass the result of the GetFields method into the following method:

    public static void SortFieldInfoArray(FieldInfo[] fieldInfos)

    {

        List<FieldInfo> list = new List<FieldInfo>(fieldInfos);

       

        list.Sort(delegate(FieldInfo fi1, FieldInfo fi2) { return fi1.MetadataToken.CompareTo(fi2.MetadataToken); });

     

        for (int i = 0; i < fieldInfos.Length; i++)

        {

            fieldInfos[i] = list[i];

        }

    }


    David Morton - http://blog.davemorton.net/
  • Friday, December 12, 2008 6:35 PM
     
      Has Code
    Thanks David, 

    The code below works for me when I send a list of classes.
    I agree a database is the way to go with strongly typed data.
    But in this case I just needed simple logging.  The actual settings are stored in a database only once.
    I am logging many important web service settings/values sent out in memo fields (for now anyways)
     
            public static List<string> EnumerableToStringList<T>(IEnumerable<T> enumerable)  
            // converts a list or collection to a list of strings (List<String>).   
            // E.g.: Calling procedure:  BillingNo = MiscLists.Generics.EnumerableToStringList(lsProviders);  
            {  
                string sLine;  
                List<string> list = new List<string>();  
                List<string> sublist = new List<string>();  
     
                if (enumerable != null)  
                {  
                    using (IEnumerator<T> enumerator = enumerable.GetEnumerator())    
                    {  
                        while (enumerator.MoveNext())                               // loop through all items in list  
                        {  
                            sublist = ConvertToStringList(enumerator.Current);      // convert class to list of strings  
                            sLine = ListToString(sublist);                          // convert list of strings to semi-colon delimited string  
                            list.Add(sLine);  
                        }  
                    }  
                }  
                return list;  
            }  
              
            public static List<string> ConvertToStringList(object value)     
            // converts all fields of the object (classes even) to a string list  
            {     
                List<string> returnStringValues = new List<string>();     
                
                Type type = value.GetType();     
                BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;     
                FieldInfo[] fields = type.GetFields(bindingFlags);  // C# 3.0 you can for sorting: .OrderBy(f => f.MetadataToken).ToArray();     
                foreach (FieldInfo field in fields)     
                {     
                    object fieldfieldValue = field.GetValue(value);     
                    if (fieldValue == null)     
                        returnStringValues.Add("NULL");     
                    else    
                        returnStringValues.Add(fieldValue.ToString());     
                }     
                
                return returnStringValues;     
            }