用反射产生迭代器

时间:2010-09-01 11:18:41

标签: c# reflection iterator yield

我有一个数据类学生,我有一个聚合课程学生。 Student有两个类型为string的属性:Name和City。

我想要做的是选择使用foreach机制来选择要迭代的属性。

我编写的代码有效,而且可读性也很漂亮。 主要问题是性能:我使用yield关键字的行可能不是很有效,但问题是多少?这是戏剧性的表现吗?

有没有更好的方法来实现此功能? (补充:我不想让某人修改返回的学生对象,因此所提出的所有Linq解决方案都不是很好。为了更清楚,我想要:
属性迭代+ foreach机制集成+学生类和学生列表是readonly。 我怎么能实现这个目标?)

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 个答案:

答案 0 :(得分:11)

为什么不简单地使用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);
    }

答案 1 :(得分:7)

为了回应你的编辑,这样的事情怎么样......

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

答案 2 :(得分:4)

您可以使用lambda来做类似的事情

然后

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

答案 3 :(得分:2)

我个人非常喜欢LukeH的回答。唯一的问题是必须使用静态只读委托包装器对象而不是原始的eStudentProperty枚举来定义静态StudentProperty类。根据您的具体情况,调用者可能无法轻松使用它。

该代码的优点是每个StudentProperty<T>对象都根据其关联属性进行强类型化,允许EnumerateBy方法返回强类型IEnumerable<T>

以下是我的想法。它与LukeH的答案非常相似,因为我提供了类似于LukeH的PropertyValues方法的EnumerateBy方法,尽管我的方法返回IEnumerable(非泛型)。 我的实现的一个问题是,如果你正在迭代一个值类型的属性(如Age),那么枚举器中会有一些装箱。但是,如果消费者对迭代的属性没有足够的了解来调用我提供的Ages方法而不是PropertyValues(eStudentProperty.Age),那么无论是否调用,最有可能在调用者的代码中进行装箱。我可以返回IEnumerable<int>IEnumerable。所以我认为,任何需要调用PropertyValues方法因为无法调用NamesCitiesAges方法的人都无法避免任何拳击实施

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

答案 4 :(得分:1)

由于每次迭代都会反复反映类型,因此可能会遇到性能下降。为避免对GetType()进行不必要的调用,请将其拉​​出循环。此外,由于类型是已知的(例如,学生),您可以使用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>();
}

答案 5 :(得分:1)

如果学生可以成为结构,那么将处理学生项目的只读部分。否则,只需在构造函数中设置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);
        }
    } 
}

答案 6 :(得分:1)

添加界面怎么样?

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对象。