ما هي الطريقة الأقل تدخلاً لجعل كود C# مرتبطًا متأخرًا في .NET <4؟

StackOverflow https://stackoverflow.com//questions/9670466

  •  12-12-2019
  •  | 
  •  

سؤال

أنا أعمل على جزء من كود C# الذي يتعامل مع عناصر تحكم Windows Forms.فيما يلي مثال صغير، عبارة عن غلاف صغير للحصول على المستطيل المحيط (في إحداثيات الشاشة) لبعض التحكم:

public class GUIObject {
    protected Control m_control;

    // [..]

    public virtual Rectangle Bounds {
        get {
            Rectangle r = m_control.Bounds;
            if ( m_control.Parent != null ) {
                return m_control.Parent.RectangleToScreen( r );
            }
            return r;
        }
    }
}

يتم تجميع هذا الرمز في مكتبة يتم توزيعها على أنها "مكون إضافي" ليتم تحميلها في تطبيقات العملاء.ومع ذلك، فقد تبين أن بعض العملاء استخدموا إصدارًا مختلفًا من Windows Forms في تطبيقاتهم عن الإصدار الذي تم ربط المكون الإضافي الخاص بي به.كانت خطتي هي معالجة هذه المشكلة عن طريق جعل الكود أعلاه مرتبطًا متأخرًا، بحيث يعمل مع أي إصدار Windows Forms يتم تحميله في مجال التطبيق الحالي.مع .NET 4، يمكنني استخدام dynamic الكلمة الأساسية ولكن للأسف، يجب أن يعمل هذا الرمز مع تطبيقات .NET3 أيضًا.ومن ثم، بدأت باستخدام واجهة برمجة تطبيقات الانعكاس، حيث قدمت كائنًا مساعدًا صغيرًا يجعل استخدام واجهة برمجة تطبيقات الانعكاس أفضل قليلاً:

public class LateBoundObject {
    private Object m_o;

    // [..]

    public Object GetProperty( String name ) {
        PropertyInfo pi = m_o.GetType().GetProperty( name );
        return pi == null ? null
                          : pi.GetValue( m_o, null );
    }

    public Object InvokeMethod( String name, Object[] args ) {
        MethodInfo mi = m_o.GetType().GetMethod( name );
        return mi == null ? null
                          : mi.Invoke( m_o, args );
    }
}

public class GUIObject {
    protected LateBoundObject m_control;

    // [..]

    public virtual Rectangle Bounds {
        get {
            Object r = m_control.GetProperty( "Bounds" );
            if ( r == null) {
                return new Rectangle();
            }

            Object parent = m_control.GetProperty( "Parent" );
            if ( parent != null ) {
                LateBoundObject po = new LateBoundObject( parent );
                r = po.InvokeMethod( "RectangleToScreen",
                                     new Object[] { r } );
            }
            return (Rectangle)r;
        }
    }
}

ليست جميلة جدا.هناك حاجة إلى الكثير من الاختيار من جانب المتصل، وأظن أنني سأضطر إلى التعامل مع الأساليب أو الخصائص المثقلة عاجلاً أم آجلاً أيضًا - إنها رحلة مليئة بالمطبات إلى الأمام.من الناحية المثالية، سيسمح كائن الغلاف بالحفاظ على الكود الأصلي كما هو إلى حد كبير.

لذا، قبل أن أبدأ في إصلاح LateBoundObject فئة المجمع حتى وأتساءل:هل لدى أي شخص آخر خبرة في جعل كود C# مرتبطًا متأخرًا باستخدام واجهة برمجة تطبيقات الانعكاس؟إذا كان الأمر كذلك، فكيف تعاملت مع الأمر لتقليل معاناة استخدام واجهة برمجة تطبيقات الانعكاس الخام إلى الحد الأدنى - هل استخدمت أيضًا فئة مجمعة على غرار LateBoundObject أم أنك سلكت طريقًا مختلفًا تمامًا؟أنا أبحث عن الطريقة الأقل تدخلاً فيما يتعلق بالرمز الأصلي.

هل كانت مفيدة؟

المحلول

تتمثل إحدى الأفكار في إنشاء واجهات لما تريد أن تبدو عليه الكائنات ثم استخدام System.Reflection.Emit لإنشاء فئات يمكنها استيعاب المثيل الفعلي.يمكنك القيام بذلك عن طريق تغليفه في كائن تم إنشاؤه ديناميكيًا يقوم باستدعاء الوكلاء من أساليب الواجهة الخاصة به إلى المثيل الفعلي الذي يقوم بتغليفه.

