Generic selectors
Exact matches only
Search in title
Search in content

c# – איך לבצע איטרציה על מאפייני מחלקה

C#
C#

כאשר אנו מעוניינים לבצע איטרציה על מאפייני מחלקה, אנו למעשה צריכים רשימה של כל המאפיינים, ואם ניצור אחת כזו באופן ידני, כנראה שהיא תגביל אותנו בביצוע אופרציות מסוימות שהיינו רוצים לבצע, אך כמובן שיש בנמצא פתרונות נוספים לבעיה זו ובמאמר זה נעבור על חלקם

האם נתקלתם במצבים שבהם רציתם לבצע איטרציה על כל המאפיינים של מחלקה על מנת לבצע אופרציה מסוימת, ומצאתם את עצמכם כותבים קוד מסורבל ולא מאוד קריא לשם ביצוע פעולה זו?
בשפת -C# יש מספר פתרונות לבעיות מסוג זה, כאשר לכל אחד מהם יתרונות וחסרונות אשר ישפיעו על קבלת ההחלטה באיזה פתרון עלינו להשתמש על מנת לפתור את הבעיה.

נתחיל מליצור את מחלקת Person שלה מאפיינים של שם פרטי, שם משפחה וגיל:

 public class Person
{
    //properties
    public string Name { get; private set; }
    public string LastName { get; private set; }
    public int Age { get; private set; }

    //ctor
    public Person(string name, string lastName, int age)
    {
        Name = name;
        LastName = lastName;
        Age = age;
    }
}

כעת ניצור מופע של מחלקת Person:

Person personJoe = new("Joe", "Moe", 25);

איטרציה על כל המאפיינים של מחלקה באמצעות Reflection

דרך יפה ונוחה לבצע איטרציה על מאפייני מחלקה היא באמצעות הספרייה Reflection:

using System.Reflection;

באמצעות-Reflection ניתן להשתמש על מנת להחזיר מידע על האובייקט בזמן ריצת התוכנית.
יש לזכור כי לרוב השימוש ב-Reflection היא פעולה "יקרה" מבחינת ניצול משאבי זיכרון בתוכנית שלנו,
אך באופן כללי – Reflection היא ספרייה חשובה ששווה להכיר אותה, לקריאה מורחבת באתר של מייקרוסופט יש ללחוץ כאן.

במקרה זה נשתמש באובייקט PropertyInfo ואת לולאת ה-foreach נכתוב כך שיודפס לנו שם מאפיין והערך שלו בכל איטרציה:

foreach (PropertyInfo prop in personJoe.GetType().GetProperties())
{
    Console.WriteLine($"{prop.Name}: {prop.GetValue(personJoe)}");
}

שימו לב ליכולות של PropertyInfo שבהם השתמשנו בלולאה:
Name מחזיר לנו את שם המאפיין, בעוד Value מחזיר לנו את הערך שהמאפיין מחזיק.

וכאשר נריץ את התוכנית נקבל את הפלט הבא:

איטרציה על מאפייני מחלקה
איטרציה על מאפייני מחלקה

איטרציה על מאפייני מחלקה באמצעות IEnumerable

דרך מאוד יפה שבאמצעותה ניתן לבצע איטרציה על מאפיינים של מחלקה היא באמצעות פונקציית איטרטור.
נייבא את System.Collections ונצהיר על מימוש האינטרפייס בראש המחלקה:

using System.Collections;

public class Person : IEnumerable<(string, object?)>

נממש את החוזה Ienumerable כך שהאינומרטור יחזיר לנו בכל אינטרוול את שם המאפיין והערך שהוא מחזיק.
כלומר, שמה שאנחנו צריכים זו פונקציית איטרטור שמחזירה לנו מעין צמדי – ValueTuple.
את המחלקה נוכל לכתוב כך:

