Bertrand Paul
Bertrand Paul

Reputation: 97

generic creation of a comma separated string list from a collection

I have the following code. The first line should be the comma separated headers string and the remaining should be the content of my collection layed out to be written to a comma separated file. How do i get the values out?

public static List<string> ToListOfStrings<T>(this List<T> items, string sep)
{
    var NewList = new List<string>();
    //string tempHeaderString = "";

    PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

    string s = string.Empty;
    foreach (var prop in props)
    {
        s += prop.Name + sep;

    }
    NewList.Add(s + System.Environment.NewLine);
    foreach (var item in items)
    {
        string s1 = string.Empty;
        var values = new object[props.Length];
        for (var i = 0; i < props.Length; i++)
        {
            s1 += (props[i].GetValue(item, null)).ToString() + sep;
        }

        NewList.Add(s1 + System.Environment.NewLine);
    }

    return NewList;
}

My target Object T only has strings as member properties.

Best

B

Upvotes: 2

Views: 753

Answers (3)

Andrey Tretyak
Andrey Tretyak

Reputation: 3231

As far as I understand from comment only problem in your code is string representation of property value. I've simplified code a bit and created special method where you can place logic to convert value to string, or you can just overload ToString() method for type if it's your types in properties.

    public static IEnumerable<string> ToListOfStrings<T>(this List<T> items, string sep)
    {
        var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        yield return properties.Select(p => p.Name).CreateRow(sep);

        foreach (var item in items)
        {
            yield return properties.Select(p => GetString(p.GetValue(item, null))).CreateRow(sep);
        }
    }

    private static string CreateRow(this IEnumerable<string> values, string separator)
    {
        return string.Join(separator, values) + System.Environment.NewLine;
    }

    public static string GetString(object obj)
    {
        //custom logic to create string from values
        return obj.ToString();
    }

Update:

According to you comment your also want to display properties of nested objects. this could be done like this:

    public static IEnumerable<string> ToListOfStrings<T>(this IEnumerable<T> items, string sep)
    {
        var properties = typeof(T).GetInstanceProperties();
        yield return properties.Select(p => p.Name).CreateRow(sep);

        foreach (var item in items)
        {
            yield return properties.GetValues(item, sep).CreateRow(sep);
        }
    }

    private static string CreateRow(this IEnumerable<string> values, string separator)
    {
        return values.JoinStrings(separator) + System.Environment.NewLine;
    }

    private static string JoinStrings(this IEnumerable<string> values, string separator)
    {
        return string.Join(separator, values);
    }

    public static PropertyInfo[] GetInstanceProperties(this Type type)
    {
        return type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
    }

    private static IEnumerable<string> GetValues(this IEnumerable<PropertyInfo> properties, object obj, string separator)
    {
        return properties.Select(p => GetString(p.GetValue(obj, null), separator));
    }

    public static string GetString(object obj, string separator)
    {
        var type = obj.GetType();
        if (type.IsPrimitive || type.IsEnum || type == typeof(string))
            return obj.ToString();

        var result = obj.GetType().GetInstanceProperties()
                        .GetValues(obj, separator)
                        .JoinStrings(separator);

        return string.Format("({0})", result);
    }

I've wrapped nested object in brackets to show hierarchy of values and preserve consistence of header row.

Upvotes: 0

Kuba Wyrostek
Kuba Wyrostek

Reputation: 6221

I have run your code and it works fine for such properties of T, which are of basic types such as string or int.

class Test
{
    public string FString { get; set; }
    public int FInt { get; set; }
}

/* ... */

List<Test> x = new List<Test>
{
    new Test {FString = "A", FInt = 10}, new Test {FString = "B", FInt = 20}
};

Output:

FString;FInt;
A;10;
B;20;

So there is no problem with the code itself.

What I understand from your comments there are some custom classes used as types of the fields in T (such as LINQPad.User.Header). As others have mentioned each class is solely responsible for representing itself as a string by overloading ToString(). Only a class can understand how to convert its content into meaningful (though usually stripped) string representation. The very default is to tell the name of the class. This obviously is not what you expect here.

You want to tell the compiler somehow how you want to represent LINQPad.User.Header as a string. The very basic option - if you are creator of this class is to replace default ToString() with custom implementation. Another solution would be to subclass LINQPad.User.Header and override ToString() in a subclass. Still not sure whether this is easily applied in your scenario. Yet another option is to recognize during CSV creation, that the object you are dealing with is a LINQPad.User.Header and write it out accordingly (by using its public fields i.e.).

Using Convert.ToString(props[i].GetValue(item,null)) is not an option since Convert also doesn't know how to represent a random class as a string.

Upvotes: 0

C.Evenhuis
C.Evenhuis

Reputation: 26446

Say you have the following object:

public class User
{
    public string Name { get; set; }
    public string Password { get; set; }
}

Then say you had the following piece of code:

User user = new User { Name = "Pete", Password = "Secret" };
Console.WriteLine(user.ToString());

It would output MyNamespace.User, MyAssembly. What would you expect it to output? Pete? Secret?

The framework cannot decide this for you, so you'll have to come up with your own solution. If you own the class, you can add your own ToString() implementation:

public override string ToString()
{
    return Name;
}

However if you do not own the code, you'll have to write a converter for each type separately, if you want any control over which property will provide the value for your CSV.

Upvotes: 1

Related Questions