220. extern alias

Namespace, כידוע, הוא תחום בו לכל טיפוס יש שם ייחודי.

עם זאת, Namespace אינו חייב להתפרס על Assembly אחד, אלא יכול להתפרס על מספר Assemblyים.

למשל, אנחנו יכולים להוסיף בAssembly שלנו מחלקות משלנו לNamespace ששמו System, והכל יתקמפל ויעבוד…

אלא, שגם מישהו אחר יכול לעשות דבר כזה.

מה שיכול לקרות זה ששני Assemblyים יגדירו אותו שם של Type באותו שם של Namespace. הדבר הזה יגרום לתוכנה שלנו לא להתקמפל, במידה ונחזיק Reference לשני הAssemblyים וננסה להשתמש בטיפוס המשותף:

נקבל את שגיאת הקימפול:

The type ‘CoolNamepsace.CoolClass’ exists in both ‘CoolDLL.dll’ and ‘CoolDLL2.dll’


טוב אז מה עושים?

התשובה הנכונה היא להשתמש בNamespace ייחודי עבור כל Assembly (עדיף שיהיה זהה לשם הפרויקט), ולא להשתמש באותו Namespace משני Assemblyים שונים.

ומה אם אנחנו בכל זאת מתעקשים לעשות משהו כזה? (כי למשל, יש לנו שתי גרסאות שונות של אותו DLL, ואנחנו רוצים להשתמש בשתיהן)

נוכל להוסיף להגדרות של הReference איזשהו AliasלAssembly, זה מתבצע דרך הSolution Explorer, בProperties של הReference המדובר:

220_1.png

לאחר מכן, אפשר לבקש להשתמש בAssembly הרצוי באמצעות הגדרה של extern alias, זו הצהרה שדומה להצהרה של using:

1
extern alias Cool1;

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

1
Cool1::CoolNamespace.CoolClass

למשל:

1
Cool1::CoolNamespace.CoolClasscoolClass = new Cool1::CoolNamespace.CoolClass();

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

אפשר גם להשתמש בusing directive (טיפ מספר 13) כדי שהשימוש יהיה יותר כיפי:

1
using VeryCool = Cool1::CoolNamespace.CoolClass;

ואז:

1
VeryCool coolClass = new VeryCool();

כאמור, עדיף לא להגיע למצב הזה.

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

סופ"ש חיצוני מעולה!

שתף

219. global namespace

בהמשך לטיפים הקודמים,

לפעמים אנחנו עשויים להיתקל במצב בו יש לנו מספרNamespaceים עם שמות דומים, אבל לא עם אותו שורש.

למשל:

1
2
3
4
5
6
7
namespace First.Second.Third
{
}
namespace Second.Third
{
}

כאשר Second.Third הוא לא Namespace מקונן של First.

נניח שמוגדר טיפוס בSecond.Third ואנחנו רוצים להשתמש בו מהNamespace הראשון:

1
2
3
4
5
6
namespace Second.Third
{
public class FirstClass
{
}
}

אם נשתמש בusing לפני ההגדרה של הNamespace, הדבר אכן אפשרי:

1
2
3
4
5
6
7
8
9
using Second.Third;
namespace First.Second.Third
{
public class SecondClass
{
private FirstClass mThisWorks;
}
}

אבל בלי זה, זה לא כל כך יעבוד:

1
2
3
4
5
6
7
namespace First.Second.Third
{
public class SecondClass
{
private Second.Third.FirstClass mThisWorks;
}
}

נקבל את שגיאת הקימפול הבאה:

Error: The type or namespace name ‘FirstClass’ does not exist in the namespace ‘First.Second.Third’ (are you missing an assembly reference?)

מה קורה כאן? כאשר אנחנו כותבים Second, הקומפיילר חושב שאנחנו מתכוונים לקיצור של First.Second (כי זהו Namespace מקונן), ולכן כאשר כתבנו Second.Third.FirstClass, הוא חושב שהתכוונו לFirst.Second.Third.FirstClass, ובו כמובן הוא לא מוצא את הטיפוס.

באופן דומה, הקומפיילר יכול להתבלבל בין Nested Types וNested Namespaces עם שמות דומים, למשל:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace First
{
public class FirstClass
{
public class InnerClass
{
}
}
}
namespace FirstClass
{
public class InnerClass
{
}
}

אז אם ננסה לגשת לInnerClass של הnamespace ששמו FirstClass מהNamespace ששמו First, לא כל כך נצליח:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace First
{
public class FirstClass
{
public void Test()
{
FirstClass.InnerClass x = new FirstClass.InnerClass();
// First.FirstClass.InnerClass and not FirstClass.InnerClass
}
public class InnerClass
{
}
}
}

