DCSIMG
Mysteries with Circular Dependencies - Liran Chen's Blog

Liran Chen's Blog

.Net Internals, Debugging, Multithreading - and More!

Mysteries with Circular Dependencies

אם יש משהו אחד בסיסי שכולם יודעים על פיתוח תוכנה, הוא שלא ניתן ליצור תלות מעגלית בין פרוייקטים (DLL'ים).
אם למשל יש לנו פרוייקט A, שפונה לפרוייקט B, אז לא יהיה ניתן שאותו פרוייקט B יפנה בחזרה לפרוייקט A בתור רפרנס. אם היינו עושים דבר כזה, היתה נוצרת לנו תלות מעגלית בין שני הפרוייקטים. וזה רע, בגלל שכשהקומפיילר ירצה לקמפל את A, הוא יראה שהוא תלוי ב-B, אז הוא יגש ל-B וינסה לקמפל אותו. אבל אז הוא יגלה ש-B למעשה תלוי ב-A, ואז הוא יחזור חלילה עד אין קץ..

האמנם?
אם יצא לכם לפשפש מספיק במבנה הספריות של ה-BCL, יתכן ושמתם לב לכל מיני תלויות לא הגיוניות, אם לא "בלתי אפשריות". קחו למשל את המקרה הזה:

  • Xml - תלוי ב-System
  • Configuration - תלוי ב-System
  • System - תלוי ב-Xml ו-Configuration
אכן, תמונות קשות. ברגע אחד, נראה שכל ההבנה שלנו על מה שאפשר, או יותר נכון: אי אפשר לעשות עם תלויות בין DLL'ים, התנפצה לרסיסים. אבל מה לעשות, זה המצב, ולמרות שאנחנו נמצאים ב-State of mind שמה שראינו לפני רגע לא יכול להתקיים ... נראה שהוא מתקיים בכל זאת. והכל בחסות ה-BCL.

אז אחרי ששיפשפנו את העיניים, ושטפנו את הפנים במעט המים, הגיע הזמן להבין איך המצב הזה באמת מתאפשר בפועל.
נחזור לדוגמה איתה פתחתי את הפוסט. לצורך העניין נאמר שיש לנו שני DLL'ים, A ו-B. בתוך A, יש לנו את המחלקות ClassA, ClassB. לעומת זאת, בתוך B, יש לנו את המחלקה ClassC. עכשיו, אנחנו רוצים ש-ClassB תירש מ-ClassC בעוד שזאתי תירש מ-ClassA. כלומר, נוצרת לנו כאן תלות מעגלית קלאסית.


 
 
כדי לאפשר את התלות הזאת, נצטרך לעזוב לרגע את Visual Studio, ולעבור ל-Command Prompt כדי שנוכל לעבוד ישירות מול הקומפיילר (ברמת העקרון ניתן לעשות זאת גם דרך VS, רק שזה הופך את התהליך להרבה יותר מסורבל).
הרעיון הוא שנבצע את הקומפילציה בשני שלבים. בשלב הראשון נקמפל את A, ללא החלקים שתלויים ב-B. כלומר, רק את ClassA. לאחר מכן, נקמפל את B כרגיל (הוא יוכל להשתמש ברפרנס ל-A בגלל שהחלקים שמעניינים אותו כבר קומפלו בצעד הקודם). הצעד האחרון, הוא לקמפל מחדש את A. הפעם את כל הקוד, גם זה שתלוי ב-B (שימו לב ש-B הוא כבר DLL מקומפל ומלא לכל דבר).
דרך ה-Command Prompt, זה נראה כך:
 
>csc /target:library /out:A.dll ClassA.cs                                         // compile a "thin" version of A
>csc /target:library /reference:A.dll /out:B.dll ClassC.cs                  // compile full B
>csc /target:library /reference:B.dll /out:A.dll ClassA.cs ClassB.cs  // compile full A
 
 
בתוצאה הסופית, קיבלנו בדיוק מה שרצינו. יש לנו כעת שני DLL'ים, A ו-B. ובניגוד לכל מה שהגיוני בעולם, קיימת ביניהם תלות מעגלית אחת ונפלאה.
אחרי מסכת חיפושים קצרה שערכתי בגוגל, הגעתי לקצה חוט שלפיו, בתהליך ה-Build הפנימי של מיקרוסופט נעשה שימוש ב-Metadata Assemblies (אסמבלים המכילים אך ורק Metadata, ללא פרטי מימוש), במקום אסמבלים אמיתיים. ובצורה זאת התלות המעגלית "נשברת" והסיטואציה המשונה הזאת מתאפשרת. עם זאת, לא נתקלתי עדיין במאמר מסודר מספיק שמתאר את התהליך בפרטי פרטים, כך שאין לשער באילו קסמים אחרים יתכן ומשתמשים ברדמונד.
שלח תגובה

(שדה חובה)  

(שדה חובה)  

(אופציונלי)

(שדה חובה) 

Please add 3 and 6 and type the answer here:


Enter the numbers above: