0Pricing
C# Academy · درس

القصّ وقيود Reflection

تعاملوا مع تحذيرات القصّ، واستخدموا [DynamicallyAccessedMembers]، واستبدلوا Reflection وقت التشغيل ببدائل مولّدة من المصدر.

القصّ وقيود Reflection درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.

ما هو تشذيب IL؟

تشذيب IL هو تحسين يُجرى وقت النشر، ويزيل الأنواع والأساليب والتجميعات غير المستخدمة من الناتج. والنتيجة ملف تنفيذي أصغر حجمًا. ويُفعّل تلقائيًا عند استخدام Native AOT وعند نشر ملف واحد مستقل.

تفعيل التشذيب

فعّلوا التشذيب في ملف المشروع. يمكنكم التحكم في وضع التشذيب بدءًا من partial، الذي يشذّب التجميعات غير المستخدمة فقط، وصولًا إلى full، الذي يشذّب الأعضاء غير المستخدمة داخل التجميعات.

// .csproj
<PropertyGroup>
  <!-- Enable trimming on publish -->
  <PublishTrimmed>true</PublishTrimmed>

  <!-- full: aggressive — trims unused members within assemblies
       partial: conservative — only removes unused assemblies -->
  <TrimMode>full</TrimMode>

  <!-- Treat all trimmer warnings as errors (recommended for CI) -->
  <TrimmerRootAssemblies>MyApp</TrimmerRootAssemblies>
  <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>

// Publish:
dotnet publish -c Release -r linux-x64 --self-contained

كيف يعمل المشذّب

يجري المشذّب تحليلًا لقابلية الوصول بدءًا من نقاط الدخول، مثل Main والخدمات المسجّلة والسمات. وتُزال أي تعليمات برمجية لا يمكن الوصول إليها من نقاط الجذر هذه.

// Example: only MyApp.Program.Main is a root
// Code flow:
//   Main → WebApplication.Run → MapGet → MyHandler
//   Everything else is trimmed

// What gets KEPT:
class MyHandler { public string Handle() => "ok"; }

// What gets TRIMMED (if not reached):
class UnusedService { public void DoWork() { } }

// Problem: reflection can access UnusedService at runtime
// But the trimmer can't know that at compile time
// → UnusedService is trimmed → MissingMethodException at runtime

الانعكاس والتشذيب

الانعكاس هو المصدر الرئيسي لمشكلات التوافق مع التشذيب. لا يستطيع المشذّب تحليل الأنواع التي ستصلون إليها بشكل ثابت عبر Type.GetType() أو Activator.CreateInstance().

// PROBLEMATIC: type name comes from config at runtime
var typeName = config["Plugin:Type"]!;
var type = Type.GetType(typeName);      // type may have been trimmed!
var instance = Activator.CreateInstance(type!); // MissingMethodException

// PROBLEMATIC: LINQ expressions with reflection
var query = dbContext.Set<T>()          // T discovered via reflection
    .Where(BuildExpression<T>("Name", "Alice"));

// PROBLEMATIC: attribute scanning
var handlers = Assembly.GetExecutingAssembly()
    .GetTypes()
    .Where(t => t.HasCustomAttribute<HandlerAttribute>());
// All handler types may be trimmed away

سمة DynamicDependency

تخبر [DynamicDependency] المشذّبَ بالحفاظ على أعضاء محددة تعرفون أنه سيجري الوصول إليها عبر الانعكاس.

// Keep all public constructors of MyPlugin:
[DynamicDependency(
    DynamicallyAccessedMemberTypes.PublicConstructors,
    typeof(MyPlugin))]
public static IPlugin CreatePlugin()
    => (IPlugin)Activator.CreateInstance(typeof(MyPlugin))!;

// Keep a specific method by name:
[DynamicDependency("ProcessOrder", typeof(OrderHandler))]
public static void Bootstrap() { }

// Keep everything on a type (use sparingly):
[DynamicDependency(
    DynamicallyAccessedMemberTypes.All,
    typeof(LegacyReflectionHelper))]
public static void EnsurePreserved() { }

تعليق DynamicallyAccessedMembers

تضع [DynamicallyAccessedMembers] تعليقًا على المعلمات والحقول والخصائص لإخبار المشذّب بالأعضاء التي سيجري الوصول إليها انعكاسيًا في وقت التشغيل من كائن Type.

// Annotate a method parameter:
public void Register(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
    Type serviceType)
{
    // trimmer knows to preserve public constructors of whatever Type is passed
    var instance = Activator.CreateInstance(serviceType)!;
}

// Annotate a generic parameter:
public static T Create<
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
    T>() where T : new()
    => new T();

// Annotate a field:
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
private Type _modelType = typeof(OrderModel);

RequiresUnreferencedCode

ضعوا السمة [RequiresUnreferencedCode] على الأساليب التي تعتمد جوهريًا على الانعكاس. يؤدي ذلك إلى إظهار تحذيرات للمتصلين كي يعرفوا أن الكود قد يفشل عند تشذيبه.

