0

How can I access to the properties of List<T>,where T every time is another type of object, Student, Product, etc.? The problem is that at the compile time C# can't recognize the properties which are included in the T. As you can see (as it's commented in the code) I make foreach, where after that when I try itemList, the properties from the object aren't shown.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StudentLinq {

    public class Student {
        public int Id {
            get;
            set;
        }
    }

    public class Admin {
        public string Name {
            get;
            set;
        }
    }

    public class Generic {
        public void Print<T>(List<T> list) {
            foreach(var itemList in list) {
                if (list is List<Student>) {
                    Console.WriteLine(itemList);
                }
                //How can I print the properties from the product class and student 
            }
        }
    }

    class Program {
        static void Main(string[] args) {
            var studentsList = new List<Student>();
            studentsList.Add(new Student {
                Id = 2
            });

            var productList = new List<Product>();
            productList.Add(new Product {
                Price = 40
            });

            var generic = new Generic();

            generic.Print<Student>(studentsList);
            generic.Print<Product>(productList);

            Console.ReadLine();
        }
    }
}
2
  • I'd like to know if you solved this? And which of the answers given below helped you?
    – prinkpan
    Commented Apr 10, 2019 at 9:11
  • All of them were very good answers.Thank you guys
    – Code
    Commented Apr 12, 2019 at 7:59

5 Answers 5

3

What generics are for

The idea behind generics is that they do the same thing to everything you pass to it. For example, it is possible to write a generic function to take the count of any type of list, because all lists can be counted.

int GetCount<T>(List<T> source)
{
    return source.Count();
}

What generics are not for

If you have to do different things to different types, generics is not the right answer. The typical way to deal with it is with different functions, each of which accepts the type that matches its logic. The functions can have the same name if you want (this is known as method overloading).

void Print(List<Student> list)
{
    foreach(var item in list)
    {
        Console.WriteLine("{0}", item.Id);
    }
}

void Print(List<Admin> list)
{
    foreach(var item in list)
    {
        Console.WriteLine("{0}", item.Name);
    }
}

How to make a method generic for more than one class

If you happen to discover that some of your objects have properties in common, you can give them a common interface that contains the common property or properties:

interface IHasName
{
    string Name { get; }
}

class Admin : IHasName
{
    public string Name { get; set; }
}

class Student: IHasName
{
    public string Name { get; set; }
}

How to make your properties appear

Once you have the common interface, you can write a generic method with a type constraint, and your properties will suddenly be available to you. Only those properties that are guaranteed to exist by the type constraint will be available; this makes the generic method type-safe.

public void Print<T>(List<T> source) where T : IHasName
{
    foreach (var item in source)
    {
        Console.WriteLine("{0}", item.Name);
    }
}

Another way to handle the problem

Another way to deal with this sort of problem is to provide a generic callback.

void Print<T>(List<T> source, Func<T,string> callback)
{
    foreach (var item in source)
    {
        Console.WriteLine("{0}", callback(item));
    }
}

Then call it like this:

var list = new List<Student>();
Print(list, item => item.Id.ToString());

var list2 = new List<Admin>();
Print(list2, item => item.Name);

This works because the logic that accesses the properties is contained in your callback and not in the generic method, thus sidestepping the issue.

1

If your objects have nothing in common and you do not want to use Polymorphism then by all means use System.Reflection:

public void Print<T>(List<T> list)
{
    foreach (var itemList in list)
    {
        var properties = typeof(T).GetProperties();
        var values = properties.Select(x => x.GetValue(itemList));
        Console.WriteLine(string.Join(",", values));
    }
}

You do not need to define the method as generic anymore.

5
  • 1
    If the source is List<T> you can't pass it as List<object>. Commented Apr 7, 2019 at 6:29
  • I assumed he could choose the signature of the method and just pointed out that it need not be generic anymore if using reflection. Commented Apr 7, 2019 at 6:33
  • 1
    You just made it much harder by not being generic. Two things - your code doesn't compile, but it's a minor issue - and rather than itemList.GetType() it might be worth doing typeof(T) so that you ensure a consistent output as the real type might be derived from T. Commented Apr 7, 2019 at 6:35
  • You are right indeed.typeof is the way to go if it is generic Commented Apr 7, 2019 at 6:38
  • 1
    You still have a syntax error. Did you test your code before posting? Commented Apr 7, 2019 at 6:41
0

John Wu's answer provides some great advice - In this case it's worth noting that you don't need to use generics to do exactly what you want, Just use the non-generic interface and interact with them as object instances :

    static void Print(IEnumerable data)
    {

        foreach (var item in data)
        {
            switch (item)
            {
                case String str:
                    Console.WriteLine(str);
                    break;
                default:
                    throw new NotSupportedException();
            }
        }
    }
-1

I'd suggest overwriting the ToString()-Method in your classes:

public class Student
{
    public int Id { get; set; }
    public override string ToString()
    {
        return Id?.ToString();
    }
}

public class Admin
{
    public string Name { get; set; }
    public override string ToString()
    {
        return Name;
    } 
}

public class Generic
{
    public void Print<T>(List<T> list)
    {
        foreach (var item in list)
        {
            Console.WriteLine(item); // Implicitly calls ToString()
        }
    }
}
4
  • I'd appreciate if the downvoter could elaborate on the problem with my answer. Did I get something wrong? Commented Apr 6, 2019 at 20:42
  • Apologies did not see your override to ToString() I have revoked downvote. However, there is one problem though in this approach, it does not give an individual access to properties if there are multiple properties in the class. We will have to resort to some string manipulations.
    – prinkpan
    Commented Apr 6, 2019 at 20:49
  • @PriyankPanchal Thank you. You are correct. But as the question is asked, it can not be deferred what exactly should be output. Therefore I'd argue, that all 3 answers have some merit, depending on what the OP wants exactly Commented Apr 6, 2019 at 20:51
  • "it can not be deferred [sic] what exactly should be output" -- frankly, given that's the case, I'd argue that none of the three answers have merit, because the question should not be answered until it can be inferred what exactly should be the output. Commented Apr 6, 2019 at 21:47
-1

You can go with this option if your list of generic classes is limited. I would recommend @Bercovici Adrian's answer over this one. Anyways, I will give another way of doing it.

You can use the c# as operator to convert the list into its non-generic member after finding out its correct type using if

public class Generic
    {
        public void Print<T>(List<T> list)
        {
            if(list is List<Student>)
            {
                var studentList = list as List<Student>;
                foreach (var itemList in studentList)
                {
                    Console.WriteLine(itemList.Id);
                }
            }
            else if(list is List<Product>)
            {
                var productList = list as List<Product>;
                foreach(var itemList in productList)
                {
                    Console.WriteLine(itemList.Price);
                }
            }
        }
    }
1
  • The OP is using Student and Product as examples; they want a general purpose approach. Commented Apr 7, 2019 at 6:31

Not the answer you're looking for? Browse other questions tagged or ask your own question.