هل من الممكن تعيين كائن فئة أساسية إلى مرجع فئة مشتقة مع Typecast صريح؟

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

سؤال

هل من الممكن تعيين كائن فئة أساسية إلى مرجع فئة مشتقة مع Typecast صريح في C #؟

لقد جربتها ويخلق خطأ وقت التشغيل.

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

المحلول

لا. يجب أن تشير الإشارة إلى فئة مشتقة بالفعل إلى مثيل للفئة المشتقة (أو NULL). وإلا كيف تتوقع أن تتصرف؟

علي سبيل المثال:

object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?

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

نصائح أخرى

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

يمكنك كتابة منشئ في الفئة المشتقة بأخذ كائن فئة أساسية كمعلمة، ونسخ القيم.

شيء من هذا القبيل:

public class Base {
    public int Data;

    public void DoStuff() {
        // Do stuff with data
    }
}

public class Derived : Base {
    public int OtherData;

    public Derived(Base b) {
        this.Data = b.Data;
        OtherData = 0; // default value
    }

    public void DoOtherStuff() {
        // Do some other stuff
    }
}

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

Base b = new Base();
Dervided d = new Derived();

b.DoStuff();    // OK
d.DoStuff();    // Also OK
b.DoOtherStuff();    // Won't work!
d.DoOtherStuff();    // OK

d = new Derived(b);  // Copy construct a Derived with values of b
d.DoOtherStuff();    // Now works!

كان لدي هذه المشكلة وحلها عن طريق إضافة طريقة تأخذ معلمة نوع وتحويل الكائن الحالي إلى هذا النوع.

public TA As<TA>() where TA : Base
{
    var type = typeof (TA);
    var instance = Activator.CreateInstance(type);

     PropertyInfo[] properties = type.GetProperties();
     foreach (var property in properties)
     {
         property.SetValue(instance, property.GetValue(this, null), null);
     }

     return (TA)instance;
}

هذا يعني أنه يمكنك استخدامه في كود مثل هذا:

var base = new Base();
base.Data = 1;
var derived = base.As<Derived>();
Console.Write(derived.Data); // Would output 1

كما أجاب العديد من الآخرين، رقم

يمكنني استخدام التعليمات البرمجية التالية على تلك المناسبات المؤسفة عندما أحتاج إلى استخدام نوع أساسي كنوع مشتق. نعم إنه انتهاك لمبدأ استبدال Liskov (LSP) ونعم معظم الوقت الذي نفضل تكوينه على الميراث. الدعائم ل Markus Knappen Johansson التي تعتمد إجابتها الأصلية.

هذا الرمز في الفئة الأساسية:

    public T As<T>()
    {
        var type = typeof(T);
        var instance = Activator.CreateInstance(type);

        if (type.BaseType != null)
        {
            var properties = type.BaseType.GetProperties();
            foreach (var property in properties)
                if (property.CanWrite)
                    property.SetValue(instance, property.GetValue(this, null), null);
        }

        return (T) instance;
    }

يسمح:

    derivedObject = baseObect.As<derivedType>()

لأنه يستخدم التفكير، فإنه "باهظ الثمن". استخدام وفقا لذلك.

لا، ليس من الممكن، وبالتالي خطأ وقت التشغيل الخاص بك.

ولكن يمكنك تعيين مثيل فئة مشتقة إلى متغير من نوع الفئة الأساسية.

كما قال الجميع هنا، هذا غير ممكن مباشرة.

الطريقة التي أفضلها وهي نظيفة إلى حد ما، هي استخدام mapper كائن مثل Automapper..

ستقوم بمهمة نسخ الخصائص من مثيل إلى آخر (وليس بالضرورة نفس النوع) تلقائيا.

يمكنك إلقاء عامل يتم كتابةها كصف قاعدة إلى نوع فئة مشتقة؛ ومع ذلك، من خلال الضرورة أن يؤدي ذلك إلى فحص وقت التشغيل، لمعرفة ما إذا كان الكائن الفعلي المعني من النوع الصحيح.

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

التوسع في إجابة @ ybo - لا يمكن ذلك لأن المثيل الذي لديك من الفئة الأساسية ليس في الواقع مثيل للفئة المشتقة. يعرف فقط عن أعضاء الفصل الأساسي، ولا يعرفون أي شيء عن تلك الموجودة في الفئة المشتقة.

