Reflection iterator

I have a Student data class and I have a cool Students class. The student has two type string properties: Name and City.

what I want to do is be able to choose which iteration property to use using the foreach mechanism.

The code I wrote works, and it is also readable and enjoyable. The main problem is performance: the line where I use the yield keyword is probably not very efficient, but the question is how much? is it a dramatic success?

Is there a better way to achieve this functionality? (Added: I don’t want to let anyone modify the returned Student objects, so all the solutions suggested by Linq are not very good here. To make it clearer I want:
Property hierarchy + integration of the foreach mechanism + Student class and student list - read-only . How can I achieve this?)

static void Main(string[] args)
    {           
        Students students = new Students();

        students.AddStudent(new Student { Age = 20, Name = "Stud1" , City="City1" });
        students.AddStudent(new Student { Age = 46, Name = "Stud2" , City="City2"});
        students.AddStudent(new Student { Age = 32, Name = "Stud3" , City="City3" });
        students.AddStudent(new Student { Age = 34, Name = "Stud4" , City="city4" });

        students.PropertyToIterate = eStudentProperty.City;
        foreach (string studentCity in students)
        {
            Console.WriteLine(studentcity);
        }

        students.PropertyToIterate = eStudentProperty.Name;
        foreach (string studentName in students)
        {
            Console.WriteLine(studentName);
        }

    }

public class Students :IEnumerable<object>
{
    private List<Student> m_Students = new List<Student>();

    private eStudentProperty m_PropertyToIterate = eStudentProperty.Name;

    public eStudentProperty PropertyToIterate
    {
        get { return m_PropertyToIterate; }
        set { m_PropertyToIterate = value; }
    }

    public void AddStudent(Student i_Student)
    {
        m_Students.Add(i_Student);
    }

    public IEnumerator<object> GetEnumerator()
    {            
        for (int i = 0; i < m_Students.Count; ++i)
        {
            yield return (object)m_Students[i].GetType().GetProperty(PropertyToIterate.ToString()).GetValue(m_Students[i], null);
        }            
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

public enum eStudentProperty
{
    Name,
    Age,
    City
} 

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

    public string City { get; set; }

    public int Age { get; set; }
}
+3
source share
7 answers

In response to your editing, how about something like that ...

Students students = new Students();
students.AddStudent(new Student { Age = 20, Name = "Stud1", City = "City1" });
students.AddStudent(new Student { Age = 46, Name = "Stud2", City = "City2" });
students.AddStudent(new Student { Age = 32, Name = "Stud3", City = "City3" });
students.AddStudent(new Student { Age = 34, Name = "Stud4", City = "city4" });

foreach (int studentAge in students.EnumerateBy(StudentProperty.Age))
{
    Console.WriteLine(studentAge);
}

foreach (string studentName in students.EnumerateBy(StudentProperty.Name))
{
    Console.WriteLine(studentName);
}

foreach (string studentCity in students.EnumerateBy(StudentProperty.City))
{
    Console.WriteLine(studentCity);
}

// ...

public class Students
{
    private List<Student> _students = new List<Student>();

    public void AddStudent(Student student)
    {
        _students.Add(student);
    }

    public IEnumerable<T> EnumerateBy<T>(StudentProperty<T> property)
    {
        return _students.Select(property.Selector);
    }
}

public static class StudentProperty
{
    public static readonly StudentProperty<int> Age =
        new StudentProperty<int>(s => s.Age);

    public static readonly StudentProperty<string> Name =
        new StudentProperty<string>(s => s.Name);

    public static readonly StudentProperty<string> City =
        new StudentProperty<string>(s => s.City);
}

public sealed class StudentProperty<T>
{
    internal Func<Student, T> Selector { get; private set; }

    internal StudentProperty(Func<Student, T> selector)
    {
        Selector = selector;
    }
}
+7
source

Why not just get the properties with Linq and keep the original students enumerated so that you can repeat all students in the Students class.