מאותה סיבה – מאחר והNamespace מקונן, הקומפיילר חושב שאנחנו מתכוונים בFirstClass.InnerClass לטיפוס שנמצא בתוך הNamespace שלנו.


כדי לפתור את הבעיות האלה, אפשר להשתמש בKeyword ששמו global::, הוא בעצם אומר לקומפיילר לשכוח מהNamespace שבו הוא נמצא, ולחפש את מה שרשמנו בעזרת השם המלא שלו.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace First
{
public class FirstClass
{
public void Test()
{
global::FirstClass.InnerClass x = new global::FirstClass.InnerClass();
// FirstClass.InnerClass
}
public class InnerClass
{
}
}
}
namespace First.Second.Third
{
public class SecondClass
{
private global::Second.Third.FirstClass mThisWorks;
}
}

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

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

מה גם, שCodeDom ספציפית אינו מאפשר להוסיף using מעל הNamespace, אלא רק בתוך הNamespace עצמו, כך שגם הבעיה הראשונה יכולה להתרחש (ואף התרחשה!).

אז מה עושים? אם תסתכלו על הקוד של אובייקט שחולל ע"י כלי של הFramework (למשל, XSD או DataSet), תראו שבכל מקום הוא משתמש בglobal:: ובשמות המלאים, כדי להיות בטוח שהוא ניגש לטיפוס שזה השם המלא שלו, ולא לטיפוס אחר שנמצא בתוך הNamespace בתוכו אנחנו חיים (ואליו הקוד מג’ונרט).

שיהיה המשך יום גלובלי טוב.

שתף

218. Nested namespaces

באופן אנלוגי לNested types, קיימת לנו אפשרות להגדיר משהו שנקרא Nested namespace.

מה זה בדיוק?

זהו בעצם Namespace שמוכל בתוך Namespace אחר:

1
2
3
4
5
6
7
8
9
namespace TopNamespace
{
namespace SecondNamespace
{
Namespace BottomNamespace
{
}
}
}

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

1
TopNamespace.SecondNamespace.BottomNamespace.BottomType myInstance;

אפשר גם להשתמש בUsing directive:

1
using TopNamespace.SecondNamespace.BottomNamespace;

מה זה נותן לנו?

ברגע שנחיה בתוך Nested Namespace, נוכל לגשת לכל מה שיש Namespaceים שמעלינו בלי using:

למשל, בקובץ אחד נכתוב משהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace TopNamespace
{
namespace MySecondNamespace
{
public class CoolType
{
}
namespace InnerNamespace
{
}
}
}

ובקובץ אחר:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace TopNamespace
{
namespace MySecondNamespace
{
namespace AnotherInnerNamespace
{
public class AnotherClass
{
public static void Access()
{
CoolType coolType = new CoolType();
}
}
}
}
}

בלי using!

בנוסף, שימוש בNested Namespaces מאפשר לנו לארגן את הקוד שלנו בצורה נכונה,

כלומר מי שמשתמש בקוד שלנו יוכל להתמצא ע"י שיטוט בNested Namespaces, כמו שיטוט בתיקיות היררכיות ברשת.

ככה אפשר לדעת למי שייך namespace מסוים, לפי הNamespace הראשי שמכיל אותו.

למשל, כל הNamespaceים של הFramework הם Nested Namespaces של System. ככה אפשר לזהות שהם של הFramework ולא שלנו.


דרך אלטרנטיבית להשתמש בNested namespaces היא באמצעות ציון השם הNamespace המקונן המלא:

ההגדרה:

1
2
3
4
5
6
7
8
9
namespace TopNamespace
{
namespace SecondNamespace
{
Namespace BottomNamespace
{
}
}
}

שקולה להגדרה:

1
2
3
namespace TopNamespace.SecondNamespace.BottomNamespace
{
}

האחרון הוא השימוש היותר נפוץ בNested namespaces.

אופן השימוש המומלץ הוא לחלק את הקוד לתת Namespaceים לפי מבנה הקבצים בפרויקט:

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

זו גם ההתנהגות הדיפולטית בVisual Studio – כאשר נוסיף מחלקה לתיקייה, הNamespace הדיפולטי שהיא תקבל יהיה תתNamepsace של הNamespace בו חיה התיקייה, ששמו של התת Namespace יהיה השם של התיקייה.

המשך יום מקונן טוב

שתף

217. Nested types

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

