เป็นไปได้หรือไม่ที่จะกำหนดอ็อบเจ็กต์คลาสพื้นฐานให้กับการอ้างอิงคลาสที่ได้รับด้วยการพิมพ์แบบชัดแจ้ง


89

เป็นไปได้หรือไม่ที่จะกำหนดอ็อบเจ็กต์คลาสพื้นฐานให้กับการอ้างอิงคลาสที่ได้รับด้วยตัวพิมพ์ที่ชัดเจนใน C #?

ฉันได้ลองแล้วและเกิดข้อผิดพลาดรันไทม์

คำตอบ:


100

ไม่ได้การอ้างอิงไปยังคลาสที่ได้รับจริงต้องอ้างถึงอินสแตนซ์ของคลาสที่ได้รับ (หรือ null) มิฉะนั้นคุณจะคาดหวังให้มันทำงานอย่างไร?

ตัวอย่างเช่น:

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

หากคุณต้องการแปลงอินสแตนซ์ของประเภทพื้นฐานเป็นประเภทที่ได้รับฉันขอแนะนำให้คุณเขียนวิธีการเพื่อสร้างอินสแตนซ์ประเภทที่ได้รับที่เหมาะสม หรือดูต้นไม้มรดกของคุณอีกครั้งแล้วลองออกแบบใหม่เพื่อที่คุณจะได้ไม่ต้องทำสิ่งนี้ตั้งแต่แรก


72
@ ไมค์: โค้ดคอมไพล์ได้ดี ถึงเวลาประหาร :)
Jon Skeet

1
แล้วจะเกิดอะไรขึ้นเมื่อเราเขียน Base b = new Derived (); เหรอ? มันจะสร้างวัตถุสำหรับทั้งฐานและคลาสที่ได้รับหรือไม่?
Ashif Nataliya

3
@Akie: ไม่มันสร้างออบเจ็กต์ประเภทDerivedเดียว แต่คุณสามารถใช้การDerivedอ้างอิงเป็นBaseข้อมูลอ้างอิงได้
Jon Skeet

ดังนั้นจึงมีความแตกต่างในวัตถุผลลัพธ์สำหรับสองคำสั่งนี้หรือไม่? ฐาน b = ฐานใหม่ () และฐาน b = ได้รับใหม่ ()? ประโยชน์ของการใช้อย่างอื่นคืออะไร?
Ashif Nataliya

4
@Akie: ใช่คนหนึ่งสร้างอินสแตนซ์ของBaseและอีกอันสร้างอินสแตนซ์ของDerived. ถ้าคุณโทรวิธีเสมือนบนbซึ่งถูกแทนที่ในDerivedคุณจะเห็นพฤติกรรมถ้าคุณได้มีตัวอย่างของDerived Derivedแต่ไม่เหมาะสมจริงๆที่จะเข้าไปดูรายละเอียดในเธรดความคิดเห็น Stack Overflow - คุณควรอ่านหนังสือ C # หรือแบบฝึกหัดที่ดีเนื่องจากเป็นสิ่งพื้นฐานที่ค่อนข้างดี
Jon Skeet

47

ไม่เป็นไปไม่ได้เนื่องจากการกำหนดให้กับการอ้างอิงคลาสที่ได้รับมาจะเหมือนกับการพูดว่า "คลาสพื้นฐานเป็นการแทนที่คลาสที่ได้รับอย่างสมบูรณ์ซึ่งสามารถทำทุกอย่างที่คลาสที่ได้รับสามารถทำได้" ซึ่งไม่เป็นความจริงเนื่องจากคลาสที่ได้รับในข้อเสนอทั่วไป ฟังก์ชันมากกว่าคลาสพื้นฐาน (อย่างน้อยนั่นคือแนวคิดเบื้องหลังการสืบทอด)

คุณสามารถเขียนตัวสร้างในคลาสที่ได้รับโดยใช้อ็อบเจ็กต์คลาสฐานเป็นพารามิเตอร์คัดลอกค่า