    foreach (string studentCity in students.Select(s => s.City))
    {
        Console.WriteLine(studentcity);
    }
    ...
    foreach (string studentName in students.Select(s => s.Name))
    {
        Console.WriteLine(studentName);
    }
+11
source

, -

PropertyToIterate Func<Student, object>, :

Students.PropertyToIterate = student => student.City;

GetEnumerator linq :

return from student in m_Students select PropertyToIterate(student);

linq,

foreach(var student in students)
{
  yield return PropertyToIterate(student);
}
+4

LukeH . , StudentProperty , eStudentProperty. .

, StudentProperty<T> , EnumerateBy IEnumerable<T>.

. LukeH , PropertyValues, LukeH EnumerateBy, IEnumerable ( ). , (, Age), . , , , , Ages, , PropertyValues(eStudentProperty.Age), , , , , IEnumerable<int> IEnumerable. , , PropertyValues, Names, Cities Ages, .

class Program
{
    static void Main(string[] args)
    {
        Students students = new Students();

        students.AddStudent(new Student { Age = 20, Name = "Stud1", City = "City1" });
        students.AddStudent(new Student { Age = 46, Name = "Stud2", City = "City2" });
        students.AddStudent(new Student { Age = 32, Name = "Stud3", City = "City3" });
        students.AddStudent(new Student { Age = 34, Name = "Stud4", City = "city4" });

        // in these two examples, you know exactly which property you want to iterate,
        // so call the corresponding iterator method directly.
        foreach (string studentCity in students.Cities())
        {
            Console.WriteLine(studentCity);
        }

        foreach (string studentName in students.Names())
        {
            Console.WriteLine(studentName);
        }

        // in these three cases, the DoSomethingWithPropertyValues method will not know which property is being iterated,
        // so it will have to use the PropertyValues method
        DoSomethingWithPropertyValues(students, eStudentProperty.Age);
        DoSomethingWithPropertyValues(students, eStudentProperty.Name);
        DoSomethingWithPropertyValues(students, eStudentProperty.City);
    }

    static void DoSomethingWithPropertyValues(Students students, eStudentProperty propertyToIterate)
    {
        // This method demonstrates use of the Students.PropertyValues method.
        // The property being iterated is determined by the propertyToIterate parameter,
        // therefore, this method cannot know which specific iterator method to call.
        // It will use the PropertyValues method instead.
        Console.WriteLine("Outputting values for the {0} property.", propertyToIterate);
        int index = 0;
        foreach (object value in students.PropertyValues(propertyToIterate))
        {
            Console.WriteLine("{0}: {1}", index++, value);
        }
    }
}

public class Students
{
    private List<Student> m_Students = new List<Student>();

    public void AddStudent(Student i_Student)
    {
        m_Students.Add(i_Student);
    }

    public IEnumerable PropertyValues(eStudentProperty property)
    {
        switch (property)
        {
            case eStudentProperty.Name:
                return this.Names();
            case eStudentProperty.City:
                return this.Cities();
            case eStudentProperty.Age:
                return this.Ages();
            default:
                throw new ArgumentOutOfRangeException("property");
        }
    }

    public IEnumerable<string> Names()
    {
        return m_Students.Select(s => s.Name);
    }

    public IEnumerable<string> Cities()
    {
        return m_Students.Select(s => s.City);
    }

    public IEnumerable<int> Ages()
    {
        return m_Students.Select(s => s.Age);
    }
}

public enum eStudentProperty
{
    Name,
    Age,
    City
}

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

    public string City { get; set; }

    public int Age { get; set; }
}
+2

- . GetType(), . , (, Student), , typeof.

public IEnumerator<object> GetEnumerator()
{    
    var property = typeof(Student).GetProperty(PropertyToIterate.ToString());
    for (int i = 0; i < m_Students.Count; ++i)
    {
        yield return property.GetValue(m_Students[i], null);
    }            
}

GetEnumerator(), .

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator<object>();
}
+1

, . Student .

edit: okay, no struct.

public class Students : ReadOnlyCollection<Student>
{
    public Students(IList<Student> students) : base(students)
    {}

    public IEnumerable<string> Names
    {
        get { return this.Select(x => x.Name); }
    }

    public IEnumerable<string> Cities
    {
        get { return this.Select(x => x.City); }
    }
}

public class Student 
{
              public Student(string name, string city, int age)
              {
                  this.Name = name;
                  this.City = city;
                  this.Age = age;
              }
    public string Name { get; private set; } 

    public string City { get; private set; } 

    public int Age { get; private set; } 
}

class Program
{
    static void Main(string[] args)
    {
        List<Student> students = new List<Student>();
        students.Add(new Student("Stud1", "City1",20));
        students.Add(new Student("Stud2", "City2",46));
        students.Add(new Student("Stud3", "City3",66));
        students.Add(new Student("Stud4","city4", 34));


        Students readOnlyStudents = new Students(students);

        foreach (string studentCity in readOnlyStudents.Cities)
        {
            Console.WriteLine(studentCity);
        }

        foreach (string studentName in readOnlyStudents.Names)
        {
            Console.WriteLine(studentName);
        }
    } 
}
+1

How about adding an interface?

public interface IStudent
{
    public string Name { get; }
    public string City { get; }
    public int Age { get; }
}

public class Student : IStudent
{
    ...
}

public class Students : IEnumerable<IStudent>
{
    ...
}

Then you can use the LINQ solution, and Student objects cannot be modified.

+1
source

Source: https://habr.com/ru/post/1762633/


All Articles