Мне лично очень нравится ответ Люка. Единственная проблема состоит в том, чтобы определить статический класс 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; }
}