สิ่งนี้:

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();//base class
Derived d = new Derived();//derived class

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!

23

ฉันมีปัญหานี้และแก้ไขได้โดยการเพิ่มวิธีการที่รับพารามิเตอร์ประเภทและแปลงวัตถุปัจจุบันเป็นประเภทนั้น

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

คุณควรใช้ประเภทของคลาสปัจจุบัน (คลาสพื้นฐาน) เพื่อรับและตั้งค่าคุณสมบัติเนื่องจากค่าเหล่านี้เป็นค่าที่คุณต้องการแมปกับคลาสที่ได้รับ
Bowofola

1
หากคุณมีคุณสมบัติที่ไม่สามารถเขียนเป็นชนิดที่ได้รับคุณควรเปลี่ยนเป็น: if (property.CanWrite) property.SetValue (instance, property.GetValue (this, null), null);
user3478586

10

อย่างที่คนอื่น ๆ ตอบไม่ได้

ฉันใช้รหัสต่อไปนี้ในโอกาสที่โชคร้ายเมื่อฉันจำเป็นต้องใช้ประเภทพื้นฐานเป็นประเภทที่ได้รับ ใช่มันเป็นการละเมิดหลักการ Liskov Substitution Principle (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>()

เนื่องจากใช้การสะท้อนแสงจึงมีราคา "แพง" ใช้ตามนั้น


ฉันเพิ่งลองสิ่งนี้และคิดว่าสามารถปรับปรุงเพิ่มเติมได้โดยการโอเวอร์โหลดตัวดำเนินการที่ชัดเจน (และตัวดำเนินการโดยนัยด้วย) .. แต่ - คอมไพเลอร์ไม่อนุญาต: user-defined conversions to or from a base class are not allowed ฉันเห็นสาเหตุของสิ่งนี้ แต่ผิดหวัง มันจะสนุกมากถ้ามันยอมให้ ..
เฮนริก

@MEC: ฉันสังเกตเห็นว่าคุณทิ้งส่วน `ที่ T: MyBaseClass` และเพิ่มif (type.BaseType != null)คำชี้แจงที่เกี่ยวข้องกับ A. ของ Markus Knappen Johansson ทำไมถึงเป็นอย่างนั้น? นั่นหมายความว่าจะอนุญาตประเภทในการโทรที่ไม่ได้มาจาก MyBaseClass (หรืออะไรก็ได้สำหรับเรื่องนั้น) ฉันรู้ว่ามันจะยังคงทำให้เกิดข้อผิดพลาดของคอมไพเลอร์หากมอบหมายให้ myDerivedObject แต่ถ้าใช้เป็นเพียงนิพจน์มันจะคอมไพล์และในขณะรันไทม์เพียงแค่สร้าง myDerivedObject โดยไม่มีข้อมูลใด ๆ ที่คัดลอกมาจาก "myBaseObject" ฉันนึกไม่ถึงกรณีการใช้งานสำหรับสิ่งนั้น
ทอม

@ ทอมตอบช้า แต่คิดว่ายังมีประโยชน์ คำตอบที่ดีที่สุดสำหรับคำถามของคุณน่าจะเป็นการบอกว่าชื่อ "As" จะดีกว่าเป็น "AsOrDefault" โดยพื้นฐานแล้วเราสามารถใช้ผลลัพธ์นี้และเปรียบเทียบกับค่าเริ่มต้นเช่นที่เราทำเมื่อใช้ SingleOrDefault หรือ FirstOrDefault ของ Linq
MEC

7

ไม่เป็นไปไม่ได้ด้วยเหตุนี้ข้อผิดพลาดรันไทม์ของคุณ

แต่คุณสามารถกำหนดอินสแตนซ์ของคลาสที่ได้รับให้กับตัวแปรของประเภทคลาสพื้นฐาน


7

โซลูชันด้วย JsonConvert (แทนการพิมพ์แบบพิมพ์)

วันนี้ฉันประสบปัญหาเดียวกันและพบวิธีแก้ปัญหาที่ง่ายและรวดเร็วโดยใช้JsonConvertไฟล์.

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

ฉันตอบสิ่งนี้อีกครั้งด้านล่างพร้อมวิธีการขยาย ใช่นี่คือคำตอบ
Patrick Knott

5

อย่างที่ทุกคนพูดกันตรงนี้ไม่ได้เลย

วิธีที่ฉันชอบและค่อนข้างสะอาดคือใช้ Object Mapper เช่นAutoMapper AutoMapper

มันจะทำหน้าที่คัดลอกคุณสมบัติจากอินสแตนซ์หนึ่งไปยังอีกอินสแตนซ์หนึ่ง (ไม่จำเป็นต้องเป็นประเภทเดียวกัน) โดยอัตโนมัติ


3

การขยายคำตอบของ @ ybo - เป็นไปไม่ได้เนื่องจากอินสแตนซ์ที่คุณมีของคลาสพื้นฐานไม่ใช่อินสแตนซ์ของคลาสที่ได้รับมา มันรู้เกี่ยวกับสมาชิกของคลาสพื้นฐานเท่านั้นและไม่รู้อะไรเกี่ยวกับคลาสที่ได้รับมา

เหตุผลที่คุณสามารถสร้างอินสแตนซ์ของคลาสที่ได้รับไปยังอินสแตนซ์ของคลาสพื้นฐานได้เนื่องจากคลาสที่ได้รับนั้นเป็นอินสแตนซ์ของคลาสพื้นฐานอยู่แล้วเนื่องจากมีสมาชิกเหล่านั้นอยู่แล้ว สิ่งที่ตรงกันข้ามไม่สามารถพูดได้


3

คุณสามารถแคสต์ตัวแปรที่พิมพ์เป็นคลาสพื้นฐานไปยังประเภทของคลาสที่ได้รับ อย่างไรก็ตามตามความจำเป็นสิ่งนี้จะทำการตรวจสอบรันไทม์เพื่อดูว่าวัตถุจริงที่เกี่ยวข้องเป็นประเภทที่ถูกต้องหรือไม่

เมื่อสร้างแล้วจะไม่สามารถเปลี่ยนประเภทของวัตถุได้ (อย่างน้อยก็อาจมีขนาดไม่เท่ากัน) อย่างไรก็ตามคุณสามารถแปลงอินสแตนซ์สร้างอินสแตนซ์ใหม่ของประเภทที่สองได้ แต่คุณต้องเขียนโค้ด Conversion ด้วยตนเอง


2

ไม่เป็นไปไม่ได้

พิจารณาสถานการณ์ที่ ACBus เป็นคลาสที่ได้รับมาของบัสคลาสพื้นฐาน ACBus มีคุณสมบัติเช่น TurnOnAC และ TurnOffAC ซึ่งทำงานบนฟิลด์ชื่อ ACState TurnOnAC ตั้งค่า ACState เป็นเปิดและ TurnOffAC ตั้งค่า ACState เป็นปิด หากคุณพยายามใช้คุณสมบัติ TurnOnAC และ TurnOffAC บน Bus ก็ไม่สมเหตุสมผล


2
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");
    }
}

}

