带反射的产量迭代器

发布于 2024-09-17 09:26:16 字数 2180 浏览 6 评论 0原文

我有一个数据类学生,我有一个聚合类学生。 Student 有两个字符串类型的属性: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; }
}

I have a data class Student, and I have an aggregate class Students.
Student has two properties of type string : Name and City.

what I want to do is have the option to choose what property to iterate using the foreach mechanism.

The code I wrote works and it's also readable and nice-looking.
The main issue is performance : the line in which I use the yield keyword is probably not very efficient , but the question is how much ? is it dramatic performance hit ?

Is there a better way to achieve this functionality?
(added: i don't want to allow someone to modify the returning Student objects, so all the Linq solutions proposed aren't good here. To make it clearer , I want:
property iteration + foreach mechanism integration + Student class and the list of students are readonly.
how can i achieve that ?)

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

流殇 2024-09-24 09:27:13

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

if student can be a struct then that will handle the readonly part of the student item. Otherwise, just make the properties of the Student class set in the constructor and remove the public set.

edit: okay, no struct. changing Student to a class

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);
        }
    } 
}
简单气质女生网名 2024-09-24 09:27:06

添加一个接口怎么样?

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 对象无法更改。

What 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 could use the LINQ solution and the Student objects can not be altered.

两个我 2024-09-24 09:27:00

由于每次迭代都重复反映类型,您可能会遇到性能损失。为了避免不必要地调用 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>();
}

You may be experiencing performance loss due to repeatedly reflecting the type every iteration. To avoid unnecessary calls to GetType(), pull it out of the loop. Also, since the type is known (e.g., Student), you can gain some compile time efficiency using 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);
    }            
}

On an aside, you can satisfy the non-generic GetEnumerator() by returning the generic version.

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator<object>();
}
懷念過去 2024-09-24 09:26:53

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

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

以下是我的想法。它与 LukeH 的答案非常相似,因为我提供了一个类似于 LukeH 的 EnumerateBy 方法的 PropertyValues 方法,尽管我的方法返回一个 IEnumerable (非-通用的)。
我的实现的一个问题是,如果您正在迭代值类型属性(例如 Age),那么枚举器中将会发生一些装箱。但是,如果消费者对正在迭代的属性不够了解,无法调用我提供的 Ages 方法而不是 PropertyValues(eStudentProperty.Age),则装箱无论我是否能够返回 IEnumerable 还是 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; }
}

I personally like LukeH's answer quite a bit. The only issue is having to define the static StudentProperty class with static readonly delegate wrapper objects rather than your original eStudentProperty enum. Depending on your situation, this might not be easily usable by the caller.

The advantage of that code is that each StudentProperty<T> object is strongly typed according to its associated property, allowing the EnumerateBy method to return a strongly typed IEnumerable<T>.

Below is what I came up with. It's very similar to LukeH's answer in that I've provided a PropertyValues method similar to LukeH's EnumerateBy method, although my method returns an IEnumerable (non-generic).
An issue with my implementation is that if you are iterating over a value-type property (such as Age), then there will be some boxing going on within the enumerator. However, if the consumer doesn't know enough about the property being iterated to call the Ages method that I've provided rather than PropertyValues(eStudentProperty.Age), then boxing will most likely occur in the caller's code regardless of whether I'm able to return an IEnumerable<int> or an IEnumerable. So I would argue that anyone who needs to call the PropertyValues method because they cannot call the Names, Cities, or Ages methods will not be able to avoid boxing with any implementation.

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; }
}
我家小可爱 2024-09-24 09:26:44

您可以使用 lambda 执行类似的操作

PropertyToIterate 然后采用 Func 您可以这样设置:

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

You could use lambda's to do something similar

PropertyToIterate would then take a Func<Student, object> that you can set this way:

Students.PropertyToIterate = student => student.City;

GetEnumerator can be implemented with linq like this:

return from student in m_Students select PropertyToIterate(student);

Or without linq like this

foreach(var student in students)
{
  yield return PropertyToIterate(student);
}
后来的我们 2024-09-24 09:26:40

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

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

In response to your edit, how about something like this...

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;
    }
}
送舟行 2024-09-24 09:26:35

为什么不简单地使用 Linq 获取属性并保留学生的原始枚举,以便您可以迭代 Students 类中的所有学生。

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

Why not simply get the properties with Linq and keep the original Enumeration of the students so you can iterate all the 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);
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文