вывести итератор с отражением - PullRequest
3 голосов
/ 01 сентября 2010

У меня есть класс данных «Студент», и у меня есть совокупный класс «Студенты».У ученика есть два свойства типа string: Name и City.

Я хочу выбрать, какое свойство итерировать, используя механизм foreach.

Код, который я написал, работает иэто также читабельно и красиво.Основная проблема - производительность: строка, в которой я использую ключевое слово yield, вероятно, не очень эффективна, но вопрос в том, сколько?это впечатляющий удар по производительности?

Есть ли лучший способ достичь этой функциональности?(добавлено: я не хочу позволять кому-либо изменять возвращаемые объекты Student, поэтому все предложенные решения Linq здесь не годятся. Чтобы сделать его более понятным, я хочу:
итерация свойства + интеграция с механизмом foreach + класс Studentи список студентов только для чтения. Как я могу этого достичь?)

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

Ответы [ 7 ]

11 голосов
/ 01 сентября 2010

Почему бы просто не получить свойства с помощью Linq и сохранить исходное Перечисление учеников, чтобы вы могли выполнить итерацию всех учеников в классе учеников.

    foreach (string studentCity in students.Select(s => s.City))
    {
        Console.WriteLine(studentcity);
    }
    ...
    foreach (string studentName in students.Select(s => s.Name))
    {
        Console.WriteLine(studentName);
    }
7 голосов
/ 01 сентября 2010

В ответ на ваши изменения, как на счет этого ...

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;
    }
}
4 голосов
/ 01 сентября 2010

Вы можете использовать лямбду, чтобы сделать что-то подобное

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);
}
2 голосов
/ 02 сентября 2010

Мне лично очень нравится ответ Люка. Единственная проблема состоит в том, чтобы определить статический класс StudentProperty со статическими объектами-оболочками делегата readonly, а не с исходным перечислением eStudentProperty. В зависимости от вашей ситуации, вызывающая сторона может не легко ее использовать.

Преимущество этого кода в том, что каждый объект StudentProperty<T> строго типизирован в соответствии со своим связанным свойством, что позволяет методу EnumerateBy возвращать строго типизированный IEnumerable<T>.

Вот то, что я придумал. Это очень похоже на ответ LukeH в том, что я предоставил метод PropertyValues, аналогичный методу EnumerateBy LukeH, хотя мой метод возвращает 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; }
}
1 голос
/ 01 сентября 2010

А как насчет добавления интерфейса?

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

public class Student : IStudent
{
    ...
}

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

Тогда вы можете использовать решение LINQ, и объекты Student не могут быть изменены.

1 голос
/ 01 сентября 2010

если ученик может быть структурой, то он будет обрабатывать только для чтения часть ученика. В противном случае просто задайте свойства класса Student в конструкторе и удалите открытый набор.

редактировать: хорошо, без структуры. изменение ученика на класс

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 голос
/ 01 сентября 2010

Возможно, вы испытываете потерю производительности из-за многократного отображения типа на каждой итерации. Чтобы избежать ненужных вызовов на 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>();
}
...