3

I'm trying to return a generic list after loading values from a file. However, after much fiddling with type manipulations I still can't get it to agree with me. The code is below; my questions are:

  1. Do I need to identify every key type like I'm starting to below, or is there a quicker way? I see the 'where T: ...' could be relevant here but I'd like to allow DateTime, int, string, double etc if possible and I can't see how to do those with 'where.'
  2. Why can't I add my DateTime item to the List which is of datetime?
  3. When I try to get the type (listType) this seems to go out of scope. Even when I declare the type in the line above where I use it, it says no such object exists.

Many thanks for your thoughts

public static List<T> FileToGenericList<T>(string FilePath, int ignoreFirstXLines = 0, bool stripQuotes = true)
{
    List<T> output = new List<T>();

    Type listType = output.GetType().GetGenericArguments()[0];

    try
    {
        using (StreamReader stream = new StreamReader(File.Open(FilePath, FileMode.Open)))
        {
            string line;
            int currentLine = 0;

            while ((line = stream.ReadLine()) != null)
            {
                // Skip first x lines
                if (currentLine < ignoreFirstXLines) continue;

                // Remove quotes if needed
                if (stripQuotes == true)
                {
                    line = line.Replace(@"""", @"");
                }

                // Q1 - DO I HAVE TO HAVE THIS FOR EACH TYPE OR IS THERE A QUICKER WAY
                if (listType == typeof(System.DateTime))
                {
                    DateTime val = new System.DateTime();
                    val = DateTime.Parse(line);

                    // Q2 ERROR: 'Argument type is not assignable to parameter type 'T''                    
                    output.Add(val);

                    // For some reason the type 'listType' from above is now out of scope when I try a cast
                    output.Add((listType)val);
                }
                if (listType == typeof(System.String))
                {
                    //DateTime val = new System.DateTime();
                    //val = DateTime.Parse(line);
                    //output.Add(val.ToString());
                }

                // Continue tracking for line skipping purposes
                currentLine++;
            }
        }
    }
    catch (Exception ex)
    {
        throw new Exception("Error - there was a problem reading the file at " + FilePath + ".  Error details: " + ex.Message);
    }    
    return output;
}
Jason Down
  • 21,731
  • 12
  • 83
  • 117
Glinkot
  • 2,924
  • 10
  • 42
  • 67
  • Also, check, how you iterate throught lines in file, if you want to skip any of them... – JleruOHeP Apr 23 '12 at 05:36
  • 1
    your ignore first X lines is not working because you need to increase current line before iterating to next line. – 000 Apr 23 '12 at 05:39
  • In the second line you could write Type listType = typeof(T), actually if I were writing this could instead of listType variable I would use typeof(T) in all places. – 000 Apr 23 '12 at 05:42

3 Answers3

3

Instead of coding your parsing logic into your FileToGenericList method, I think a cleaner and more flexible approach would be to refactor this out and pass it in as a lambda. Here is a quick console app that demonstrates this approach:

class Program
{
    static void Main(string[] args)
    {
        // second argument is a lambda that describes how to convert the line into the type you require
        var dateList = FileToGenericList<DateTime>("dates.txt", DateTime.Parse);
        var stringList = FileToGenericList<string>("strings.txt", s => s);
        var intList = FileToGenericList<int>("integers.txt", Int32.Parse); 

        Console.ReadLine();
    }

    static List<T> FileToGenericList<T>(string filePath, Func<string, T> parseFunc, int ignoreFirstXLines = 0, bool stripQuotes = true)
    {
        var output = new List<T>();

        try
        {
            using (StreamReader stream = new StreamReader(File.Open(filePath, FileMode.Open)))
            {
                string line;
                int currentLine = 0;

                while ((line = stream.ReadLine()) != null)
                {
                    // Skip first x lines
                    if (currentLine < ignoreFirstXLines)
                        continue;

                    // Remove quotes if needed
                    if (stripQuotes == true)
                        line = line.Replace(@"""", @"");

                    var parsedValue = parseFunc(line);
                    output.Add(parsedValue);
                    currentLine++;
                }
            }
        }
        catch (Exception ex)
        {
            throw new Exception("Error - there was a problem reading the file at " + FilePath + ".  Error details: " + ex.Message);
        }    
        return output;
   }
}
Mike Chamberlain
  • 39,692
  • 27
  • 110
  • 158
2

// Q1 - DO I HAVE TO HAVE THIS FOR EACH TYPE OR IS THERE A QUICKER WAY

Here is some test code to get you started:

using System;
using System.Collections.Generic;

namespace AddGenericToList
{
    class Program
    {
        static void Main(string[] args)
        {
            var tc = new ListClass<string>();

            tc.Add("a value");
            tc.Add(123);
            tc.Add(DateTime.Now);
        }
    }

    internal class ListClass<T>
    {
        private readonly List<T> list = new List<T>();

        public void Add(object value)
        {
            list.Add((T)Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof (T)) ?? typeof (T)));
        }
    }
}

However, invalid casts will throw an error. For instance, DateTime can be converted to string but not to int.

siride
  • 200,666
  • 4
  • 41
  • 62
Eben Roux
  • 12,983
  • 2
  • 27
  • 48
  • Hi Eben, that is fantastic! I've tested it with DateTime, int, double and string, all work fine with that one line you've got there. I agree there will be an error if (for example) we put floats in the input and we've defined it for int. It's great though. I might even think about how to adapt this to read a multi-field file into a tuple- that would be handy too! Cheers Eben – Glinkot Apr 23 '12 at 06:51
0

For your question #3: the reason you get an "out of scope" error is that you can't cast to a variable. Your output.Add((listType)val); is not a legal C# statement - you can only cast to an explicit type definition. Luckily, you don't need to do all your casting through the Type listType variable, since you have an explicit type definition - the T you got as a generic parameter. You can see the answer deep in @Pravin Pawar's answer: output.Add(val as T);, or better yet use the explicit cast syntax output.Add((T)val), since T isn't necessarily a reference type.

EDIT:

You're right that (T)val won't compile, since the compiler doesn't go the extra mile for us and decide that T is DateTime, despite the check we have earlier. So you can do this:

 (T)Convert.ChangeType(val, typeof(T)));

Which will convert your DateTime val to T (which is also DateTime), which is enough to satisfy the compiler.

Avner Shahar-Kashtan
  • 14,492
  • 3
  • 37
  • 63