السبب في أنه يمكنك إلقاء مثيل الفئة المشتقة بمثابة الفئة الأساسية هو أن الفئة المشتقة بالفعل هي بالفعل مثيل للفئة الأساسية، لأنه يحتوي على هؤلاء الأعضاء بالفعل. العكس لا يمكن قوله.

لا، لم يكن ممكنا.

النظر في سيناريو حيث يوجد ACBUS فئة مشتقة من حافلة الطبقة الأساسية. يحتوي ACBus على ميزات مثل Turnonac و REGULFAC التي تعمل على حقل يدعى acstate. تقوم Turnonac بتعيين acstate to and turnoffac يحدد acstate لإيقاف التشغيل. إذا حاولت استخدام ميزات Turnonac و Turnoffac على الحافلة، فهذا لا معنى له.

class Program
{
    static void Main(string[] args)
    {
        a a1 = new b();  
        a1.print();  
    }
}
class a
{
    public a()
    {
        Console.WriteLine("base class object initiated");
    }
    public void print()
    {
        Console.WriteLine("base");
    }
}
class b:a
{
    public b()
    {
        Console.WriteLine("child class object");
    }
    public void print1()
    {
        Console.WriteLine("derived");
    }
}

}

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

ولكن ليس العكس لأن المتغير المرجعي لفئة الطفل لا يمكن أن يشير إلى كائن الفئة الأساسية لأنه لا يتم إنشاء كائن فئة للطفل.

ولاحظ أيضا أن المتغير المرجعي للفئة الأساسية لا يمكن إلا أن يدعو عضو في الفئة الأساسية.

في الواقع هناك طريقة للقيام بذلك. فكر في كيفية استخدامك Newtonsoft JSON لتحمل كائن من JSON. سوف يتجاهل ذلك (أو على الأقل) عناصر مفقودة وملء جميع العناصر التي تعرفها.

حتى هنا كيف فعلت ذلك. سوف تتبع عينة رمز صغيرة تفسيري.

  1. قم بإنشاء مثيل لكائنك من الفئة الأساسية وملءه وفقا لذلك.

  2. باستخدام فئة "JsonConvert" من Newtonsoft JSON، تسلسل هذا الكائن في سلسلة JSON.

  3. قم الآن بإنشاء كائن الفئة الفرعية الخاصة بك عن طريق التحيز باستخدام سلسلة JSON التي تم إنشاؤها في الخطوة 2. سيؤدي ذلك إلى إنشاء مثيل من فئتك الفرعية مع جميع خصائص الفئة الأساسية.

هذا يعمل مثل سحر! لذلك .. متى يكون هذا مفيدا؟ سأل بعض الأشخاص متى سيؤدي ذلك إلى معنى واقترح تغيير مخطط OP لاستيعاب حقيقة أنه لا يمكنك القيام بذلك بطريقة مذهلة مع ميراث الطبقة (في .NET).

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

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

رمز المثال هذا هو vb.net، ولكن يمكنك تحويلها بسهولة إلى C #.

' First, create the base settings object.
    Dim basePMSettngs As gtmaPayMethodSettings = gtmaPayments.getBasePayMethodSetting(payTypeId, account_id)
    Dim basePMSettingsJson As String = JsonConvert.SerializeObject(basePMSettngs, Formatting.Indented)

    ' Create a pmSettings object of this specific type of payment and inherit from the base class object
    Dim pmSettings As gtmaPayMethodAimACHSettings = JsonConvert.DeserializeObject(Of gtmaPayMethodAimACHSettings)(basePMSettingsJson)

قد لا تكون ملاءمة، لكنني كنت قادرا على تشغيل التعليمات البرمجية على كائن مشتق بالنظر إلى قاعدته. إنها بالتأكيد مزخرفة مما أردت، لكنها تعمل:

public static T Cast<T>(object obj)
{
    return (T)obj;
}

...

//Invoke parent object's json function
MethodInfo castMethod = this.GetType().GetMethod("Cast").MakeGenericMethod(baseObj.GetType());
object castedObject = castMethod.Invoke(null, new object[] { baseObj });
MethodInfo jsonMethod = baseObj.GetType ().GetMethod ("ToJSON");
return (string)jsonMethod.Invoke (castedObject,null);

يمكنك استخدام Extention:

public static void CopyOnlyEqualProperties<T>(this T objDest, object objSource) where T : class
    {
        foreach (PropertyInfo propInfo in typeof(T).GetProperties())
            if (objSource.GetType().GetProperties().Any(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()))
                propInfo.SetValue(objDest, objSource.GetType().GetProperties().First(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()).GetValue(objSource));
    }

