代码之家  ›  专栏  ›  技术社区  ›  Yaron Levi

带反射的屈服迭代器

  •  3
  • Yaron Levi  · 技术社区  · 15 年前

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

    我写的代码工作,它也可读性和好看。 主要的问题是性能:我使用yield关键字的行可能效率不高,但问题是效率有多高?是戏剧表演吗?

    有没有更好的方法来实现这个功能? (补充:我不想允许任何人修改returning Student对象,所以这里提出的所有Linq解决方案都不好。更清楚地说,我想:
    属性迭代+foreach机制集成+学生类和学生列表为只读。 我怎样才能做到呢?)

    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 回复  |  直到 15 年前
        1
  •  7
  •   LukeH    15 年前

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

    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
  •  11
  •   Torbjörn Hansson    15 年前

    为什么不简单地用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);
        }
    
        3
  •  4
  •   Mendelt    15 年前

    你可以用lambda做类似的事情

    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
  •  2
  •   Dr. Wily's Apprentice    15 年前

    我个人很喜欢卢克的回答。唯一的问题是必须使用静态只读委托包装器对象定义静态StudentProperty类,而不是使用原始的eStudentProperty枚举。根据您的情况,这可能不容易被调用方使用。

    该代码的优点是 StudentProperty<T> 对象根据其关联属性是强类型的,从而允许EnumeratBy方法返回强类型 IEnumerable<T>

    PropertyValues EnumerateBy 方法,尽管我的方法返回 IEnumerable (非通用)。 我的实现的一个问题是,如果对值类型属性(如Age)进行迭代,则枚举器中会出现一些装箱。但是,如果使用者对正在迭代的属性了解不够,无法调用 Ages PropertyValues(eStudentProperty.Age) ,则无论是否能够返回 IEnumerable<int> I可数 属性值 Names , Cities ,或 方法将无法避免任何实现的装箱。

    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; }
    }
    
        5
  •  1
  •   kbrimington    15 年前

    由于每次迭代都重复反映类型,您可能会遇到性能损失。避免不必要的呼叫 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>();
    }
    
        6
  •  1
  •   Jeroen    15 年前

    添加一个接口怎么样?

    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解决方案,并且不能更改学生对象。

        7
  •  1
  •   internet man    15 年前

    如果student可以是一个struct,那么它将处理student项的readonly部分。否则,只需在构造函数中设置Student类的属性并移除public集。

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