เมื่อเราสร้างอ็อบเจ็กต์คลาสลูกอ็อบเจ็กต์คลาสพื้นฐานจะเริ่มต้นโดยอัตโนมัติดังนั้นตัวแปรอ้างอิงคลาสพื้นฐานสามารถชี้ไปที่อ็อบเจ็กต์คลาสลูก

แต่ไม่ใช่ในทางกลับกันเนื่องจากตัวแปรอ้างอิงคลาสย่อยไม่สามารถชี้ไปที่อ็อบเจ็กต์คลาสพื้นฐานได้เนื่องจากไม่มีการสร้างอ็อบเจ็กต์คลาสลูก

และสังเกตด้วยว่าตัวแปรอ้างอิงคลาสพื้นฐานสามารถเรียกเฉพาะสมาชิกคลาสพื้นฐานเท่านั้น


2

มีวิธีการทำเช่นนี้จริงๆ ลองนึกดูว่าคุณจะใช้ Newtonsoft JSON เพื่อแยกวัตถุออกจาก json ได้อย่างไร มันจะ (หรืออย่างน้อยก็สามารถ) ละเว้นองค์ประกอบที่ขาดหายไปและเติมองค์ประกอบทั้งหมดที่มันรู้

นี่คือวิธีที่ฉันทำ ตัวอย่างโค้ดขนาดเล็กจะเป็นไปตามคำอธิบายของฉัน

  1. สร้างอินสแตนซ์ของออบเจ็กต์ของคุณจากคลาสพื้นฐานและเติมข้อมูลตามนั้น

  2. ใช้คลาส "jsonconvert" ของ Newtonsoft json ทำให้วัตถุนั้นเป็นอนุกรมเป็นสตริง json

  3. ตอนนี้สร้างอ็อบเจ็กต์คลาสย่อยของคุณโดย deserializing ด้วยสตริง json ที่สร้างในขั้นตอนที่ 2 สิ่งนี้จะสร้างอินสแตนซ์ของคลาสย่อยของคุณพร้อมคุณสมบัติทั้งหมดของคลาสพื้นฐาน

