لماذا لا يوجد ICloneable<T>؟
-
22-08-2019 - |
سؤال
هل هناك سبب معين لماذا عام ICloneable<T>
غير موجود؟
سيكون الأمر أكثر راحة، إذا لم أكن بحاجة إلى إلقاءه في كل مرة أقوم فيها باستنساخ شيء ما.
المحلول
ويعتبر ICloneable على API سيئة الآن، لأنه لا يحدد ما إذا كانت النتيجة هي عميقة أو ضحلة نسخة. وأعتقد أن هذا هو السبب في أنها لا تحسن هذه الواجهة.
وربما يمكنك أن تفعل كتبته استنساخ طريقة التمديد، ولكن أعتقد أنه يحتاج إلى اسم مختلف منذ أساليب الإرشاد لها الأولوية أقل من تلك الأصلي.
نصائح أخرى
وبالإضافة إلى الرد أندري (الذي أنا أتفق مع، +1) - عندما ICloneable
<م> هو م> القيام به، ويمكنك أيضا اختيار تنفيذ صريح لجعل Clone()
الجمهور إرجاع كائن أدخلته:
public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
وبالطبع هناك المسألة الثانية مع ICloneable<T>
عام - الميراث
إذا كنت لديك:
public class Foo {}
public class Bar : Foo {}
وأنا نفذت ICloneable<T>
، ثم أقوم بتنفيذ ICloneable<Foo>
؟ ICloneable<Bar>
؟ لك بسرعة البدء في تنفيذ الكثير من واجهات متطابقة ...
مقارنة يلقي ... وهل هو حقا بهذا السوء؟
أريد أن أسأل، ما الذي ستفعله بالضبط بالواجهة غير ذلك تنفيذه؟عادةً ما تكون الواجهات مفيدة فقط عندما تقوم بالإرسال إليها (على سبيل المثال، هل تدعم هذه الفئة "IBar")، أو عندما تحتوي على معلمات أو أدوات ضبط تقبلها (أي أنني آخذ "IBar").باستخدام ICloneable - مررنا بإطار العمل بأكمله وفشلنا في العثور على استخدام واحد في أي مكان كان شيئًا آخر غير تنفيذه.لقد فشلنا أيضًا في العثور على أي استخدام في "العالم الحقيقي" يقوم أيضًا بشيء آخر غير تنفيذه (في حوالي 60.000 تطبيق يمكننا الوصول إليها).
الآن، إذا كنت ترغب فقط في فرض نمط تريد أن تنفذه الكائنات "القابلة للاستنساخ"، فهذا استخدام جيد تمامًا - والمضي قدمًا.يمكنك أيضًا أن تقرر بالضبط ما يعنيه "الاستنساخ" بالنسبة لك (أي عميق أو سطحي).ومع ذلك، في هذه الحالة، ليست هناك حاجة لنا (BCL) لتحديده.نحن نحدد التجريدات في BCL فقط عندما تكون هناك حاجة لتبادل المثيلات المكتوبة على هذا النحو التجريدي بين المكتبات غير ذات الصلة.
ديفيد كين (فريق بي سي إل)
وأعتقد أن السؤال "لماذا" هو لا داعي لها. هناك الكثير من واجهات / فئات / الخ ... وهي مفيدة جدا، ولكن ليست جزءا من مكتبة قاعدة. NET Frameworku.
ولكن، أساسا يمكنك أن تفعل ذلك بنفسك.
public interface ICloneable<T> : ICloneable {
new T Clone();
}
public abstract class CloneableBase<T> : ICloneable<T> where T : CloneableBase<T> {
public abstract T Clone();
object ICloneable.Clone() { return this.Clone(); }
}
public abstract class CloneableExBase<T> : CloneableBase<T> where T : CloneableExBase<T> {
protected abstract T CreateClone();
protected abstract void FillClone( T clone );
public override T Clone() {
T clone = this.CreateClone();
if ( object.ReferenceEquals( clone, null ) ) { throw new NullReferenceException( "Clone was not created." ); }
return clone
}
}
public abstract class PersonBase<T> : CloneableExBase<T> where T : PersonBase<T> {
public string Name { get; set; }
protected override void FillClone( T clone ) {
clone.Name = this.Name;
}
}
public sealed class Person : PersonBase<Person> {
protected override Person CreateClone() { return new Person(); }
}
public abstract class EmployeeBase<T> : PersonBase<T> where T : EmployeeBase<T> {
public string Department { get; set; }
protected override void FillClone( T clone ) {
base.FillClone( clone );
clone.Department = this.Department;
}
}
public sealed class Employee : EmployeeBase<Employee> {
protected override Employee CreateClone() { return new Employee(); }
}
وانه من السهل جدا ل كتابة واجهة نفسك إذا كنت في حاجة إليها:
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
بعد أن قرأت المقال مؤخرًا لماذا يعد نسخ كائن أمرًا فظيعًا؟, أعتقد أن هذا السؤال يحتاج إلى توضيح إضافي.توفر الإجابات الأخرى هنا نصائح جيدة، ولكن لا تزال الإجابة غير كاملة - لماذا لا ICloneable<T>
?
الاستخدام
لذلك، لديك فئة تنفذ ذلك.بينما في السابق كان لديك الطريقة التي تريدها
ICloneable
, ، يجب أن يكون الآن عامًا لقبولهICloneable<T>
.سوف تحتاج إلى تحريره.بعد ذلك، كان من الممكن أن يكون لديك طريقة تتحقق مما إذا كان هناك كائن
is ICloneable
.ماذا الان؟لا يمكنك أن تفعلis ICloneable<>
وبما أنك لا تعرف نوع الكائن في نوع الترجمة، فلا يمكنك جعل الطريقة عامة.أول مشكلة حقيقيةلذلك عليك أن يكون لديك كليهما
ICloneable<T>
وICloneable
, ، الأول ينفذ الأخير.وبالتالي سيحتاج المنفذ إلى تنفيذ كلتا الطريقتين -object Clone()
وT Clone()
.لا، شكرًا، لقد استمتعنا بالفعل بما يكفيIEnumerable
.وكما أشرنا سابقًا، هناك أيضًا تعقيد الميراث.في حين أن التغاير قد يبدو وكأنه يحل هذه المشكلة، إلا أن النوع المشتق يحتاج إلى التنفيذ
ICloneable<T>
من نوعه الخاص، ولكن هناك بالفعل طريقة بنفس التوقيع (= المعلمات، بشكل أساسي) -Clone()
من الطبقة الأساسية.إن جعل واجهة طريقة الاستنساخ الجديدة واضحة لا معنى له، فسوف تفقد الميزة التي كنت تسعى إليها عند الإنشاءICloneable<T>
.لذا أضفnew
الكلمة الرئيسية.لكن لا تنس أنك ستحتاج أيضًا إلى تجاوز الفئة الأساسية.Clone()
(يجب أن يظل التنفيذ موحدًا لجميع الفئات المشتقة، أي.لإرجاع نفس الكائن من كل طريقة استنساخ، لذلك يجب أن تكون طريقة الاستنساخ الأساسية كذلكvirtual
)!لكن لسوء الحظ، لا يمكنك القيام بالأمرين معًاoverride
وnew
طرق بنفس التوقيع.باختيار الكلمة الرئيسية الأولى، ستفقد الهدف الذي أردت تحقيقه عند الإضافةICloneable<T>
.باختيار الخيار الثاني، فإنك ستكسر الواجهة نفسها، مما يجعل الطرق التي يجب أن تفعل الشيء نفسه تعيد كائنات مختلفة.نقطة
انت تريد
ICloneable<T>
من أجل الراحة، ولكن الراحة ليست ما تم تصميم الواجهات من أجله، فمعناها (بشكل عام OOP) هو توحيد سلوك الكائنات (على الرغم من أنه في C# يقتصر على توحيد السلوك الخارجي، على سبيل المثال.الأساليب والخصائص، وليس عملها).إذا لم يقنعك السبب الأول بعد، فيمكنك الاعتراض على ذلك
ICloneable<T>
يمكن أيضًا أن تعمل بشكل مقيد للحد من النوع الذي يتم إرجاعه من طريقة الاستنساخ.ومع ذلك، يمكن للمبرمج سيئة تنفيذICloneable<T>
حيث T ليس من النوع الذي ينفذه.لذا، لتحقيق التقييد الخاص بك، يمكنك إضافة قيد لطيف إلى المعلمة العامة:
public interface ICloneable<T> : ICloneable where T : ICloneable<T>
بالتأكيد أكثر تقييدا من الذي لاwhere
, ، مازلت لا تستطيع تقييد ذلك ت هو النوع الذي يقوم بتنفيذ الواجهة (يمكنك استخلاصه منICloneable<T>
من نوع مختلف ينفذها).كما ترون، حتى هذا الهدف لا يمكن تحقيقه (الأصل
ICloneable
فشل أيضًا في هذا، لا يمكن لأي واجهة أن تحد حقًا من سلوك فئة التنفيذ).
كما ترون، هذا يثبت أن جعل الواجهة العامة أمراً صعب التنفيذ بالكامل وغير ضروري وغير مفيد.
ولكن بالعودة إلى السؤال، ما تسعى إليه حقًا هو الشعور بالراحة عند استنساخ كائن ما.هناك طريقتان للقيام بذلك:
طرق إضافية
public class Base : ICloneable
{
public Base Clone()
{
return this.CloneImpl() as Base;
}
object ICloneable.Clone()
{
return this.CloneImpl();
}
protected virtual object CloneImpl()
{
return new Base();
}
}
public class Derived : Base
{
public new Derived Clone()
{
return this.CloneImpl() as Derived;
}
protected override object CloneImpl()
{
return new Derived();
}
}
يوفر هذا الحل الراحة والسلوك المقصود للمستخدمين، ولكنه أيضًا يستغرق وقتًا طويلاً جدًا للتنفيذ.إذا لم نرغب في استخدام الطريقة "المريحة" لإرجاع النوع الحالي، فمن الأسهل بكثير أن يكون لدينا فقط public virtual object Clone()
.
لذلك دعونا نرى الحل "النهائي" - ما الذي يهدف حقًا في C# إلى منحنا الراحة؟
طرق التمديد!
public class Base : ICloneable
{
public virtual object Clone()
{
return new Base();
}
}
public class Derived : Base
{
public override object Clone()
{
return new Derived();
}
}
public static T Copy<T>(this T obj) where T : class, ICloneable
{
return obj.Clone() as T;
}
لقد تم تسميتها ينسخ لكي لا يصطدم بالتيار استنساخ الأساليب (يفضل المترجم الأساليب المعلنة الخاصة بالنوع على الأساليب الملحقة).ال class
يوجد قيد على السرعة (لا يتطلب التحقق من القيمة الخالية وما إلى ذلك).
آمل أن يوضح هذا سبب عدم القيام بذلك ICloneable<T>
.ومع ذلك، فمن المستحسن عدم التنفيذ ICloneable
على الاطلاق.
على الرغم من أن السؤال قديم جدًا (5 سنوات من كتابة هذه الإجابات :) وتمت الإجابة عليه بالفعل، إلا أنني وجدت هذا المقال يجيب على السؤال جيدًا، تحقق منه هنا
يحرر:
إليكم الاقتباس من المقال الذي يجيب على السؤال (تأكد من قراءة المقال كاملاً، فهو يتضمن أشياء أخرى مثيرة للاهتمام):
هناك العديد من المراجع على الإنترنت تشير إلى منشور مدونة لعام 2003 من تأليف براد أبرامز - في ذلك الوقت الذي يعمل في Microsoft - حيث تتم مناقشة بعض الأفكار حول iClonable.يمكن العثور على إدخال المدونة في هذا العنوان: تنفيذ ICloneable.رغم التضليل العنوان ، يدعو إدخال المدونة هذا إلى عدم تنفيذ ICloneable ، بشكل أساسي بسبب الارتباك الضحل / العميق.المادة تنتهي في مستقيم اقتراح:إذا كنت بحاجة إلى آلية استنساخ ، فحدد استنساخ خاص بك ، أو انسخ المنهجية، وتأكد من توثيق ما إذا كانت نسخة عميقة أو ضحلة.النمط المناسب هو:
public <type> Copy();
وهناك مشكلة كبيرة هي أنها لا يمكن أن تقيد T أن تكون نفس الفئة. مثال الصدارة ما يمكن أن تمنعك من القيام بذلك:
interface IClonable<T>
{
T Clone();
}
class Dog : IClonable<JackRabbit>
{
//not what you would expect, but possible
JackRabbit Clone()
{
return new JackRabbit();
}
}
وانهم في حاجة الى تقييد المعلمة مثل:
interfact IClonable<T> where T : implementing_type
وانها مسألة جيدة جدا ... هل يمكن أن تجعل الخاصة بك، على الرغم من:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
وأندري تقول انها تعتبر واجهة برمجة تطبيقات سيئة، ولكن لم اسمع شيئا عن هذه الواجهة تصبح مهملة. والتي من شأنها كسر طن من واجهات ... طريقة استنساخ يجب إجراء نسخة الضحلة. إذا كان الكائن كما يوفر نسخة عميق، وهو استنساخ زائد (عميق منطقي) يمكن استخدامها.
وتحرير: نمط يمكنني استخدام ل "استنساخ" كائن، يمر النموذج في منشئ
class C
{
public C ( C prototype )
{
...
}
}
وهذا يزيل أي حالات تنفيذ كود زائدة عن الحاجة المحتملة. راجع للشغل، والحديث حول قيود ICloneable، أليس كذلك حقا حتى الكائن نفسه أن يقرر ما إذا كان استنساخ الضحلة أو استنساخ عميق، أو حتى الضحلة جزئيا / استنساخ عميق جزئيا، يجب أن يتم تنفيذ؟ علينا أن نهتم حقا، ما دام يعمل الكائن على النحو المنشود؟ في بعض المناسبات، وتنفيذ استنساخ جيدة ويمكن أن تشمل بشكل جيد للغاية على حد سواء الاستنساخ الضحلة والعميقة.