في الرمز:

public class BaseClass
{
  public string test{ get; set;}
}
public Derived : BaseClass
{
//Some properies
}

public void CopyProps()
{
   BaseClass baseCl =new BaseClass();
   baseCl.test="Hello";
   Derived drv=new Derived();
   drv.CopyOnlyEqualProperties(baseCl);
   //Should return Hello to the console now in derived class.
   Console.WriteLine(drv.test);

}

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

   private void PopulateDerivedFromBase<TB,TD>(TB baseclass,TD derivedclass)
    {
        //get our baseclass properties
        var bprops = baseclass.GetType().GetProperties();
        foreach (var bprop in bprops)
        {
            //get the corresponding property in the derived class
            var dprop = derivedclass.GetType().GetProperty(bprop.Name);
            //if the derived property exists and it's writable, set the value
            if (dprop != null && dprop.CanWrite)
                dprop.SetValue(derivedclass,bprop.GetValue(baseclass, null),null);
        }
    } 

حل مع JSONConvert (بدلا من typecast)

واجهت اليوم نفس القضية ووجدت بسيطة و حل سريع للمشكلة استخدام JsonConvert.

var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);

حل آخر هو إضافة طريقة التمديد مثل ذلك:

 public static void CopyProperties(this object destinationObject, object sourceObject, bool overwriteAll = true)
        {
            try
            {
                if (sourceObject != null)
                {
                    PropertyInfo[] sourceProps = sourceObject.GetType().GetProperties();
                    List<string> sourcePropNames = sourceProps.Select(p => p.Name).ToList();
                    foreach (PropertyInfo pi in destinationObject.GetType().GetProperties())
                    {
                        if (sourcePropNames.Contains(pi.Name))
                        {
                            PropertyInfo sourceProp = sourceProps.First(srcProp => srcProp.Name == pi.Name);
                            if (sourceProp.PropertyType == pi.PropertyType)
                                if (overwriteAll || pi.GetValue(destinationObject, null) == null)
                                {
                                    pi.SetValue(destinationObject, sourceProp.GetValue(sourceObject, null), null);
                                }
                        }
                    }
                }
            }
            catch (ApplicationException ex)
            {
                throw;
            }
        }

ثم لديك منشئ في كل فئة مشتقة يقبل الطبقة الأساسية:

  public class DerivedClass: BaseClass
    { 
        public DerivedClass(BaseClass baseModel)
        {
            this.CopyProperties(baseModel);
        }
    }

ستقوم أيضا بتحريك خصائص الوجهة التي تم كتابتها إذا تم تعيينها بالفعل (وليس NULL) أم لا.

هل من الممكن تعيين كائن فئة أساسية إلى مرجع فئة مشتقة مع Typecast صريح في C #؟

ليس فقط صريح، ولكن أيضا تحويلات ضمنية ممكنة.

لا تسمح لغة C # بمشغل التحويلات، ولكن لا يزال بإمكانك كتابةها باستخدام C # نقي ويعملون. لاحظ أن الفصل الذي يحدد مشغل التحويل الضمني (Derived) والصف الذي يستخدم المشغل (Program) يجب تعريفه في مجالات منفصلة (مثل Derived الطبقة في library.dll التي تمت الإشارة إليها program.exe تحتوي على Program صف دراسي).

//In library.dll:
public class Base { }

public class Derived {
    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Implicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }

    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Explicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }
}

//In program.exe:
class Program {
    static void Main(string[] args) {
        Derived z = new Base(); //Visual Studio can show squiggles here, but it compiles just fine.
    }
}

عند الرجوع إلى المكتبة باستخدام مرجع المشروع في Visual Studio، يعرض VS Scriggles عند استخدام التحويل الضمني، لكنه يتجمع بشكل جيد. إذا كنت مجرد الرجوع إلى library.dll, ، لا توجد شكلي.

يمكنك القيام بذلك باستخدام عام.

public class BaseClass
{
    public int A { get; set; }
    public int B { get; set; }
    private T ConvertTo<T>() where T : BaseClass, new()
    {
         return new T
         {
             A = A,
             B = B
         }
    }

    public DerivedClass1 ConvertToDerivedClass1()
    {
         return ConvertTo<DerivedClass1>();
    }

    public DerivedClass2 ConvertToDerivedClass2()
    {
         return ConvertTo<DerivedClass2>();
    }
}

public class DerivedClass1 : BaseClass
{
    public int C { get; set; }
}

