كيف يمكنك أن تطلب مُنشئًا بدون معلمات للأنواع التي تنفذ الواجهة؟

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

سؤال

هل هناك طريقة؟

أحتاج إلى أن يكون لدى جميع الأنواع التي تنفذ واجهة معينة مُنشئ بدون معلمات، هل يمكن القيام بذلك؟

أقوم بتطوير الكود الأساسي للمطورين الآخرين في شركتي لاستخدامه في مشروع معين.

هناك عملية ستنشئ مثيلات من الأنواع (في سلاسل مختلفة) التي تؤدي مهام معينة، وأحتاج إلى أن تتبع هذه الأنواع عقدًا محددًا (وبالتالي، الواجهة).

ستكون الواجهة داخلية للتجميع

إذا كان لديك اقتراح لهذا السيناريو بدون واجهات، فسأأخذه بعين الاعتبار بكل سرور...

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

المحلول

وقال خوان مانويل:

هذا أحد الأسباب التي تجعلني لا أفهم سبب عدم إمكانية أن يكون جزءًا من العقد في الواجهة

إنها آلية غير مباشرة.يتيح لك العام "الغش" وإرسال معلومات النوع مع الواجهة.الشيء المهم الذي يجب تذكره هنا هو أن القيد ليس موجودًا على الواجهة التي تعمل معها مباشرةً.إنه ليس قيدًا على الواجهة نفسها، ولكن على نوع آخر "سيستمر" على الواجهة.أخشى أن هذا هو أفضل تفسير يمكنني تقديمه.

على سبيل التوضيح لهذه الحقيقة، سأشير إلى الثغرة التي لاحظتها في كود أكو.من الممكن كتابة فصل دراسي يتم تجميعه بشكل جيد ولكنه يفشل في وقت التشغيل عندما تحاول إنشاء مثيل له:

public class Something : ITest<String>
{
  private Something() { }
}

شيء ما مشتق من ITest<T>، لكنه لا يطبق مُنشئًا بدون معلمات.سيتم تجميعه بشكل جيد، لأن String تنفذ مُنشئًا بدون معلمات.مرة أخرى، القيد موجود على T، وبالتالي على String، بدلاً من ITest أو Something.بما أن القيد على T قد تم استيفاءه، فسيتم تجميع هذا.لكنها سوف تفشل في وقت التشغيل.

كى تمنع بعض في حالات هذه المشكلة، تحتاج إلى إضافة قيد آخر إلى T، على النحو التالي:

public interface ITest<T>
  where T : ITest<T>, new()
{
}

لاحظ القيد الجديد:ت :اختبار <T>.يحدد هذا القيد ما تقوم بتمريره إلى معلمة الوسيطة ITest<T> يجب أيضًا استخلاص من ITest<T>.

ومع ذلك فإن هذا لن يمنع الجميع حالات الحفرة.سيتم تجميع الكود أدناه بشكل جيد، لأن A لديه مُنشئ بدون معلمات.ولكن بما أن مُنشئ B بدون معلمات خاص، فإن إنشاء مثيل B مع عمليتك سوف يفشل في وقت التشغيل.

public class A : ITest<A>
{
}

public class B : ITest<A>
{
  private B() { }
}

نصائح أخرى

لا أريد أن أكون صريحًا جدًا، لكنك أسيء فهم الغرض من الواجهات.

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

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

خوان,

لسوء الحظ، لا توجد طريقة للتغلب على هذا الأمر بلغة مكتوبة بقوة.لن تتمكن في وقت الترجمة من التأكد من إمكانية إنشاء مثيل للفئات بواسطة التعليمات البرمجية المستندة إلى المنشط.

(إد:إزالة حل بديل خاطئ)

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

