90. Anonymous Types

בעבר נתקלנו בכמה Featureים תכנותיים שיוצרים לנו מחלקות אנונימיות שלמות מאחורי הקלעים.

למשל, Anonymous Method שניגשת למשתנים לוקאליים של פונקציה, יוצרת מאחורי הקלעים מחלקה.

גם שימוש בyield return יוצר מאחורי הקלעים מחלקה.

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

השימוש הוא בצורה הבאה:

נוכל להגדיר משתנה לוקאלי של פונקציה כך:

1
2
3
4
5
6
var person =
new
{
FirstName = "Yali",
LastName = "Sobol",
};

השימוש בvarהוא הכרחי, משום שהטיפוס הוא אנונימי, ולכן אין לו שם.

כנ"ל השימוש בObject Initializer, שהרי הטיפוס אנונימי, ואנחנו בעצם מגדירים את המאפיינים שלו ביצירה.

נוכל גם לגשת לProperties שלו:

1
string firstName = person.FirstName;

קיים Feature נחמד – אם ניצור את הטיפוס עפ"י Properties או משתנים לוקאליים, הוא ידע לקבל את השמות שלהם לבד:

1
2
3
4
5
6
7
8
9
10
int Width = 3;
int Height = 5;
var box = new {Width, Height};
// Same as
var box = new
{
Width = Width,
Height = Height
};

נוצרת מאחורי הקלעים מחלקה עם קוד הדומה לקוד המופיע להלן (שיניתי את השמות, כדי שיהיה אפשר להבין יותר)

הקוד עצמו לא כל כך מעניין, אבל יש מספר דברים מעניינים בו:

  1. שימו לב שהשדות הם readonly, והאובייקט Immutable
  2. שימו לב שמחלקה גנרית
  3. שימו לב שלEquals יש מימוש לא טריוויאלי והוא ממומש באמצעות EqualityComparer (ראו גם טיפ 79)
  4. גם לGetHashCode יש מימוש לא טריוויאלי
  5. גם לToString יש מימוש טוב, והוא אפילו משתמש בStringBuilder!
  6. יש Attribute של DebuggerDisplay, שמאפשר לנו לראות בWatch את המחלקה בצורה נוחה לעין

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

נספח קוד (יש המשך אחריו):

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
[DebuggerDisplay(@"\{ FirstName = {FirstName}, LastName = {LastName} }", Type="<Anonymous Type>"), CompilerGenerated]
internal sealed class AnonymousType<TFirstName, TLastName>
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly TFirstName FirstNameField;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly TLastName LastNameField;
// Methods
[DebuggerHidden]
public AnonymousType(TFirstName FirstName, TLastName LastName)
{
this.FirstNameField = FirstName;
this.LastNameField = LastName;
}
[DebuggerHidden]
public override bool Equals(object value)
{
var type = value as AnonymousType<TFirstName, TLastName>;
return (((type != null) && EqualityComparer<TFirstName>.Default.Equals(this.FirstNameField, type.FirstNameField)) && EqualityComparer<TLastName>.Default.Equals(this.LastNameField, type.LastNameField));
}
[DebuggerHidden]
public override int GetHashCode()
{
int num = 0x31750700;
num = (-1521134295 * num) + EqualityComparer<TFirstName>.Default.GetHashCode(this.FirstNameField);
return ((-1521134295 * num) + EqualityComparer<TLastName>.Default.GetHashCode(this.LastNameField));
}
[DebuggerHidden]
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append("{ FirstName = ");
builder.Append(this.FirstNameField);
builder.Append(", LastName = ");
builder.Append(this.LastNameField);
builder.Append(" }");
return builder.ToString();
}
// Properties
public TFirstName FirstName
{
get
{
return this.FirstNameField;
}
}
public TLastName LastName
{
get
{
return this.LastNameField;
}
}
}

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

השימוש בAnonymous Types מוגבל רק לscope של המתודה בה אנחנו משתמשים בו. לא נוכל להשתמש מחוץ לScope של המתודה, לכן לא נרצה להחזיר אותו כערך פונקציה וכו’.

כמובן, גם לא נרצה לאתחל אותו כערך של Field של מחלקה שלנו.

השימוש העיקרי בAnonymous Types הוא בשימוש בLINQ, כפי שעוד נראה…

סופ"ש השלמות טוב

שתף