public class DerivedClass2 : BaseClass
{
    public int D { get; set; }
}

تحصل على ثلاث فوائد باستخدام هذا النهج.

  1. أنت لا تكرر الكود
  2. أنت لا تستخدم انعكاس (وهو بطيء)
  3. جميع تحويلاتك في مكان واحد

كنت مجتمعة بعض الأجزاء من الإجابات السابقة (بفضل هؤلاء المؤلفين) ووضعوا فئة ثابتة بسيطة مع طريقتين نستخدمهما.

نعم، إنه بسيط، لا، لا يغطي جميع السيناريوهات، نعم، يمكن توسيعه واستفادته، لا شيء غير مثالي، نعم قد يكون من الممكن أن يكون أكثر كفاءة، لا، ليس أعظم شيء منذ شرائح الخبز، نعم هناك كامل على شكل نوجيه كائن الحزمة المبرمجي هناك طريقة أفضل للاستخدام الشديد، إلخ، إلخ، YADA YAAA - ولكنها تعمل على احتياجاتنا الأساسية رغم ذلك :)

وبالطبع، سيحاول تعيين القيم من أي كائن إلى أي كائن أو مشتقة أو لا (فقط الخصائص العامة التي تسمى نفسها بالطبع - يتجاهل الباقي).

الاستعمال:

SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };

// creates new object of type "RealPerson" and assigns any matching property 
// values from the puppet object 
// (this method requires that "RealPerson" have a parameterless constructor )
RealPerson person = ObjectMapper.MapToNewObject<RealPerson>(puppet);

// OR

// create the person object on our own 
// (so RealPerson can have any constructor type that it wants)
SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };
RealPerson person = new RealPerson("tall") {Name = "Steve"};

// maps and overwrites any matching property values from 
// the puppet object to the person object so now our person's age will get set to 5 and
// the name "Steve" will get overwritten with "Elmo" in this example
ObjectMapper.MapToExistingObject(puppet, person);

فئة المرافق الثابتة:

public static class ObjectMapper
{
    // the target object is created on the fly and the target type 
    // must have a parameterless constructor (either compiler-generated or explicit) 
    public static Ttarget MapToNewObject<Ttarget>(object sourceobject) where Ttarget : new()
    {
        // create an instance of the target class
        Ttarget targetobject = (Ttarget)Activator.CreateInstance(typeof(Ttarget));

        // map the source properties to the target object
        MapToExistingObject(sourceobject, targetobject);

        return targetobject;
    }

    // the target object is created beforehand and passed in
    public static void MapToExistingObject(object sourceobject, object targetobject)
    {
        // get the list of properties available in source class
        var sourceproperties = sourceobject.GetType().GetProperties().ToList();

        // loop through source object properties
        sourceproperties.ForEach(sourceproperty => {

            var targetProp = targetobject.GetType().GetProperty(sourceproperty.Name);

            // check whether that property is present in target class and is writeable
            if (targetProp != null && targetProp.CanWrite)
            {
                // if present get the value and map it
                var value = sourceobject.GetType().GetProperty(sourceproperty.Name).GetValue(sourceobject, null);
                targetobject.GetType().GetProperty(sourceproperty.Name).SetValue(targetobject, value, null);
            }
        });
    }
}

ماذا عن:

public static T As<T>(this object obj)
    {
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
    }

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

    public Derived(Base item) :base()
    {

        Type type = item.GetType();

        System.Reflection.PropertyInfo[] properties = type.GetProperties();
        foreach (var property in properties)
        {
            try
            {
                property.SetValue(this, property.GetValue(item, null), null);
            }
            catch (Exception) { }
        }

    }

أنا لا أوافق على أنه غير ممكن. يمكنك أن تفعل ذلك مثل هذا:

public class Auto 
{ 
    public string Make {get; set;}
    public string Model {get; set;}
}

public class Sedan : Auto
{ 
    public int NumberOfDoors {get; set;}
}

public static T ConvertAuto<T>(Sedan sedan) where T : class
{
    object auto = sedan;
    return (T)loc;
}

الاستعمال:

var sedan = new Sedan();
sedan.NumberOfDoors = 4;
var auto = ConvertAuto<Auto>(sedan);

لا، انظر هذا السؤال الذي طرحته - التحدث في .NET باستخدام Generics

أفضل طريقة هي إنشاء منشئ افتراضي على الفصل، وبناء ثم استدعاء Initialise طريقة

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