البنائين والأساليب الثابتة يجب لديك معلومات صريحة (موجود في الكود) متاحة في وقت المكالمة.يعد هذا مطلوبًا لأنه لا يوجد مثيل للفئة المعنية والتي يمكن الاستعلام عنها بواسطة وقت التشغيل للحصول على النوع الأساسي، والذي يحتاجه وقت التشغيل لتحديد الطريقة الملموسة الفعلية التي سيتم الاتصال بها.

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

يمكنك استخدام نوع قيود المعلمة

interface ITest<T> where T: new()
{
    //...
}

class Test: ITest<Test>
{
    //...
}

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

public interface IInterface
{
    void DoSomething();
}

public class Foo : IInterface
{
    public void DoSomething() { /* whatever */ }
}

يعد استخدام النوع أمرًا قبيحًا جدًا، ولكنه منطقي في بعض السيناريوهات:

public IInterface CreateUsingType(Type thingThatCreates)
{
    ConstructorInfo constructor = thingThatCreates.GetConstructor(Type.EmptyTypes);
    return (IInterface)constructor.Invoke(new object[0]);
}

public void Test()
{
    IInterface thing = CreateUsingType(typeof(Foo));
}

أكبر مشكلة في ذلك هي أنه في وقت الترجمة، ليس لديك أي ضمان بأن Foo بالفعل لديه منشئ افتراضي.أيضًا، يكون الانعكاس بطيئًا بعض الشيء إذا كان هذا رمزًا مهمًا للأداء.

الحل الأكثر شيوعًا هو استخدام المصنع:

public interface IFactory
{
    IInterface Create();
}

public class Factory<T> where T : IInterface, new()
{
    public IInterface Create() { return new T(); }
}

public IInterface CreateUsingFactory(IFactory factory)
{
    return factory.Create();
}

public void Test()
{
    IInterface thing = CreateUsingFactory(new Factory<Foo>());
}

في ما سبق، IFactory هو ما يهم حقًا.المصنع هو مجرد فئة ملائمة للفصول التي يفعل توفير منشئ افتراضي.هذا هو الحل الأبسط والأفضل في كثير من الأحيان.

الحل الثالث غير الشائع حاليًا ولكن من المحتمل أن يصبح أكثر شيوعًا هو استخدام المفوض:

public IInterface CreateUsingDelegate(Func<IInterface> createCallback)
{
    return createCallback();
}

public void Test()
{
    IInterface thing = CreateUsingDelegate(() => new Foo());
}

الميزة هنا هي أن الكود قصير وبسيط، ويمكن العمل به أي طريقة البناء، و(مع عمليات الإغلاق) تتيح لك تمرير البيانات الإضافية اللازمة لإنشاء الكائنات بسهولة.

قم باستدعاء أسلوب RegisterType بالنوع، وقم بتقييده باستخدام الأدوية العامة.بعد ذلك، بدلاً من السير في التجميعات للعثور على منفذي ITest، ما عليك سوى تخزينها وإنشاءها من هناك.

void RegisterType<T>() where T:ITest, new() {
}

أنا لا أعتقد ذلك.

لا يمكنك أيضًا استخدام فئة مجردة لهذا الغرض.

وأود أن أذكر الجميع بأن:

  1. من السهل كتابة السمات في .NET
  2. من السهل كتابة أدوات التحليل الثابتة في .NET التي تضمن التوافق مع معايير الشركة

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

اللغة، والمترجم، وIDE، وعقلك - كلها أدوات.استخدمهم!

لا، لا يمكنك فعل ذلك.ربما بالنسبة لموقفك قد تكون واجهة المصنع مفيدة؟شيء مثل:

interface FooFactory {
    Foo createInstance();
}

لكل تطبيق لـ Foo، يمكنك إنشاء مثيل لـ FooFactory يعرف كيفية إنشائه.

لا تحتاج إلى مُنشئ بدون معلمات حتى يتمكن المنشط من إنشاء مثيل لفصلك.يمكن أن يكون لديك مُنشئ ذو معلمات وتمرير كافة المعلمات من المنشط.الدفع MSDN على هذا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top