งานนี้เหมือนมีเสน่ห์! แล้ว.. เมื่อไหร่จะมีประโยชน์? บางคนถามว่าเมื่อไหร่จะสมเหตุสมผลและแนะนำให้เปลี่ยนสคีมาของ OP เพื่อรองรับความจริงที่ว่าคุณไม่สามารถทำสิ่งนี้กับการสืบทอดคลาสได้ (ใน. Net)

ในกรณีของฉันฉันมีคลาสการตั้งค่าที่มีการตั้งค่า "ฐาน" ทั้งหมดสำหรับบริการ บริการเฉพาะมีตัวเลือกมากกว่าและมาจากตาราง DB อื่นดังนั้นคลาสเหล่านั้นจะสืบทอดคลาสพื้นฐาน พวกเขาทั้งหมดมีชุดตัวเลือกที่แตกต่างกัน ดังนั้นเมื่อดึงข้อมูลสำหรับบริการ FIRST จะเติมค่าโดยใช้อินสแตนซ์ของวัตถุพื้นฐานได้ง่ายกว่ามาก วิธีหนึ่งที่ทำได้กับแบบสอบถาม 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)

โดยใช้ C # และ var destObject = JsonConvert.DeserializeObject<DestinationType>(JsonConvert.SerializeObject(srcObject));Newtonsoft.Json: ฉันจะใช้สิ่งนี้สำหรับการทดสอบหน่วยและ "การแฮ็ก" อื่น ๆ ที่ไม่ใช่การผลิตเท่านั้น!
thinkOfaNumber

2

คุณสามารถใช้ 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);

}

1

อาจไม่เกี่ยวข้อง แต่ฉันสามารถรันโค้ดบนวัตถุที่ได้รับจากฐานของมัน แน่นอนว่ามันแฮ็คมากกว่าที่ฉันต้องการ แต่ใช้งานได้:

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);

1

คุณสามารถทำได้โดยใช้ทั่วไป

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. Conversion ทั้งหมดของคุณอยู่ในที่เดียว

1

ฉันรู้ว่ามันเก่า แต่ฉันก็ใช้มันสำเร็จมาระยะหนึ่งแล้ว

   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);
        }
    } 

1

ฉันรวมบางส่วนของคำตอบก่อนหน้านี้ (ขอบคุณผู้เขียนเหล่านั้น) และรวบรวมคลาสแบบคงที่ง่ายๆด้วยสองวิธีที่เราใช้