למשל, נניח שיש לנו מבנה של עץ, אזי עץ מורכב מצמתים (Nodes) שכל אחד מהם מכיל (חלק מ)המידע הבא: הערך בצומת, הבנים של הצומת, האבא של צומת.

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

בנוסף, יכול להיות שאנחנו לא מעוניינים לחשוף את המושג של צומת לכל העולם, ואולי גם לא למחלקות “החברות” שלנו (כלומר, שנמצאות באותו Assembly או בAssemblyים חברים, מה שinternal מאפשר לנו), אלא רק למחלקה של העץ.

פתרון אפשרי לבעיות אלה הוא המושג של Nested Types.

מה זה בכלל Nested Type? מדובר בטיפוס מקונן בטיפוס אחר: ההגדרה מתבצעת בצורה הבאה:

1
2
3
4
5
6
7
8
9
10
public class MyClass
{
public class MyInnerClass
{
}
public interface IMyInterface
{
}
}

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

1
2
MyInnerClass myInnerClass = new MyInnerClass(); // Doesn't compile
MyClass.MyInnerClass myInnerClass = new MyClass.MyInnerClass(); // compiles

אפשר כמובן לעשות דברים כאלה כדי שהשורה הראשונה כן תעבוד:

1
using MyInnerClass = MyNamespace.MyClass.MyInnerClass;

(ראו גם טיפ מספר 13)

בנוסף, בניגוד להגדרות של מחלקות וממשקים רגילים שיכולים רק לקבל את הAccess Modifier של public או internal, טיפוסים מקוננים יכולים לקבל כל Access Modifier:

private פשוט אומר שהטיפוס המקונן נגיש רק בתוך המחלקה, וprotected אומר שאפשר לגשת לטיפוס המקונן גם דרך המחלקות הבנות של המחלקה. (נחשו לבד מה אומר protected internal)

הדבר הזה בעצם מאפשר לנו הסתרה יותר ספציפית של מחלקות (מאשר להפוך אותן לinternal או public)

בנוסף, מחלקה מקוננת יכולה להנות מהיכולת לגשת לMemberים הפרטיים של המחלקה המכילה אותה, לדוגמה:

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 class MyType
{
private int mSecret = 3;
public void UseNestedType()
{
MyNestedType nestedType = new MyNestedType(this);
nestedType.TellSecret();
}
private class MyNestedType
{
private MyType mInstance;
public MyNestedType(MyType instance)
{
mInstance = instance;
}
public void TellSecret()
{
Console.WriteLine("The secret is {0}", mInstance.mSecret);
}
}
}

אז הקוד הבא ידפיס

1
2
MyType typeInstance = new MyType();
typeInstance.UseNestedType(); // The secret is 3

אז למה זה טוב? כמו שכתבתי, יש שתי סיבות טובות להשתמש בטיפוסים מקוננים:

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

השנייה היא כדי להסתיר את הטיפוס מרוב העולם, יותר ממה שמאפשר לנו שימוש בModifier בשם internal.

המשך יום מקונן טוב.

שתף

216. protected internal access modifier

אז הכרנו את הAccess modifier ששמו internal.

אנחנו מכירים גם עוד Access modifiers כגון public,private וprotected.

protected, כזכור מאפשר לנו לגשת לMember של המחלקה שלנו גם במחלקות הבנות.

פחות מוכר הוא הAccess modifier ששמו protected internal. זהו איחוד של הAccess modifierים ששמם internal וprotected – מה שנסמן בAccess modifier זה יהיה נגיש למחלקות שיורשות מהמחלקה שלנו, ולמחלקות שהן “חברות שלנו” (כלומר באותו Assembly או שיש לנו InternalsVisibleTo אל הAssembly שלהן)

לדוגמה:

1
2
3
4
5
6
7
public class Animal
{
protected internal void AddChild(Animal child)
{
// ...
}
}

אז נוכל לקרוא לפונקציה AddChild ממחלקות בנות של Animal, או ממחלקות שחיות עם Animal באותו Assembly.

סתם להעשרה, הCLR תומך באפשרות של חיתוך של internal וprotected (כלומר Memberים שאפשר לגשת אליהם רק ממחלקות שהן גם בנות של המחלקה, והן גם "חברות" שלה), אבל שפת C# לא תומכת בזה.
עדכון: בC# 7.2 נוספה תמיכה בprivate protected, שזה בדיוק החיתוך שדיברתי עליו. ראו כאן.

כזכור, אני לא אוהב Memberים שהם internal, אבל שווה להכיר שקיים דבר כזה.