public class Person: IEnumerable<(string, object?)>
{
    //properties
    public string Name { get; private set; }
    public string LastName { get; private set; }
    public int Age { get; private set; }
    //ctor
    public Person(string name, string lastName, int age)
    {
        Name = name;
        LastName = lastName;
        Age = age;
    }
    //Ienumerable implentation
    IEnumerator<(string, object?)> IEnumerable<(string, object?)>.GetEnumerator()
    {
        foreach (var prp in GetType().GetProperties())
        {
            yield return (prp.Name, prp.GetValue(this));
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

משום שכעת המחלקה מממשת את החוזה IEnumerable, נוכל לרוץ על המאפיינים בלולאת – foreach:

static void Main(string[] args)
{
    Person personJoe = new("Joe", "Moe", 25);

    foreach (var(name, value) in personJoe)
    {
        Console.WriteLine($"{name}: {value}");
    }
}

ואת הנתונים של האובייקט שלנו נוכל לקבל באמצעות – tuple deconstraction.
זוהי בעצם טכניקה לפירוק של מבני נתונים מסוג tuple, בחתימת לולאת ה- ForEach ניתן לראות כיצד זה מתבצע.

איטרציה באמצעות פונקציית הרחבה לאיטרטור

למען הסדר הנוחות וההכמסה ניצור פונקציית הרחבה לאיטרטור במחלקה סטאטית.
שימוש בדרך זו תגרום לכך שכל מחלקה תוכל להשתמש בפונקציית הרחבה זו,
מה שגם יגרום לקוד שלנו יהיה גנרי יותר:

public static class Extensions
{
    public static IEnumerator<(string, object?)> GetEnumerator(this object? obj)
    {
        if (obj == null)
        {
            yield break;
        }
        foreach (var prp in obj.GetType().GetProperties())
        {
            yield return (prp.Name, prp.GetValue(obj));
        }
    }
 }

את מחלקת Person נכתוב כפי שהייתה כתובה בתחילת המאמר, ללא מימוש של אף חוזה.
פונקציית האיטרטור תחזיר לנו מעין צמדי – ValueTuple שהם שם המאפיין והערך שהוא מחזיק.
כך שהלולאה מהפתרון הקודם תעבוד גם כאן:

static void Main(string[] args)
{
    Person personJoe = new("Joe", "Moe", 25);

    foreach (var(name, value) in personJoe)
    {
        Console.WriteLine($"{name}: {value}");
    }
}

סיכום

לסיכום ניתן לומר כי השימוש ב-Reflection הוא נוח וגורם לקוד שלנו להיות מאוד גנרי,
אך השימוש בו עלול להיות יקר מבחינת ניצול משאבי זיכרון,
כמו כן, יש לשים לב כי שימוש ב-Reflection עלול לגרום לשגיאות בזמן ריצה,
כך שלעתים הפתרון הטוב יהיה דווקא לעשות את זה ידנית, במקום השימוש היקר ב-Reflection.

רוצים לשתף את המדריך?

אהבתכם את המדריך? פתר לכם תקלה? הזמינו את כותב המדריך לכוס קפה

גולשים יקרים, רוב התכנים המוצגים באתר נכתבים בהתנדבות מלאה מתוך כוונה להנגיש מידע עבורכם. אם נתקלתם במדריך חינמי שפתר לכם תקלה או לימד אתכם משהו חדש שלא ידעתם, וברצונכם לתגמל את כותב המדריך או סתם להזמין אותו לכוס קפה, הינכם יותר ממוזמנים לתרום.

כתיבת תגובה

הזמינו אותי לכוס קפה
buy me coffee

אהבתכם את המדריך? פתר לכם תקלה? הזמינו את כותב המדריך לכוס קפה

גולשים יקרים, רוב התכנים המוצגים באתר נכתבים בהתנדבות מלאה מתוך כוונה להנגיש מידע עבורכם. אם נתקלתם במדריך חינמי שפתר לכם תקלה או לימד אתכם משהו חדש שלא ידעתם, וברצונכם לתגמל את כותב המדריך או סתם להזמין אותו לכוס קפה, הינכם יותר ממוזמנים לתרום.