ใช่มันง่ายไม่มันไม่ได้ครอบคลุมทุกสถานการณ์ใช่มันสามารถขยายและทำให้ดีขึ้นได้ไม่มันไม่สมบูรณ์แบบใช่มันอาจจะทำให้มีประสิทธิภาพมากขึ้นไม่ใช่ไม่ใช่สิ่งที่ดีที่สุดตั้งแต่ขนมปังหั่นบาง ๆ ใช่มี เครื่องมือแมปอ็อบเจ็กต์แพ็คเกจ nuget ที่มีประสิทธิภาพเต็มรูปแบบมีวิธีที่ดีกว่าสำหรับการใช้งานหนัก ฯลฯ ฯลฯ yada yada - แต่ใช้ได้กับความต้องการพื้นฐานของเรา :)

และแน่นอนมันจะพยายามแมปค่าจากวัตถุใด ๆ กับวัตถุใด ๆ โดยได้รับมาหรือไม่ก็ได้ (เฉพาะคุณสมบัติสาธารณะที่มีชื่อเหมือนกันเท่านั้น - ละเว้นส่วนที่เหลือ)

การใช้งาน:

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);
            }
        });
    }
}

1

คุณสามารถใช้ตัวสร้างการคัดลอกที่เรียกใช้ตัวสร้างอินสแตนซ์ทันทีหรือถ้าตัวสร้างอินสแตนซ์ของคุณทำมากกว่าการกำหนดให้ตัวสร้างการคัดลอกกำหนดค่าขาเข้าให้กับอินสแตนซ์

class Person
{
    // Copy constructor 
    public Person(Person previousPerson)
    {
        Name = previousPerson.Name;
        Age = previousPerson.Age;
    }

    // Copy constructor calls the instance constructor.
    public Person(Person previousPerson)
        : this(previousPerson.Name, previousPerson.Age)
    {
    }

    // Instance constructor.
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public int Age { get; set; }

    public string Name { get; set; }
}

อ้างถึงเอกสารMicrosoft C # ภายใต้ Constructorสำหรับตัวอย่างนี้เคยมีปัญหานี้ในอดีต


0

อีกวิธีหนึ่งคือการเพิ่มวิธีการขยายดังนี้:

 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) หรือไม่ก็ได้


0

เป็นไปได้หรือไม่ที่จะกำหนดอ็อบเจ็กต์คลาสพื้นฐานให้กับการอ้างอิงคลาสที่ได้รับด้วยตัวพิมพ์ที่ชัดเจนใน 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 จะแสดง squiggles เมื่อคุณใช้การแปลงโดยนัย แต่คอมไพล์ได้ดี หากคุณเพียงแค่อ้างอิงlibrary.dllไม่มี squiggles


นี่มันมนต์ดำอะไรกัน!? นอกจากนี้ "Derived z = new Base ()" จะช่วยฉันทำ "BaseCls baseObj; DerivedCls receivedObj; receivedObj = (DerivedCls) baseObj" (Q ของ OP) ได้อย่างไร นอกจากนี้System.Runtime.CompilerServices.SpecialNameAttribute ทำอะไร? เอกสารสำหรับทุกเวอร์ชันตั้งแต่รุ่นแรกสุดที่มี (2.0) ถึง "เวอร์ชันปัจจุบัน" (4.6? "ใคร? ใคร?") ไม่ได้บอกว่าทำอะไร แต่บอกว่า "ขณะนี้คลาส SpecialNameAttribute ไม่ได้ใช้ใน. NET Framework แต่สงวนไว้สำหรับใช้ในอนาคต ". ดู: [ลิงค์] ( msdn.microsoft.com/en-us/library/ms146064(v=vs.100).aspx )
ทอม