שבוע פנימי בטוח טוב.

שתף

215. Default access modifiers

יש לנו אפשרות בשפה לא לציין Access Modifierים של Memberים וטיפוסים שלנו.

כעקרון זה Feature שמומלץ לא להשתמש בו כי הוא גורם לאי-בהירות, אבל שווה להכיר אותו, בעיקר כי הוא נפוץ בתור שאלות בראיונות עבודה.

אם לא נשים Modifier על מחלקה או ממשק לא מקוננים, אז הם נהיים internal דיפולטית.

אם לא נשים Modifier על Member של מחלקה או Struct, אז הוא נהיה private דיפולטית.

אם לא נשים Modifier על מחלקה מקוננת, אז היא תהיה private דיפולטית. (לא כתבתי על זה, אבל אלה מחלקות מקוננות, ואם הן private, אז ניתן לגשת אליהן רק מתוך המחלקה שמכילה אותן)


כאמור, אני חושב שעדיף לא להשתמש בזה, כיוון שזה מאוד לא ברור לקורא איזה Access Modifier יש לMember, אלא אם כן הוא במקרה עבר ראיון עבודה לאחרונה,

שיהיה אחלה סופ”ש עם גישה חיובית.

שתף

214. InternalsVisibleToAttribute

אז פעם שעברה הכרנו את הAccess modifier ששמו internal.

אמרתי שבד”כ הוא מציין שMember או Type הוא ניתן לגישה בתוך הAssembly.

למה בדרך כלל? כי אפשר לשתף Memberים שהם internal בין Assemblyים ספציפיים שונים.

איך עושים זאת? בAssemblyInfo אפשר לציין בAttribute ששמו InternalsVisibleTo איזה Assemblyים יכולים לגשת לMemberים שלנו שהם internal.

למשל, ככה:

1
[assembly:InternalsVisibleTo("MyOtherAssembly")]

ככה הAssembly ששמו MyOtherAssembly יוכל לגשת לMemberים שהם internal מהAssembly שלנו.


בהמשך למה שכתבתי פעם שעברה, אני חושב שזה לא נכון לשתף טיפוסים שהם internal בין Assemblyים שונים.

על שיתוף Memberים שהם internal אני אפילו לא מדבר… (כמו שכתבתי פעם קודמת, אני לא כל כך אוהב Memberים שהם internal)

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

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

אני זוכר מקרה שאיזשהו צוות לא הכיר את הAttribute הזה, ונאלץ לשנות Modifierים של הרבה מאוד קוד מinternal לpublic.

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

המשך יום פנימי חשוף לכל טוב.

שתף

213. internal access modifier

דרך נוספת להימנע מאי נעימויות של חשיפת המשתמש לדברים שהוא לא אמור להיחשף אליהם, היא להשתמש בModifier ששמו internal.

את הAccess modifier הזה אנחנו יכולים לשים על טיפוסים (בדומה לModifier ששמו public) וMemberים שלנו (בדומה לשאר הModifierים שאפשר לשים, public, private וprotected)

עצם הפיכת טיפוס או Member לinternal מונעת את חשיפתם לעולם החיצון. זה אומר שבד”כ מחוץ לAssembly שלנו, לא תהיה לנו גישה למחלקות או Memberים אלה.

הModifier יכול לעזור לנו ליצור רמת אבסטרקציה בשימוש בAssembly שלנו.

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

לדוגמה המחלקה OracleConnection מממשת את הממשק IDbConnection:

1
2
3
4
5
6
7
8
9
10
11
public class OracleConnection : IDbConnection
{
// Constructors:
public OracleConnection()
{
// ...
}
// Implementation
// ...
}

כעת מה שסביר להניח שיקרה הוא שכל מי שישתמש בOracleConnection יכיר את המימוש הספציפי:

1
2
3
4
public void TestConnection(OracleConnection connection)
{
// ...
}

למרות שהוא היה אמור להשתמש בIDbConnection.

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

ראו גם טיפ מספר 81.

מה שהיינו יכולים לעשות במקום, זה לעשות שהמימוש יהיה internal.

1
2
3
4
5
6
7
8
9
10
11
internal class OracleConnection : IDbConnection
{
// Constructors:
public OracleConnection()
{
// ...
}
// Implementation
// ...
}

וליצור איזושהי פונקציה שהיוצרת את המחלקה הזאת שהחתימה שלה מחזירה IDbConnection.

ככה אנחנו יכולים להיות בטוחים שרק אנחנו מכירים את הType הקונקרטי. זה גם אומר שרק אנחנו יכולים ליצור instanceים שלנו וכו’.