سيبدو الاستخدام شيئًا مثل هذا:

interface IGUIObject 
{
  Rectangle Bounds { get; }
  Rectangle RectangleToScreen(Rectangle bounds);
  IGUIObject Parent { get; }
}

var obj = GetInstance();
var proxy = Reflection.Coerce<IGUIObject>(obj);
return proxy.Parent.RectangleToScreen(proxy.Bounds);

لدي تدوينة هنا تحتوي على نقطة بداية بسيطة لكيفية القيام بالإكراه الديناميكي، بما في ذلك نموذج التطبيق: أنواع الإكراه وتجميعات التفريغ

الأمر المثير للاهتمام هو أنه باستخدام هذه التقنية يمكنك بالفعل التخلص من انعكاس كل مكالمة، وهو أداء مكلف للغاية.بدلاً من ذلك، يمكنك إجراء الانعكاس مرة واحدة في منشئ الوكيل وما تقوم بإنشائه يستدعي في الواقع الخاصية/الطريقة/الحقل المقابل مباشرة بعد ذلك.باستخدام هذه الخدعة أيضًا، سيتم إلغاء تحميل التجميع الديناميكي الذي تم إنشاؤه عندما تقوم بإسقاط المرجع إلى مثيل الوكيل.يمكنك تخزين النوع الذي تم إنشاؤه مؤقتًا لإجراء عمليات إنشاء الوكيل اللاحقة بسرعة كبيرة.

إن موقفك أكثر تعقيدًا من عينتي الصغيرة، لكنني أعتقد أنه يمكنك المضي قدمًا فيه كنقطة بداية.

نصائح أخرى

انا لم احصل عليها.أقوم بتمرير عناصر تحكم .NET 4 إلى ملفات dll التي تم تجميعها مقابل .NET 2 وهي تعمل بشكل جيد.

استخدم الامتدادات المساعدة للتفكير:

 var r = m_control._P<Rectangle>("Bounds") ?? new Rectangle();
 var parent = m_control._P<Control>("Parent");
 if (parent != null)
   r = parent._M<Rectangle>("RectangleToScreen", r);



static public class ReflectionHlp2
{
  public static T _P<T>(this object item, string name)
  {
    if (item == null)
      return default(T);
    var type = item.GetType();

    var members = type.GetMembers(BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
      .Where(_member => _member.Name == name)
      .ToArray();
    if (members.Length == 0)
      return default(T);
    if (members.Length > 1)
      throw new Exception(string.Format("У объекта полей/свойств с именем '{0}' больше чем один: '{1}'", name, members.Length));
    var member = members.First();
    object result;
    if (member is FieldInfo)
      result = ((FieldInfo)member).GetValue(item);
    else
      result = ((PropertyInfo)member).GetValue(item, null);
    if (result is T)
      return (T)result;
    return default(T);
  }
  public static void _P<T>(this object item, string name, T value)
  {
    if (item == null)
      return;
    var type = item.GetType();

    var members = type.GetMembers(BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
      .Where(_member => _member.Name == name)
      .ToArray();
    if (members.Length == 0)
      return;
    if (members.Length > 1)
      throw new Exception(string.Format("У объекта полей/свойств с именем '{0}' больше чем один: '{1}'", name, members.Length));
    var member = members.First();
    if (member is FieldInfo)
      ((FieldInfo)member).SetValue(item, value);
    else
      ((PropertyInfo)member).SetValue(item, value, null);
  }
  public static void _M(this object item, string name, params object[] args)
  {
    _M<object>(item, name, args);
  }
  public static T _M<T>(this object item, string name, params object[] args)
  {
    if (item == null)
      return default(T);
    var type = item.GetType();

    var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
      .Where(_member => _member.Name == name)
      .ToArray();
    if (methods.Length == 0)
      return default(T);
    if (methods.Length > 1)
      throw new Exception(string.Format("Вызов перегруженных методов не поддерживается, у объекта методов с именем '{0}' больше чем один: '{1}'.", name, methods.Length));
    var method = methods.First();
    var result = method.Invoke(item, args);
    if (result is T)
      return (T)result;
    return default(T);
  }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top