> "นี่มันมนต์ดำอะไรเนี่ย!?" ซึ่งเรียกว่า. Net Framework (CLR, IL, BCL) ชุดคุณลักษณะของภาษา IL, C # และ VB ไม่เหมือนกัน มีคุณสมบัติใน VB ที่ C # ไม่รองรับ มีคุณสมบัติใน IL ที่ C # ไม่รองรับ มีข้อ จำกัด ใน C # ที่ค่อนข้างจะเป็นไปตามอำเภอใจและไม่มีอยู่ใน IL พื้นฐาน ( where T : Delegateคุณสมบัติเหมือนหรือพารามิเตอร์ที่เรียกว่าตัวทำดัชนี ฯลฯ ฯลฯ )
Ark-kun

> "นอกจากนี้" Derived z = new Base () "ช่วยฉันทำอย่างไร" BaseCls baseObj; DerivedCls ได้มาObj; receivedObj = (DerivedCls) baseObj "(Q ของ OP)?" มันก็ไม่ ช่วยแก้คำถามของ OP และคุณไม่จำเป็นต้องใช้นักแสดงที่ชัดเจน
Ark-kun

> what does System.Runtime.CompilerServices.SpecialName Attribute do?- ใช้เพื่อทำเครื่องหมายวิธีการที่สร้างขึ้นโดยโครงสร้างอำนวยความสะดวกพิเศษบางอย่างของภาษา. Net ระดับสูง: ตัวเข้าถึงคุณสมบัติ, ตัวเข้าถึงเหตุการณ์, ตัวสร้าง, ตัวดำเนินการ, ตัวทำดัชนี ฯลฯ เว้นแต่specialnameจะไม่เห็นวิธีการของ IL เป็นคุณสมบัติ / เหตุการณ์ / ตัวสร้างและจะได้รับการยอมรับว่าเป็นวิธีการปกติ การทำเครื่องหมายเมธอดที่ตั้งชื่ออย่างเหมาะสมด้วยตนเองด้วยแอ็ตทริบิวต์นี้เป็นเพียงการทำงานของคอมไพเลอร์ด้วยตนเอง
Ark-kun

VB.Net มีตัวดำเนินการไฟฟ้า C # ไม่ คุณจะโอเวอร์โหลดตัวดำเนินการไฟฟ้าใน C # เพื่อใช้ใน VB.Net ได้อย่างไร เพียงกำหนดop_Exponentวิธีการและทำเครื่องหมายด้วยspecialnameแอตทริบิวต์
Ark-kun


0

วิธีที่ดีที่สุดในการเพิ่มคุณสมบัติพื้นฐานทั้งหมดให้กับรายการที่ได้รับคือใช้การสะท้อนใน costructor ลองใช้รหัสนี้โดยไม่ต้องสร้างเมธอดหรืออินสแตนซ์

    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) { }
        }

    }

0

ฉันไม่เห็นด้วยที่มันเป็นไปไม่ได้ คุณสามารถทำได้ดังนี้:

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);

var auto =ยังคงเป็นประเภทsedan
bendecko

0

นี่คือวิธีที่ฉันแก้ไขปัญหานี้สำหรับฟิลด์ คุณสามารถทำซ้ำแบบเดิมผ่านคุณสมบัติได้หากต้องการ คุณอาจต้องการตรวจสอบnullฯลฯ แต่นี่เป็นแนวคิด

 public static DerivedClass ConvertFromBaseToDerived<BaseClass, DerivedClass>(BaseClass baseClass)
            where BaseClass : class, new()
            where DerivedClass : class, BaseClass, new()
        {
            DerivedClass derived = (DerivedClass)Activator.CreateInstance(typeof(DerivedClass));
            derived.GetType().GetFields().ToList().ForEach(field =>
            {
                var base_ = baseClass.GetType().GetField(field.Name).GetValue(baseClass);
                field.SetValue(derived, base_);

            });

            return derived;
        }

0

คุณสามารถจัดลำดับออบเจ็กต์พื้นฐานให้เป็น JSON จากนั้นจึงแยกซีเรียลเป็นอ็อบเจ็กต์ที่ได้รับ


0

