سؤال

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;
}
هل كانت مفيدة؟

المحلول

// 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.

نصائح أخرى

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;
   }
}

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top