// Mark a method that uses reflection internally:
[RequiresUnreferencedCode("Uses Type.GetType() — not safe for trimming")]
public static object LoadPlugin(string typeName)
{
    var type = Type.GetType(typeName)!
        ?? throw new InvalidOperationException($"Type {typeName} not found");
    return Activator.CreateInstance(type)!;
}

// Callers will see a trimmer warning:
// IL2026: Members decorated with [RequiresUnreferencedCode]
// need their callers to be decorated with the same attribute
// or be suppressed with [UnconditionalSuppressMessage]

// The warning reminds callers to use a trim-safe alternative
// or add a DynamicDependency for the types they expect

بدائل آمنة مع التشذيب

تتوفر في .NET الحديث بدائل آمنة مع التشذيب للعديد من الأنماط التي تعتمد بكثافة على الانعكاس. فضّلوا هذه البدائل لتجنب مشكلات التشذيب.

// INSTEAD OF: Type.GetType(name) + Activator.CreateInstance
// USE: registered factories or switch expressions:
public IHandler CreateHandler(string name) => name switch
{
    "order" => new OrderHandler(),
    "email" => new EmailHandler(),
    _       => throw new ArgumentException($"Unknown: {name}")
};

// INSTEAD OF: reflection-based JSON serialization
// USE: source-generated JsonSerializerContext

// INSTEAD OF: attribute scanning via Assembly.GetTypes()
// USE: compile-time source generators

// INSTEAD OF: dynamic proxy libraries (Castle, DispatchProxy)
// USE: source-generated interceptors (.NET 8+)

واصف XML للتشذيب

استخدموا واصفًا جذريًا بصيغة XML للحفاظ على مساحات أسماء أو تجميعات كاملة لا يستطيع المشذّب تحليلها، مثل مكتبات الجهات الخارجية التي تعتمد بكثافة على الانعكاس.

<!-- TrimmerRoots.xml -->
<linker>
  <!-- Keep everything in LegacyLib -->
  <assembly fullname="LegacyLib">
    <type fullname="LegacyLib.*" preserve="all" />
  </assembly>

  <!-- Keep a specific type -->
  <assembly fullname="MyApp">
    <type fullname="MyApp.Plugins.PluginLoader" preserve="all" />
  </assembly>
</linker>

// Reference in .csproj:
<ItemGroup>
  <TrimmerRootDescriptor Include="TrimmerRoots.xml" />
</ItemGroup>

من الواقع العملي: إصلاح تحذيرات التشذيب

سير عمل عملي لحل تحذيرات المشذّب في قاعدة كود حقيقية.

// Step 1: enable analyzer during development
// .csproj:
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>

// Step 2: build and read warnings:
// warning IL2026: 'MyMapper.MapDynamic(Type)' uses
// [RequiresUnreferencedCode] via 'Type.GetMethod'

// Step 3: fix the root cause:
// BEFORE:
public void MapDynamic(Type t)
{
    var method = t.GetMethod("MapFrom")!; // IL2026
    method.Invoke(null, null);
}

// AFTER: use an interface instead of reflection
public void Map(IMapper mapper) => mapper.MapFrom();

// Step 4: if fix is not feasible, add annotation and document:
[RequiresUnreferencedCode("Uses reflection: ensure target types are preserved")]
public void MapDynamic(Type t) { /* ... */ }

تحقق سريع

ماذا تخبر السمة [DynamicallyAccessedMembers] مشذّب IL؟

مراجعة: قيود التشذيب والانعكاس

أهم النقاط:

  • يزيل التشذيب الكود غير القابل للوصول وقت النشر، ويؤدي الانعكاس إلى تعطيل هذا التحليل
  • يُفعّل باستخدام <PublishTrimmed>true</PublishTrimmed>؛ استخدموا TrimMode=full لتحقيق أكبر قدر من التوفير
  • [DynamicDependency]: يحافظ صراحةً على أعضاء محددة لاستخدامها مع الانعكاس
  • [DynamicallyAccessedMembers]: يضع تعليقات على معلمات Type كي يعرف المشذّب ما يجب الحفاظ عليه
  • [RequiresUnreferencedCode]: يحذّر المتصلين من أن الأسلوب غير آمن مع التشذيب
  • فضّلوا مولدات المصدر والواجهات على الأنماط التي تعتمد بكثافة على الانعكاس

الأسئلة الشائعة

هل درس «القصّ وقيود Reflection» مجاني؟

نعم — نص درس «القصّ وقيود Reflection» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.

ماذا ستتعلم في «القصّ وقيود Reflection»؟

تعاملوا مع تحذيرات القصّ، واستخدموا [DynamicallyAccessedMembers]، واستبدلوا Reflection وقت التشغيل ببدائل مولّدة من المصدر. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟

لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «القصّ وقيود Reflection»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟

نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. الترجمة إلى Native AOT
  2. القصّ وقيود Reflection
  3. ReadyToRun والترجمة متعددة المستويات
  4. قياس الأداء باستخدام BenchmarkDotNet
← العودة إلى C# Academy