ไม่ได้อยู่ในความรู้สึกดั้งเดิม ... แปลงเป็น Json จากนั้นเป็นวัตถุของคุณและบูมเสร็จแล้ว! เจสซี่ข้างบนมีคำตอบโพสต์ก่อน แต่ไม่ได้ใช้วิธีการขยายเหล่านี้ซึ่งทำให้กระบวนการง่ายขึ้นมาก สร้างวิธีการขยายสองสามวิธี:

    public static string ConvertToJson<T>(this T obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
    public static T ConvertToObject<T>(this string json)
    {
        if (string.IsNullOrEmpty(json))
        {
            return Activator.CreateInstance<T>();
        }
        return JsonConvert.DeserializeObject<T>(json);
    }

ใส่ไว้ในกล่องเครื่องมือของคุณตลอดไปจากนั้นคุณสามารถทำได้ตลอดเวลา:

var derivedClass = baseClass.ConvertToJson().ConvertToObject<derivedClass>();

อาพลังของ JSON

มี gotcha สองสามอย่างที่ใช้แนวทางนี้: เรากำลังสร้างวัตถุใหม่ไม่ใช่การหล่อซึ่งอาจมีหรือไม่สำคัญก็ได้ ช่องส่วนตัวจะไม่ถูกถ่ายโอนตัวสร้างที่มีพารามิเตอร์จะไม่ถูกเรียกเป็นต้นเป็นไปได้ว่าจะไม่กำหนดลูก json บางตัว JsonConvert ไม่ได้จัดการสตรีม อย่างไรก็ตามหากคลาสของเราไม่ได้อาศัยฟิลด์ส่วนตัวและคอนสตรัคเตอร์นี่เป็นวิธีการที่มีประสิทธิภาพมากในการย้ายข้อมูลจากคลาสหนึ่งไปยังคลาสโดยไม่ต้องแมปและเรียกคอนสตรัคเตอร์ซึ่งเป็นเหตุผลหลักว่าทำไมเราถึงต้องการแคสต์ตั้งแต่แรก


สิ่งนี้ไม่ได้ทำตามที่ OP ถาม สิ่งที่คุณกำลังทำคือการสร้างออบเจ็กต์ใหม่ที่มีประเภทที่ถูกต้องสำหรับตัวแปรโดยใช้ข้อมูลจากออบเจ็กต์ดั้งเดิมที่ผิดประเภท สิ่งนี้อาจได้ผลหรือไม่ก็ได้ แต่ไม่ว่าจะด้วยวิธีใดก็ไม่ได้กำหนดอ็อบเจ็กต์ของประเภทคลาสฐานให้กับตัวแปรของชนิดที่ได้รับ
Lasse V.Karlsen

ฉันตอบคำถาม: เป็นไปได้หรือไม่ที่จะกำหนดอ็อบเจ็กต์คลาสพื้นฐานให้กับการอ้างอิงคลาสที่ได้รับมาพร้อมกับตัวพิมพ์ที่ชัดเจน โดยบอกว่าไม่ ฉันกำลังจัดหาทางเลือกที่ใช้งานได้จริงและสับสนน้อยกว่ายาสามัญ ดังที่ระบุไว้ข้างต้นหลายครั้งอาจทำให้เกิดปัญหาในการกำหนดคุณสมบัติคลาสที่ได้รับจากคลาสฐานอย่างไรก็ตามนี่เป็นวิธีการทำงาน (และทำใน apis) หากเป็นไปได้ เพียงเพราะคำตอบของฉันสามารถใช้จากประเภท "ผิด" ไม่ได้หมายความว่าจะใช้กับประเภท "ถูก" ไม่ได้ @ LasseV.Karlsen โปรดถอนการให้คะแนนเชิงลบของคุณ
Patrick Knott

ไม่เหมือนกับคำตอบส่วนใหญ่ที่นี่ที่โซ่เดซี่ JsonConverts ฉันแสดงวิธีจัดการกับโมฆะด้วย
Patrick Knott

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.