2010-09-01 66 views
3

我有一個數據類Student,並且我有一個聚合類Student。 學生有兩個字符串類型的屬性:名稱和城市。用反射產生迭代器

我想要做的是可以選擇使用foreach機制來迭代哪些屬性。

我寫的代碼很有作用,它也很好看。 主要問題是性能:我使用yield關鍵字的行可能不是非常有效,但問題是多少?這是戲劇性的表現嗎?

有沒有更好的方法來實現這個功能? (補充說:我不想讓別人修改返回的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; } 
} 
+0

你不能只拋出NotImplementedException! – leppie 2010-09-01 11:32:36

+6

好看?!?! – flq 2010-09-01 12:45:19

+0

這有幫助嗎? http://stackoverflow.com/questions/359495/how-to-make-ienumerablet-readonly – Odrade 2010-09-01 15:26:56

回答

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

我讓我的帖子更清晰。現在有解決方案嗎? – 2010-09-01 13:24:40

+0

謝謝! ....看起來像回答了所有的要求,但看起來很複雜,我需要更徹底地閱讀它。 – 2010-09-01 16:31:31

+0

有趣!看起來很乾淨,我敢說,很有趣! – wageoghe 2010-12-15 16:29:31

4

你可以使用拉姆達的做同樣的事情

PropertyToIterate會再採取Func<Student, object>,你可以這樣設置:?

Students.PropertyToIterate = student => student.City; 

的GetEnumerator可以使用LINQ可以實現這樣的:

return from student in m_Students select PropertyToIterate(student); 

還是不喜歡這個

foreach(var student in students) 
{ 
    yield return PropertyToIterate(student); 
} 
+0

我讓我的帖子更清晰。現在有解決方案嗎? – 2010-09-01 13:25:19

11

爲什麼不直接使用LINQ獲取屬性並保持學生的原始枚舉,所以你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); 
    } 
+0

+1:ogirinal解決方案看起來很可怕,沒有解釋。 – Grozz 2010-09-01 11:54:45

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>(); 
} 
+0

'yield return(string)property.GetValue(m_Students [i],null);'功能不正確;它不適用於'Age',它是一個'int'。您可能需要刪除轉換,或者調用'.ToString()',在這種情況下,您可以通過將枚舉類型更改爲'IEnumerator '來改進。 – Ani 2010-09-01 11:58:19

+0

@Ani:夠公平的。我只是轉載了OP的電話。現在編輯忽略類型轉換。 – kbrimington 2010-09-01 12:33:45

+0

據我瞭解,該對象的元數據已經包含類型信息,所以'GetType()'是一個非常非常小的性能命中。這已經說了,我沒有看到在這裏使用它的理由。 – 2010-09-01 12:47:31

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

我不想讓學生成爲一個結構。它不適合我的應用程序。 – 2010-09-01 16:29:18

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

+0

+1,爲了增加安全性,班級學生可以被標記爲內部,或者是學生的私人嵌套班級。這將防止向下轉換。 – TheFogger 2010-09-01 14:28:02

+0

我雖然關於接口解決方案,但像你說的,你可以向學生下達。 但隱藏學生對我不好。我希望能夠在應用程序的任何地方使用構造函數。 – 2010-09-01 16:28:19

2

我個人很喜歡LukeH的回答。唯一的問題是必須使用靜態只讀委託包裝對象而不是您的原始eStudentProperty枚舉定義靜態StudentProperty類。根據你的情況,這可能不容易被調用者使用。

該代碼的優點是每個StudentProperty<T>對象都根據其相關屬性進行強類型化,從而允許EnumerateBy方法返回強類型IEnumerable<T>

下面是我想到的。這與LukeH的答案非常相似,我提供了一個類似於LukeH的EnumerateBy方法的PropertyValues方法,儘管我的方法返回IEnumerable(非泛型)。 我的實現的一個問題是,如果你迭代一個值類型的屬性(如Age),那麼在枚舉器中將會出現一些裝箱。然而,如果消費者不太瞭解被迭代的財產以調用我提供的Ages方法而不是PropertyValues(eStudentProperty.Age),那麼無論我是否能夠返回IEnumerable<int>,在調用者的代碼中最有可能發生拳擊或IEnumerable。所以我會爭辯說,任何需要調用PropertyValues方法的人,因爲他們無法調用Names,CitiesAges方法,所以無法避免使用任何實現進行裝箱。

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