139. Activator CreateInstance

עד כה ראינו מספר דרכים להשיג Type בזמן ריצה.

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

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

למשל, נניח שיש לנו מחלקה המייצגת צורה:

1
2
3
public abstract class Shape
{
}

עכשיו, כל אחד יכול לממש צורה בדרך שלו. אנחנו רוצים לאפשר ליצור צורה לפי השם שלה:

1
2
3
4
5
6
public static class ShapeFactory
{
public static Shape CreateByName(string name)
{
}
}

הדרך האינטואיטיבית היא לעשות משהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class ShapeFactory
{
public static Shape CreateByName(string name)
{
if (name == "Triangle")
{
return new Triangle();
}
else if (name == "Square")
{
return new Square();
}
else if (name == "Circle")
{
return new Circle();
}
return null;
}
}

או כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static class ShapeFactory
{
public static Shape CreateByName(string name)
{
switch (name)
{
case "Triangle":
{
return new Triangle();
}
case "Square":
{
return new Square();
}
case "Circle":
{
return new Circle();
}
default:
{
return null;
}
}
}
}

המימוש הזה נחמד, אבל יש איתו שתי בעיות עיקריות:

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

ראו גם טיפ 50.

כדי לפתור את זה, נוכל להשתמש בReflection כדי ליצור instance של צורה לפי הType שלה באופן דינאמי.

(נוכל למשל, להשתמש בType.GetType כדי להשיג את הType של הצורה לפי השם שלו)

כדי ליצור instance חדש לפי Type, קיימת הפונקציה הסטטית Activator.CreateInstance.

הOverload הכי פשוט שלה מקבל Type ופרמטרים, וקורא לConstructor הכי מתאים של הType עם הפרמטרים.

נוכל להשתמש בו למשל כדי לממש את הShapeFactory:

1
2
3
4
5
6
7
8
9
public static class ShapeFactory
{
public static Shape CreateByName(string name)
{
// This isn't accurate
Type givenType = Type.GetType(name);
return (Shape)Activator.CreateInstance(givenType);
}
}

ככה יצרנו בעצם צורה בהינתן השם שלה בזמן ריצה.

(שימו לב שהדרך שבה אנחנו משיגים את הType היא לא מדויקת, כיוון שצריך גם לציין את הNamespace (ולעתים גם את הAssemblyQualifiedName).)

נוכל גם ליצור instance של טיפוס שמקבל בConstructor שלו פרמטרים, למשל:

השורה הבאה:

1
2
Person meir =
(Person) Activator.CreateInstance(typeof (Person), "Meir", "Ariel");

מבצעת בסופו של דבר קריאה לConstructor כמו השורה הזאת:

1
Person meir = new Person("Meir", "Ariel");

הדבר ממש מגניב, אבל לכל דבר יש מחיר.

המחיר כאן הוא בביצועים – יצירת אובייקט בצורה דינמית באמצעות הפונקציהActivator.CreateInstance מוסיף מספר מילישניות לזמן שלוקח ליצור את האובייקט. זה די הרבה באופן יחסי, מאחר והCLR מאוד מהיר ביצירת אובייקטים (קריאה רגילה לnew לוקחת בערך 10 נאנו שניות).

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

נראה בעתיד דרכים יותר יעילות לעשות דברים דומים..

המשך יום טיפוסי

שתף