תכלס, אני פחות אוהב את השימוש בinternal על Memberים.

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

כמובן, לפעמים אין כל כך ברירה.

לעומת זאת, אני חושב ששימוש בinternal על מחלקות או ממשקים יכול להיות לגיטימי – דוגמה אחת היא הדוגמה שרשמתי קודם: החבאת המימוש מהמשתמש.

עוד סיבה להפוך ממשק/מחלקה לinternal היא שמדובר בטיפוס שהוא באמת פנימי – כלומר, אף אחד לא אמור לדעת שהוא קיים – הוא Helper Class העוזר לנו בתוך הAssembly שלנו לממש איזשהו משהו יותר מורכב.

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

שיהיה המשך יום פנימי טוב

שתף

212. sealed methods

ראינו פעם שעברה שאפשר להפוך מחלקה לsealed, דבר המונע ממפתחים אחרים לרשת מהמחלקה שלנו.

משהו פחות מוכר זה האפשרות להפוך מתודה לsealed. מה זה אומר?

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

מה שנוכל לעשות זה לשים ליד הפונקציה את המילה השמורה sealed, וכעת לא יהיה אפשר לדרוס אותה יותר:

למשל:

1
2
3
4
5
6
7
8
9
public class Rectangular : Shape
{
protected readonly Edge[] mEdges = new Edge[4];
public sealed override intGetPerimiter()
{
return mEdges.Sum(x => x.Length);
}
}

כעת אם ננסה לדרוס את היקפו של ריבוע, לא נוכל יותר:

1
2
3
4
5
6
7
public class Square
{
public override int GetPerimiter()
{
return 4 * mEdges.First().Length;
}
}

נקבל שגיאת קימפול:

Error: ‘Square.GetPerimiter()’: no suitable method found to override

זה מאפשר לנו להחליט שמתודה מסוימת החל ממקום מסוים בירושה היא כבר לא וירטואלית.

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

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

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

למעשה, כאשר הופכים מחלקה לsealed, הקומפיילר הופך את כל המתודות בה לsealed באופן implicitly.

המשך יום פחות סגור טוב,

שתף

211. sealed classes

כאשר אנחנו כותבים מחלקות משלנו, באפשרותנו להפוך אותן להיות sealed, כלומר אטומות.

מחלקה שהיא sealed היא מחלקה שלא ניתן לרשת ממנה ולכן לא ניתן להרחיב אותה.

כדי להפוך מחלקה לsealed פשוט נשים את הKeyword ששמו sealed בהגדרה שלה:

1
public sealed class MyClass

היתרונות בלהפוך מחלקה לsealed:

  1. יכול להיות שיש לנו מחלקה שאנחנו לא באמת רוצים לחשוף החוצה, היא די פנימית. עכשיו במקרה היא public וגם לא sealed. מה שקורה אחרי זה, זה שאנחנו מגלים שמישהו ירש מהמחלקה שלנו ודרס כמה פונקציות, כאשר הוא לא היה אמור באמת להרחיב את המחלקה.
  2. במידה וקורה מה שמתואר בסעיף הקודם, ייתכן ועכשיו צריך לדאוג לתמוך לאחור בAPI ישן (למשל, חתימות של פונקציות שהן protected) כיוון שלא חשבנו על האפשרות שמישהו ירש מהמחלקה שלנו.
  3. קריאה לפונקציה וירטואלית שנדרסת ע"י מחלקה שהיא sealed (ע"י החזקת Reference מסוג מחלקה זו) גורמת לאופטימיזציה בJIT בקריאה לפקודת callvirt, מאחר ואין צורך לבדוק לאיזה פונקציה התכוון המתכנת לקרוא.
  4. בריצה באמצעות לולאת foreach יש לנו יותר Type-safety במחלקות שהן sealed (ראו גם טיפ מספר 52)

שימו לב שמחלקות סטטיות לא יכולות "sealed" מאחר ואין בהן מתודות וירטאליות שניתן לדרוס:

1
2
3
public sealed static class MyStaticClass
{
}

Error: ‘MyStaticClass’: a class cannot be both static and sealed

מצד שני, לעולם לא ניתן להרחיבן:

1
2
3
4
5
6
7
public static class MyStaticClass
{
}
public static class MyOtherStaticClass : MyStaticClass
{
}

Error: Static class ‘MyOtherStaticClass’ cannot derive from type ‘MyStaticClass’. Static classes must derive from object.

כך שהן סוג של "sealed"…

שבוע לא סגור טוב

שתף