سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)
سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.
- أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
- أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621355)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
小村 豪 (2026). ما هو Native AOT في .NET - الفرق عن JIT و trimming. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621355 https://comcomponent.com/ar/blog/2026/03/13/001-dotnet-native-aot-what-is/
- DOI (أحدث نسخة)
- 10.5281/zenodo.21621355
- DOI (هذه النسخة)
- 10.5281/zenodo.22240822
سبق أن كتبت في كيف نستدعي DLL مبنية بـ Native AOT من C# عبر C/C++ عن استدعاء C# من C/C++ باستخدام Native AOT. كان الأرفق أن أضع قبل ذلك ما هو Native AOT أصلاً. الترتيب انعكس قليلاً.
في حديث Native AOT تختلط المصطلحات من أوّل جملة.
- هل الموضوع إلغاء JIT؟
- ما الفرق عن self-contained أو single-file؟
- هل هو من عائلة ReadyToRun نفسها؟
- ماذا يحدث عندما تتدفّق تحذيرات trimming؟
- هل يمكن استخدامه بالمزاج نفسه مع WPF و WinForms و ASP.NET Core؟
إذا اختلطت هذه الأسئلة بدا Native AOT «زرّاً سحريّاً للسرعة»، أو على العكس «شيئاً مخيفاً مليئاً بالقيود». كلا النظرتين فضفاضتان.
flowchart TB
accTitle: سوء الفهم الذي يحدث عندما تختلط المصطلحات
accDescr: إذا اختلطت مصطلحات مثل JIT و self-contained و ReadyToRun و trimming بدا Native AOT زرّاً سحريّاً للسرعة أو شيئاً مخيفاً مليئاً بالقيود، وكلا النظرتين فضفاضتان.
mix["اختلاط المصطلحات"] --> m1["يبدو زرّاً سحريّاً للسرعة"]
mix --> m2["يبدو مخيفاً لكثرة القيود"]
sort["فصل الكلمات وترتيبها"] --> fair["خيار تتّضح مواضع استخدامه"]
الشكل 1: سبب سوء الفهم اختلاط المصطلحات. نبدأ بفصل الكلمات.
تفترض هذه المقالة الإحساس العملي الحالي منذ .NET 8 فصاعداً، وترتّب أوّلاً أربع نقاط.
- ما هو Native AOT فعلاً
- ما الذي يتحسّن، وأين يضيق الأمر
- كيف يختلف عن ReadyToRun و trimming
- من أيّ نوع من التطبيقات يبدأ التجريب بهدوء
الفهرس
- الخلاصة في جملة
- جداول للنظر أوّلاً
- 2.1. كلمات محيط Native AOT
- 2.2. الفرق بين JIT و ReadyToRun و Native AOT
- الصورة الكاملة لـ Native AOT (رسم)
- ماذا يفيد Native AOT؟
- 4.1. الإقلاع يصير أخفّ غالباً
- 4.2. لا حاجة لافتراض تثبيت الـ runtime مسبقاً
- 4.3. يناسب بيئات تشغيل مقيّدة
- أين يضيق Native AOT؟
- 5.1. Reflection وتوليد الشيفرة الديناميكي
- 5.2. يلزم التفكير بافتراض trimming
- 5.3. النشر لكل منصّة على حدة
- 5.4. سطح مكتب Windows وسياق COM يستدعيان حذراً شديداً
- الحدّ الأدنى من الخطوات
- 6.1.
csproj - 6.2. publish
- 6.3. طريقة كتابة JSON
- 6.1.
- الحالات التي يناسبه
- الحالات التي لا يناسبه
- مواضع التعثّر
- الخلاصة
- روابط مرجعية
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 16، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
1. الخلاصة في جملة
- Native AOT أسلوب يترجم تطبيق .NET مسبقاً إلى شيفرة أصلية عند publish ثم يوزّعه.
- لأنه لا يستخدم JIT وقت التشغيل، يتحسّن غالباً زمن الإقلاع وحجم الذاكرة، ويصبح التوزيع إلى بيئة بلا .NET runtime أسهل.
- غير أن الملاءمة تسوء مع Reflection الحرّ، وتوليد الشيفرة الديناميكي، و built-in COM، والمكتبات غير المتوافقة مع trimming.
- أي أنه ليس زرّاً سحريّاً للسرعة، بل نموذج نشر يتخلّى قليلاً عن العالم الديناميكي ويقترب من عالم ثابت لأجل الإقلاع والتوزيع وبيئة التشغيل.
Native AOT آلية لتوزيع .NET بشكل أقرب إلى تطبيق أصلي، وليس مربّع اختيار لتسريع التجميع فحسب.
flowchart TB
accTitle: ماذا نكسب وماذا نتخلّى عنه
accDescr: Native AOT نموذج نشر يكسب زمن الإقلاع وحجم الذاكرة والتوزيع بلا runtime عبر ترجمة مسبقة عند publish، ويتخلّى مقابل ذلك عن الملاءمة مع Reflection الحرّ وتوليد الشيفرة الديناميكي و built-in COM.
aot["Native AOT"] --> gain["ما نكسبه"]
aot --> lose["ما نتخلّى عنه"]
gain --> g1["خفّة الإقلاع والذاكرة والتوزيع"]
lose --> l1["Reflection الحرّ"]
lose --> l2["توليد الشيفرة الديناميكي و built-in COM"]
الشكل 2: ليس زرّاً سحريّاً للسرعة، بل صفقة تقرّب التوزيع من عالم ثابت بعد التخلّي قليلاً عن العالم الديناميكي.
2. جداول للنظر أوّلاً
2.1. كلمات محيط Native AOT
فصل هذه الكلمات مبكّراً يسهّل ما يأتي.
| المصطلح | ماذا يفعل | علاقته بـ Native AOT |
|---|---|---|
| JIT | يولّد شيفرة أصلية من IL وقت التشغيل | Native AOT ينجز هذا العمل مسبقاً |
| self-contained | يوزّع مجموعة .NET اللازمة للتشغيل مع التطبيق | Native AOT يُفهم ضمن هذا المسار |
| single-file | يجمع مادّة التوزيع في ملف واحد | ليس جوهر Native AOT، لكن شكل الناتج يقترب غالباً |
| trimming | يحذف الشيفرة غير المستخدمة | يكاد يكون شرطاً مع Native AOT |
| ReadyToRun | يبقي الـ IL ويقدّم جزءاً من عمل JIT | شيء يشبه Native AOT اسماً ويختلف عنه فعلاً |
| source generator | ينقل المعالجة الديناميكية وقت التشغيل إلى توليد شيفرة وقت البناء | ملاءمته مع Native AOT جيّدة |
ما يختلط بسهولة أن Native AOT ليس ميزة واحدة، بل نموذج نشر يعمل مع self-contained و trimming و source generation و publish المثبت على RID.
flowchart TB
accTitle: الآليات التي تعمل مع Native AOT
accDescr: Native AOT ليس ميزة واحدة، بل نموذج نشر يعمل مع مسار self-contained وافتراض trimming وملاءمة source generation و publish المثبت على RID.
aot["Native AOT (نموذج نشر)"] --> s1["مسار self-contained"]
aot --> s2["trimming يكاد يكون شرطاً"]
aot --> s3["ملاءمة جيدة مع source generator"]
aot --> s4["publish مثبت على RID"]
الشكل 3: Native AOT ليس ميزة منفردة، بل نموذج نشر يعمل مع آليات محيطة كمجموعة.
2.2. الفرق بين JIT و ReadyToRun و Native AOT
هنا أيضاً أسرع أن نرى الجدول أوّلاً.
| الزاوية | تشغيل JIT العادي | ReadyToRun | Native AOT |
|---|---|---|---|
| JIT وقت التشغيل | يُستخدم | ما زال يُستخدم في بعض الحالات | لا يُستخدم |
| محتوى مادّة التوزيع | IL في الأساس | IL + شيفرة مولَّدة مسبقاً | ملف تنفيذي أصلي في الأساس |
| الإقلاع | خط الأساس | يتحسّن بسهولة | يتحسّن بقوّة غالباً |
| التوافق | الأوسع | واسع | القيود أشدّ |
| الميزات الديناميكية | سهلة الاستخدام | سهلة الاستخدام في الغالب | قيود كثيرة |
| الغرض المناسب | تطوير .NET العام | تحسين الإقلاع أوّلاً | الإقلاع والتوزيع والبيئات المقيّدة بقوّة |
إن كان ReadyToRun يتّجه إلى «تخفيف JIT قليلاً»، فإن Native AOT يتّجه إلى «عدم افتراض JIT وقت التشغيل أصلاً». حتّى مع كلمة AOT المشتركة، المزاج العملي مختلف جدّاً.
flowchart TB
accTitle: اختلاف اتّجاه ReadyToRun و Native AOT
accDescr: ReadyToRun يبقي الـ IL ويقدّم جزءاً من عمل JIT لتخفيفه، و Native AOT لا يفترض JIT وقت التشغيل أصلاً، فالمزاج مختلف رغم كلمة AOT المشتركة.
q{"ماذا نريد من JIT؟"}
q -->|"تخفيفه قليلاً"| r2r["ReadyToRun (الـ IL يبقى)"]
q -->|"عدم افتراضه"| aot["Native AOT (أصلي في الأساس)"]
r2r --> soft["التوافق يبقى واسعاً"]
aot --> hard["الإقلاع أسرع لكن القيود أشدّ"]
الشكل 4: حتّى مع كلمة AOT نفسها، «تخفيف JIT» و«عدم افتراض JIT» شيئان مختلفان.
بعد ذلك، إذا جمعنا «أي شكل توزيع نختار في النهاية» في رسم واحد، صار كالتالي.
flowchart TD
S["نريد اختيار شكل التوزيع"] --> Q1{"هل يمكن وضع .NET runtime في جهة التوزيع؟"}
Q1 -- "يمكن" --> Q2{"هل نريد ضغط زمن الإقلاع؟"}
Q2 -- "ليس إلى ذلك الحد" --> P1["framework-dependent: إصدار عادي"]
Q2 -- "نريد ضغطه" --> P2["ReadyToRun: تحسين الإقلاع مع بقاء التوافق"]
Q1 -- "لا نريد / لا نستطيع" --> Q3{"هل نعتمد على reflection أو توليد شيفرة ديناميكي أو built-in COM؟"}
Q3 -- "نعم نعتمد" --> P3["self-contained، ومع الحاجة جمعه single-file"]
Q3 -- "لا نعتمد" --> Q4{"هل الهدف console أو worker أو API صغيرة؟"}
Q4 -- "نعم" --> P4["Native AOT"]
Q4 -- "لا" --> P5["self-contained أوّلاً، ثم إعادة النظر بعد تقليل التبعيات الديناميكية"]
الشكل 5: اختيار شكل التوزيع. التفرّع أوّلاً حسب إمكان وضع الـ runtime، ثم حسب مدى تقليل الآليات الديناميكية.
التفرّع الأوّل هو هل يمكن وضع الـ runtime في جهة التوزيع، والتفرّع التالي هو إلى أي مدى يمكن تقليل الآليات الديناميكية. self-contained و single-file حديث عن «طريقة التوزيع»، و ReadyToRun و Native AOT حديث عن «متى تُولَّد الشيفرة الأصلية»، لذا يُنظر إليهما عمليّاً كتركيبات.
3. الصورة الكاملة لـ Native AOT (رسم)
إذا رسمنا Native AOT بصورة تقريبية، يكون كالتالي.
flowchart LR
Src["شيفرة مصدر C# / .NET"] --> IL["تجميعات IL"]
IL -->|تشغيل عادي| JIT["JIT وقت التشغيل"]
JIT --> Run1["تشغيل التطبيق"]
IL -->|dotnet publish + PublishAot| Analyze["تحليل AOT / trim"]
Analyze --> Trim["حذف الشيفرة غير اللازمة"]
Trim --> AOT["توليد شيفرة أصلية"]
AOT --> Run2["ملف تنفيذي خاص بـ RID"]
الشكل 6: التشغيل العادي يجري JIT وقت التشغيل، أمّا Native AOT فيقدّم التحليل والحذف والتوليد الأصلي إلى وقت publish.
.NET العادي يولّد IL أوّلاً، ثم يجري JIT لما يلزم وقت التشغيل. Native AOT يقدّم الجزء الكبير اللاحق إلى وقت publish.
المهم هنا أن وقت publish يحتاج إلى معرفة شبه كاملة بالشيفرة التي ستلزم وقت التشغيل. هنا تتغيّر مقدّمات الكتابة المسموحة.
- إيجاد الأنواع وقت التشغيل
- توليد الشيفرة وقت التشغيل
- تحميل Assembly وقت التشغيل
- تأجيل الحلّ بعبارة «سيُدبَّر الأمر لاحقاً»
هذا الأسلوب يصطدم فجأة مع Native AOT.
flowchart TB
accTitle: يلزم معرفة الشيفرة كلّها عند publish
accDescr: Native AOT يحتاج عند publish إلى معرفة شبه كاملة بالشيفرة اللازمة وقت التشغيل، فتسوء ملاءمته مع إيجاد الأنواع أو توليد الشيفرة أو تحميل Assembly أو الحلّ المؤجّل وقت التشغيل.
need["تثبيت الشيفرة اللازمة عند publish"] --> ng1["إيجاد الأنواع وقت التشغيل"]
need --> ng2["توليد الشيفرة وقت التشغيل"]
need --> ng3["قراءة Assembly وقت التشغيل"]
need --> ng4["تجاوز الأمر بحلّ مؤجّل"]
ng1 -.-> bad["كلّها سيّئة الملاءمة مع AOT"]
الشكل 7: جوهر تغيّر المقدّمة. كلّما مالت الكتابة إلى «نقرر وقت التشغيل» اصطدمت بـ Native AOT.
4. ماذا يفيد Native AOT؟
4.1. الإقلاع يصير أخفّ غالباً
أوضح أثر لـ Native AOT هو الإقلاع.
- أدوات CLI
- عمليات قصيرة العمر
- إقلاع يشبه serverless
- إقلاع الحاويات واستبدالها
- أدوات المراقبة والعمليات المقيمة الصغيرة
في هذه الحالات تظهر تكلفة JIT بسهولة، ونقل ذلك العمل إلى وقت أبكر بـ Native AOT يخفّف الانطلاقة الأولى.
حجم الذاكرة يتحسّن أيضاً غالباً، فيفيد حين تريد حشر عدد أكبر من النسخ على الأجهزة نفسها. في السحابة حيث تقوم عمليات متشابهة بأعداد كبيرة، يظهر هذا الفرق تدريجيّاً.
4.2. لا حاجة لافتراض تثبيت الـ runtime مسبقاً
التطبيق المنشور بـ Native AOT أسهل تشغيلاً في بيئة لم يُثبَّت فيها .NET runtime.
هذا مكسب هادئ وكبير.
- لا تريد أن تقول لجهة التوزيع «ثبّتوا .NET 9 Runtime أوّلاً»
- تريد صورة حاوية أنحف
- تريد وضع أداة صغيرة واحدة وتشغيلها
- بيئة التشغيل لا تسمح بـ JIT أو لا تريد السماح به
في هذه الحالات يكفي غياب افتراض «تجهيز runtime منفصل» ليهدأ النقاش كثيراً.
«بلا runtime» هنا تعني أن جهة التوزيع لا تحتاج تثبيت .NET منفصلاً. ليست بمعنى أن أجزاء runtime اللازمة تختفي تماماً من داخل التطبيق.
flowchart TB
accTitle: المعنى الصحيح لعبارة بلا runtime
accDescr: بلا runtime في Native AOT تعني أن جهة التوزيع لا تحتاج تثبيت .NET منفصلاً، وليست بمعنى أن أجزاء runtime اللازمة تختفي تماماً من داخل التطبيق.
word["عبارة بلا runtime"] --> ok["لا حاجة لوضع .NET في جهة التوزيع"]
word -.-> ngx["لا يعني اختفاء ما يعادل runtime"]
ok --> merit["تقلّ مقدّمات التوزيع والإقلاع"]
الشكل 8: «بلا runtime» حديث عن جهة التوزيع. ما يعادل runtime يبقى داخل التطبيق.
4.3. يناسب بيئات تشغيل مقيّدة
لأن Native AOT لا يستخدم JIT وقت التشغيل، يسهل تشغيله أيضاً حيث لا يُسمح بـ JIT.
هذا يظهر أكثر في السحابة والحاويات والجوال منه في سطح المكتب. غير أنه في تطوير Windows أيضاً مكسب كافٍ بمعنى «تقليل الافتراضات الزائدة في جهة التوزيع».
5. أين يضيق Native AOT؟
5.1. Reflection وتوليد الشيفرة الديناميكي
هذا قلب قيود Native AOT.
- التحميل الديناميكي مثل
Assembly.LoadFile - توليد الشيفرة وقت التشغيل مثل
System.Reflection.Emit - Reflection يتتبّع الأنواع بلا حدود وقت التشغيل
- كتابة تجمع generic كما تشاء وقت التشغيل
هذه الأنماط تصعّب تثبيت الشيفرة اللازمة عند publish، فتصير مرتع تحذيرات AOT.
طبعاً الأمر ليس بالبساطة حدّ «سطر Reflection واحد فيخرجك فوراً». غير أن الميل ثابت: كلّما مال التصميم إلى «ننظر ونقرر وقت التشغيل» ضاق الأمر.
من أسماء التحذير التي تظهر كثيراً عائلة RequiresDynamicCode.
المعنى «هذا الاستدعاء قد ينكسر تحت AOT»، لذا من الأأمن ألّا تُكبت جزافاً.
عند اعتماد Native AOT يسهل الفهم إذا اعتبرته انزياحاً نحو تقليل «ذكاء وقت التشغيل» وزيادة «التصريح وقت البناء».
flowchart TB
accTitle: من ذكاء وقت التشغيل إلى التصريح وقت البناء
accDescr: التحميل الديناميكي وتوليد الشيفرة وقت التشغيل و Reflection بلا حدود مرتع لتحذيرات AOT، لذا يُزاح التصميم نحو تقليل ذكاء وقت التشغيل وزيادة التصريح وقت البناء.
dynamicway["تصميم يعتمد على ذكاء وقت التشغيل"] --> warn["تحذيرات من عائلة RequiresDynamicCode"]
warn --> shift["زيادة التصريح وقت البناء"]
shift --> calm["تثبيت الشيفرة اللازمة عند publish"]
warn -.-> nosup["لا تُكبت جزافاً"]
الشكل 9: مواجهة القيد الجوهري باتّجاه واحد: من «نقرر وقت التشغيل» إلى «نصرّح وقت البناء».
5.2. يلزم التفكير بافتراض trimming
Native AOT مرتبط بعمق مع trimming. ما يسهل إغفاله هنا أن طريقة كتابة مكتباتك التابعة تؤثّر أيضاً، لا شيفرتك وحدها.
النقاط التي تستحق الانتباه:
- مسلسِلات قائمة على reflection
- تكوين DI أو إضافات يجمع الأنواع بمسح وقت التشغيل
- آلية تجد النوع من اسم نصّي ثم تنشّئه
- مكتبات تميل إلى proxy ديناميكي أو توليد IL
إذا ظهرت تحذيرات هنا وقلت «publish نجح إذن بخير»، فالفاتورة غالباً تأتي لاحقاً. مع Native AOT، التحذيرات تستحق القراءة بجدّ في الغالب.
flowchart TB
accTitle: trimming لا يُحسَم بشيفرتك وحدها
accDescr: في trimming تؤثّر كتابة المكتبات التابعة أيضاً لا شيفرتك وحدها، والمسلسِلات القائمة على reflection و DI الذي يمسح وقت التشغيل وحلّ الأنواع من أسماء نصّية والمكتبات التي تميل إلى proxy ديناميكي تستحق الحذر.
trim["نجاح trimming أو فشله"] --> own["طريقة كتابة شيفرتك"]
trim --> dep["طريقة كتابة المكتبات التابعة"]
dep -.-> ex["مسارات reflection والمسح وقت التشغيل"]
dep --> care["اقرأ التحذير بجدّ وتثبّت"]
الشكل 10: ما يسهل إغفاله هو جهة المكتبات التابعة. «publish نجح إذن بخير» يصير مزعجاً لاحقاً.
غير أن «اقرأ بجدّ» وحدها لا تحرّك العمل، لذا أضع أولوية المعالجة. توثيق مايكروسوفت الرسمي يوصي أيضاً بالتجربة بهذا الترتيب.
| الترتيب | المعالجة | متى تُستخدم |
|---|---|---|
| 1 | إيقاف reflection | يمكن استبدال Activator.CreateInstance(Type) بوسيط generic، أو الإزاح نحو source generator |
| 2 | وضع DynamicallyAccessedMembers |
reflection لازم، لكن النوع المستهدف معروف وقت التجميع |
| 3 | وضع RequiresUnreferencedCode |
حالات لا تقبل التحليل الساكن أساساً، مثل تحديد اسم النوع بنصّ وقت التشغيل |
| 4 | الكبت بـ UnconditionalSuppressMessage |
آخر وسيلة بعد تعذّر ما سبق، وبعد التثبّت من الأمان |
أكثر أنماط الخطوة الأولى شيوعاً هو النمط 2: «النوع معروف لكن التحذير يظهر».
مثلاً، الشيفرة التالية تخرج IL2070.
// IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
void PrintMethodNames(Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
إذا كنت تستدعي GetMethods() فأعلن بالسمة أنك تريد الإبقاء على المناهج العامة، فيزول التحذير.
using System.Diagnostics.CodeAnalysis;
void PrintMethodNames(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// إن مرّر الطرف المستدعي typeof فإن المتطلّب يُستوفى تلقائيّاً
PrintMethodNames(typeof(DateTime));
واجهة الاستدعاء والتحديد المطلوب يتطابقان في الغالب. GetMethod / GetMethods يعني PublicMethods، و GetProperty / GetProperties يعني PublicProperties، و Activator.CreateInstance يعني PublicParameterlessConstructor أو PublicConstructors.
DynamicallyAccessedMemberTypes.All مريح، لكنه يبقي أعضاء النوع كلّهم فيتضخّم الحجم، والأعضاء المُبقاة تجلب تحذيرات أخرى، لذا المبدأ هو تحديد الحدّ الأدنى اللازم.
إذا أضفت السمة ولم يزل التحذير، تتبّع من موضع استخدام reflection صعوداً إلى المستدعين، وتأكد أن المسار كلّه يحمل السمة. موضع واحد ناقص يقطع المتطلّب هناك.
flowchart TB
accTitle: عندما تبقى التحذيرات بعد إضافة السمة
accDescr: DynamicallyAccessedMembers يُحدَّد بالحدّ الأدنى اللازم، وإن بقي التحذير تتبّع من موضع reflection إلى المستدعين وتأكد أن المسار كلّه يحمل السمة. موضع واحد ناقص يقطع المتطلّب.
warnleft["أُضيفت السمة وبقي التحذير"] --> back["تتبّع صعوداً إلى المستدعي"]
back --> chain["هل المسار كلّه يحمل السمة؟"]
chain -.-> cut["موضع واحد ناقص يقطع المتطلّب"]
warnleft -.-> minrule["التحديد بالحدّ الأدنى هو المبدأ"]
الشكل 11: السمة تُوضَع على مسار لا على نقطة. موضع واحد ناقص في الوسط يقطع المتطلّب هناك.
5.3. النشر لكل منصّة على حدة
Native AOT يُنشَر مثبتاً على RID (Runtime Identifier).
أي أن ما بُني لـ win-x64 ليس عالماً يُشغَّل كما هو على linux-x64.
- Windows x64
- Windows Arm64
- Linux x64
- Linux Arm64
- macOS Arm64
المقدمة أن تُنتَج مادّة نشر لكل هدف.
هنا يصير الإحساس أقرب بكثير إلى «تطبيق أصلي» منه إلى .NET العادي المعتمد على إطار مثبت (framework-dependent).
5.4. سطح مكتب Windows وسياق COM يستدعيان حذراً شديداً
في سياق شركة كومورا سوفت ذ.م.م. هذه النقطة مهمّة خصوصاً.
على Windows لا يتضمّن Native AOT built-in COM. ثم إن WPF ضعيف التوافق مع trimming، و WinForms يعتمد بثقل على built-in COM marshalling، لذا في الوقت الحالي على الأقل ينبغي النظر إلى كليهما بحذر شديد كـ «مرشّح Native AOT أوّل».
أوضح معيار «الوقت الحالي» هنا. وصف هذه المقالة يستند إلى التوثيق الرسمي لأجيال .NET 8 / 9 / 10 (حتى يوليو 2026). عن WPF و WinForms، تذكر صفحة Microsoft «Known trimming incompatibilities» صراحة أن دعم trimming معطّل من جهة .NET SDK لكليهما: WPF لا يكاد يعمل بعد trimming لاعتماده القوي على reflection وفحص الشيفرة وقت التشغيل، و WinForms لاعتماده الثقيل على built-in COM marshalling. أي أن الأمر ليس «يُنظَر إليه بحذر»، بل أن SDK يوقفه في الوضع الحالي. قد يتغيّر هذا الوصف مستقبلاً، فراجع الصفحة نفسها عند القراءة.
flowchart TB
accTitle: سبب إيقاف WPF و WinForms
accDescr: Native AOT على Windows بلا built-in COM، و WPF يكاد لا يعمل بعد trimming لاعتماد reflection وفحص الشيفرة وقت التشغيل، و WinForms ثقيل الاعتماد على built-in COM marshalling، لذا عطّل .NET SDK دعم trimming لكليهما.
win["Native AOT على Windows"] --> nocom["لا يوجد built-in COM"]
wpf["WPF"] --> refl["اعتماد قوي على reflection"]
wf["WinForms"] --> commar["اعتماد ثقيل على COM marshalling"]
refl --> off["trimming معطّل من جهة SDK"]
commar --> off
الشكل 12: ليس «بحذر»، بل SDK يوقفه حالياً. لا تجعل نواة سطح المكتب المرشّح الأوّل.
باختصار:
- تحويل نواة WPF / WinForms فوراً إلى Native AOT
- إدخال COM interop بالإحساس المعتاد كما هو
هذه المسارات تكثّف تحذيرات publish وقيود وقت التشغيل دفعة واحدة، فتقفز تكلفة المعالجة.
في المقابل:
- تطبيقات console
- worker
- Web API صغيرة
- مكوّنات في التكامل الأصلي يسهل تقريبها من حدود دوال C
أوضح كمدخل.
إن لزم COM، فقد يكون الإبقاء على JIT، أو إعادة التصميم بافتراض ComWrappers / source-generated COM، أوضح في بعض الحالات.
6. الحدّ الأدنى من الخطوات
قبل ذلك، publish لـ Native AOT يحتاج سلسلة أدوات أصلية.
إن وضعت PublishAot ثم نفّذت dotnet publish دونها، يسقط الأمر في الربط الأصلي الأخير لا في التجميع. هذا أوّل حاجز، فجهّزها مسبقاً.
| البيئة | المطلوب |
|---|---|
| Windows | Visual Studio 2022 أو أحدث. تثبيت حمل عمل «تطوير سطح المكتب بـ C++» مع المكوّنات الافتراضية كلّها |
| Ubuntu 18.04 أو أحدث | sudo apt-get install clang zlib1g-dev |
| Alpine 3.15 أو أحدث | sudo apk add clang build-base zlib-dev |
| Fedora 39 أو أحدث / RHEL 8 أو أحدث | sudo dnf install clang zlib-ng-devel zlib-ng-compat-devel zlib-devel |
| macOS | Xcode Command Line Tools (مدعوم منذ .NET 8) |
باختصار: سلسلة أدوات المترجم، وحزم التطوير للمكتبات التي يعتمد عليها .NET runtime. إن كنت تشغّله في CI، فعينات Native AOT في dotnet/samples تتضمّن Dockerfile لكلّ من Linux و Windows، وأسرع طريق هو أخذ خطوات تثبيت المقدّمات من هناك.
لاحظ أن الثنائي المبني على Linux لا يعمل إلا على Linux مماثل أو أحدث. ما بُني على Ubuntu 20.04 يعمل على 20.04 وما بعده، ولا يعمل على 18.04. اختيار بيئة البناء يصير مباشرة نطاق التوزيع الممكن.
flowchart TB
accTitle: الحاجز قبل publish ونطاق التوزيع الممكن
accDescr: publish لـ Native AOT يحتاج سلسلة أدوات أصلية، ودونها يسقط في الربط الأصلي الأخير. الثنائي المبني على Linux لا يعمل إلا على Linux مماثل أو أحدث، فاختيار بيئة البناء يصير نطاق التوزيع.
tool{"هل سلسلة الأدوات موجودة؟"}
tool -->|"لا"| fail["يسقط في الربط الأصلي"]
tool -->|"نعم"| bin["يخرج ثنائي لكل RID"]
bin -.-> range["Linux يعمل فقط على بيئة مماثلة أو أحدث"]
range -.-> pick["اختيار بيئة البناء هو نطاق التوزيع"]
الشكل 13: الحاجز الأوّل سلسلة الأدوات. على Linux يحدّد قِدَم بيئة البناء نطاق التوزيع الممكن.
6.1. csproj
أوّلاً ضع PublishAot في ملف المشروع.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PublishAot>true</PublishAot>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
العينة كافية بـ net8.0. طريقة التفكير نفسها تقريباً في .NET 9 / 10.
المهم ألّا تضعه مؤقّتاً على سطر أوامر dotnet publish فقط، بل تبقيه في المشروع يوميّاً وتنظر إلى تحليل build / publish كجزء من العمل المعتاد.
ووضع <PublishAot>true</PublishAot> لا يعني أن التشغيل المحلّي اليومي يصير Native AOT فجأة.
dotnet run اليومي والتشغيل العادي يبقيان على JIT، وترجمة Native AOT الفعلية عند publish.
flowchart TB
accTitle: اليومي بعد وضع PublishAot
accDescr: حتّى مع وضع PublishAot في المشروع يبقى dotnet run اليومي على JIT، وترجمة Native AOT تجري عند publish. المهم إبقاؤه في المشروع والنظر يوميّاً إلى التحليل والتحذيرات عند build و publish.
put["وضع PublishAot في csproj"] --> daily["dotnet run اليومي يبقى JIT"]
put --> pub["ترجمة AOT عند publish"]
daily -.-> watch["انظر إلى التحليل والتحذيرات يوميّاً"]
الشكل 14: اليومي JIT، والإنتاج عند publish. لذلك يبقى الإعداد دائماً لا مؤقّتاً، وتتابع التحليل.
6.2. publish
مثلاً لـ Windows x64 يكون الشكل كالتالي.
dotnet publish -c Release -r win-x64
ولـ Linux x64 كالتالي.
dotnet publish -c Release -r linux-x64
الخرج مثبت على RID. النظرة تنتقل من «DLL واحد من .NET يعمل في أي مكان» إلى «ملف تنفيذي بُني لهذا نظام التشغيل / هذه المعمارية».
إن اقتربت من جهة Web API، فالدخول من قالب يفترض Native AOT أسهل.
dotnet new webapiaot -o MyFirstAotWebApi
ولـ worker هذا.
dotnet new worker -o WorkerWithAot --aot
6.3. طريقة كتابة JSON
موضع يُصادَف بهدوء مع Native AOT هو JSON.
System.Text.Json إن استُخدم بالإحساس المعتاد مال إلى reflection، لذا الإزاح نحو source generation أهدأ.
using System.Text.Json;
using System.Text.Json.Serialization;
[JsonSerializable(typeof(AppConfig))]
internal partial class AppJsonContext : JsonSerializerContext
{
}
public sealed class AppConfig
{
public string? Name { get; init; }
public int RetryCount { get; init; }
}
var config = new AppConfig
{
Name = "sample",
RetryCount = 3
};
string json = JsonSerializer.Serialize(config, AppJsonContext.Default.AppConfig);
في العمل اليومي أسهل أن تتذكّر الأمر لا كـ «دعم Native AOT» بل كـ إزاح نحو «لا تجعل وقت التشغيل يبحث عن الأنواع».
7. الحالات التي يناسبه
Native AOT ينسجم بسهولة في حالات كهذه.
- أدوات CLI يكون الإقلاع فيها محوريّاً
- واجهات API صغيرة تُنشَر بكثافة في حاويات
- worker / خدمات خلفية
- serverless أو عمليات قصيرة العمر
- مكوّنات .NET صغيرة تُدرَج في تطبيق أصلي
- حالات لا تريد اشتراط تثبيت .NET runtime مسبقاً في بيئة التشغيل
القاسم المشترك أن الحدود أوضح نسبيّاً، وتقليل الآليات الديناميكية أسهل.
8. الحالات التي لا يناسبه
في المقابل تتّضح أيضاً مواضع لا ينبغي جعل Native AOT ساحة رئيسية من البداية.
- نواة تطبيقات WPF / WinForms الكبيرة القائمة
- تكوين يفترض built-in COM interop
- تطبيق يقوم على تحميل plugin وقت التشغيل
- تكوين يعتمد بقوّة على إطار يستكشف الأنواع بالـ reflection
- مكتبات تستخدم
System.Reflection.Emitأو proxy ديناميكي كأمر مسلَّم به - تصميم يتوسّط فيه C++/CLI
هنا أوضح الإبقاء على .NET العادي بافتراض JIT، أو ReadyToRun، أو إعادة رسم الحدود في التصميم.
9. مواضع التعثّر
أخيراً، نقاط يسهل الوقوع فيها عند أوّل تعامل مع Native AOT.
- الاستخفاف بتحذيرات publish
- كما سبق، تحذيرات Native AOT تستحق القراءة بجدّ.
- build ينجح و publish ينكسر
- عند publish يجري التحليل جدّيّاً حتّى المكتبات التابعة، فتظهر هنا أمور لا تُرى قبل ذلك.
- معاملة ReadyToRun و Native AOT بالمزاج نفسه
- الكلمات متقاربة، وشدّة القيود مختلفة جدّاً.
- البدء فوراً من نواة تطبيق سطح المكتب
- console / worker / API صغيرة أهدأ أوّلاً.
- كتابة JSON وربط الإعدادات بالإحساس المعتاد
- الكتابة التي تفترض reflection تظهر أثرها لاحقاً.
- التوزيع بنيّة الاستقلال عن المنصّة
- مادّة Native AOT مثبتة على RID.
- اعتقاد أن «Native AOT = كل شيء يصير أسرع»
- المحور هو الإقلاع والتوزيع وبيئة التشغيل. إن انحرف هذا انحرفت التوقّعات.
مع Native AOT، ما يحسم القبول في النهاية ليس dotnet build بل dotnet publish.
كلّما بدأت تشغيل publish مبكّراً، قلّ العسر لاحقاً.
flowchart TB
accTitle: ما يحسم القبول هو publish
accDescr: قد ينجح build وينكسر publish لأن التحليل الجدّي يشمل المكتبات التابعة عند publish، وما يحسم قبول Native AOT في النهاية ليس dotnet build بل dotnet publish.
buildok["dotnet build ينجح"] --> notyet["القبول لم يُحسَم بعد"]
pubx["dotnet publish"] --> deep["تحليل جدّي يشمل التبعيات"]
deep --> verdict["هنا يظهر القبول أوّل مرّة"]
verdict -.-> early["لذلك شغّل publish مبكّراً"]
الشكل 15: القاسم المشترك في مواضع التعثّر: لا تطمئن لأن build نجح، وشغّل publish مبكّراً.
10. الخلاصة
Native AOT في جملة: آلية تُزيح تطبيق .NET من نموذج تشغيل ديناميكي إلى نموذج توزيع يسهل تثبيته ساكناً.
النقاط التي تستحق النظر تنحصر في خمس.
- Native AOT يترجم مسبقاً إلى شيفرة أصلية عند publish
- يؤثّر جيّداً في الإقلاع والذاكرة وملاءمة التوزيع
- في المقابل يضيق على reflection وتوليد الشيفرة الديناميكي و built-in COM والشيفرة غير المتوافقة مع trimming
- الهدف الأوّل أهدأ إن كان console / worker / API صغيرة لا نواة سطح المكتب
- المهم إزالة التحذيرات والتحقّق مبكّراً على أساس publish
Native AOT ليس مفتاحاً قياسياً يُوضَع على كل تطبيق .NET. لكن في مواضع يهمّ فيها الإقلاع، أو تريد تخفيف التوزيع، أو تريد تقليل افتراضات بيئة التشغيل، هو سلاح قوي جدّاً.
في المقابل، في عالم WPF / WinForms / COM الكثيف، ما زال .NET العادي أوضح في حالات كثيرة. إذا أمكن تمييز هذه الحالات، لم يعد Native AOT «ميزة جديدة صعبة»، بل خياراً واضح موضع الاستخدام.
11. روابط مرجعية
- Native AOT deployment overview - .NET
- Introduction to AOT warnings - .NET
- Fixing trim warnings - .NET
- DynamicallyAccessedMembersAttribute Class - .NET
- Prepare .NET libraries for trimming - .NET
- Known trimming incompatibilities - .NET
- How to use source generation in System.Text.Json - .NET
- ASP.NET Core support for Native AOT
- ReadyToRun deployment overview - .NET
- Building native libraries - .NET
- ComWrappers source generation - .NET
- مقال ذو صلة: كيف نستدعي DLL مبنية بـ Native AOT من C# عبر C/C++
- مقال ذو صلة: استدعاء DLL أصلية من C#: غلاف C++/CLI مقابل P/Invoke
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
أفضل الممارسات العمليّة لتعدّد مؤشّرات الترابط: نسخة .NET ── ما تقرِّره قبل إضافة مؤشّرات ترابط
قواعد تصميم عمليّة تمنع شيفرة .NET/C# متعدّدة مؤشّرات الترابط من الانهيار أو التجمّد أحياناً: اركب على Task بدل إنشاء المؤشّرات بنفسك، قل...
كيف نفهم عزل الجلسات في Windows ── Session 0 وRDP وتشغيل عدة مستخدمين معاً
نوضح مفهوم «الجلسة» الذي يربك كثيراً من مطوّري تطبيقات Windows. نشرح عملياً سبب عزل Session 0 الذي يمنع الخدمة من عرض واجهة، وسلوك الجلسا...
كيف تختار وسيلة الاتّصال بين عمليّات Windows ── جدول قرار للأنابيب المسمّاة / TCP / gRPC / الذاكرة المشتركة / COM
كيف تختار وسيلة التواصل بين تطبيقات Windows؟ ننظّم في جدول قرار مجالات تفوّق ومطبّات الأنابيب المسمّاة، وTCP المحليّ، وgRPC، والذاكرة الم...
كيف تختار مكان حفظ بيانات تطبيق Windows محليّاً ── جدول قرار بين SQLite وJSON وRegistry وAccess
أين ينبغي حفظ بيانات تطبيق سطح مكتب Windows، وبأيّ صيغة؟ نرتّب الفرق بين AppData وProgramData، ونقاط قوّة وعيوب كلّ من SQLite وملفّات JSO...
ما هو Generic Host في .NET - أساس DI والإعدادات والسجلات
ننظّم دور Generic Host من علاقة DI والإعدادات والسجلات و IHostedService و BackgroundService، ونلخّص أين يؤثّر وأين يصير زائداً من منظور ع...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
صيانة وتحديث برامج ويندوز الحالية
ندعم إضافة الميزات، والصيانة، والتحديث المتدرّج لبرامج ويندوز الحالية.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- ما هو Native AOT؟
- هو أسلوب نشر يترجم تطبيق .NET مسبقاً إلى شيفرة أصلية عند publish ثم يوزّعه. لأنه لا يستخدم JIT وقت التشغيل، يتحسّن غالباً زمن الإقلاع وحجم الذاكرة، ويصبح التوزيع إلى بيئة بلا .NET runtime أسهل. في المقابل تسوء الملاءمة مع Reflection الحرّ، وتوليد الشيفرة الديناميكي، و built-in COM، والمكتبات غير المتوافقة مع trimming. ليس زراً سحريّاً للسرعة، بل نموذج نشر يتخلّى قليلاً عن العالم الديناميكي ويقترب من عالم ثابت لأجل الإقلاع والتوزيع وبيئة التشغيل.
- ما الفرق بين Native AOT و ReadyToRun؟
- يبقي ReadyToRun الـ IL ويقدّم جزءاً من عمل JIT فقط، وما زال يستخدم JIT وقت التشغيل في بعض الحالات. التوافق واسع، والميزات الديناميكية تبقى سهلة الاستخدام في الغالب. أمّا Native AOT فلا يفترض JIT وقت التشغيل أصلاً، وتصير مادّة التوزيع ملفاً تنفيذيّاً أصليّاً في الأساس، فيتحسّن الإقلاع بقوّة مقابل قيود أشدّ. حتّى مع كلمة AOT المشتركة، يتّجه ReadyToRun إلى «تخفيف JIT قليلاً»، ويتّجه Native AOT إلى «عدم افتراض JIT»، والمزاج العملي مختلف جدّاً.
- هل يمكن استخدام Native AOT مع تطبيقات WPF أو WinForms؟
- في الوقت الحالي ينبغي النظر إليه بحذر شديد. لا يتضمّن Native AOT على Windows built-in COM، و WPF ضعيف التوافق مع trimming، و WinForms يعتمد بثقل على built-in COM marshalling، لذا لا يناسب أيّ منهما كمرشّح أوّل. إن لزم COM فقد يكون الإبقاء على JIT، أو إعادة التصميم حول ComWrappers / source-generated COM، أوضح. كمدخل، التطبيقات من نوع console و worker و Web API الصغيرة أهدأ.
- أيّ نوع من التطبيقات يناسب Native AOT؟
- يناسب أدوات CLI التي يكون الإقلاع فيها محوريّاً، وواجهات API صغيرة تُنشَر بكثافة في حاويات، وعمال الخلفية والخدمات المقيمة، والعمليات قصيرة العمر أو نمط serverless، ومكوّنات .NET صغيرة تُدرَج في تطبيق أصلي، والحالات التي لا تريد اشتراط تثبيت .NET runtime مسبقاً. القاسم المشترك أن الحدود أوضح، وأن تقليل الآليات الديناميكية أسهل. في المقابل لا يناسب تطبيقاً يقوم على تحميل إضافات وقت التشغيل، ولا تكويناً يعتمد بقوّة على إطار يستكشف الأنواع بالـ Reflection.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.