ฉันพยายามแคสต์จากคลาสพาเรนต์ไปยังคลาสย่อย แต่ฉันได้รับ InvalidCastException คลาสย่อยมีคุณสมบัติประเภท int เพียงอย่างเดียว มีใครรู้บ้างว่าฉันต้องทำอย่างไร?
ฉันพยายามแคสต์จากคลาสพาเรนต์ไปยังคลาสย่อย แต่ฉันได้รับ InvalidCastException คลาสย่อยมีคุณสมบัติประเภท int เพียงอย่างเดียว มีใครรู้บ้างว่าฉันต้องทำอย่างไร?
คำตอบ:
วิธีง่ายๆในการดาวน์แคสต์ใน C # คือการทำให้เป็นอนุกรมพาเรนต์จากนั้นจึงแยกสายเข้ากับลูก
var serializedParent = JsonConvert.SerializeObject(parentInstance);
Child c = JsonConvert.DeserializeObject<Child>(serializedParent);
ฉันมีแอปคอนโซลง่ายๆที่ทำให้สัตว์กลายเป็นสุนัขโดยใช้โค้ดสองบรรทัดด้านบนตรงนี้
คุณไม่สามารถเสกสัตว์เลี้ยงลูกด้วยนมให้กลายเป็นสุนัขได้มันอาจจะเป็นแมวก็ได้
คุณไม่สามารถโยนอาหารลงในแซนวิชได้ - อาจเป็นชีสเบอร์เกอร์
คุณไม่สามารถหล่อรถเป็นเฟอร์รารีได้ - อาจเป็นฮอนด้าหรือโดยเฉพาะอย่างยิ่งคุณไม่สามารถหล่อ Ferrari 360 Modena ไปยัง Ferrari 360 Challange Stradale ได้ - มีชิ้นส่วนที่แตกต่างกันแม้ว่าจะเป็น Ferrari 360 ทั้งคู่ก็ตาม
อินสแตนซ์ที่การอ้างอิงคลาสพื้นฐานของคุณอ้างถึงไม่ใช่อินสแตนซ์ของคลาสย่อยของคุณ ไม่มีอะไรผิดปกติ
โดยเฉพาะอย่างยิ่ง:
Base derivedInstance = new Derived();
Base baseInstance = new Base();
Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException
เพื่อให้การแคสต์ประสบความสำเร็จอินสแตนซ์ที่คุณดาวน์คาสติ้งจะต้องเป็นอินสแตนซ์ของคลาสที่คุณกำลังดาวน์คาสติ้ง (หรืออย่างน้อยคลาสที่คุณดาวน์คาสติ้งจะต้องอยู่ในลำดับชั้นของอินสแตนซ์) มิฉะนั้น การร่ายจะล้มเหลว
ฉันเคยเห็นคนส่วนใหญ่พูดว่าพ่อแม่ที่ชัดเจนต่อการคัดเลือกเด็กเป็นไปไม่ได้ซึ่งจริงๆแล้วไม่เป็นความจริง มาเริ่มต้นแก้ไขและลองพิสูจน์ด้วยตัวอย่าง
ดังที่เราทราบใน. net การแคสติ้งทั้งหมดมีสองประเภทกว้าง ๆ
ประเภทการอ้างอิงมีสถานการณ์หลักอีกสามกรณีที่สถานการณ์ใด ๆ สามารถโกหกได้
กรณีที่ 1.เด็กกับผู้ปกครองโดยตรงหรือโดยอ้อม
Employee e = new Employee();
Person p = (Person)e; //Allowed
กรณีที่ 2.ตัวแปรหลักที่ถืออ็อบเจ็กต์พาเรนต์ (ไม่อนุญาต)
Person p = new Person(); // p is true Person object
Employee e = (Employee)p; //Runtime err : InvalidCastException <-------- Yours issue
กรณีที่ 3.ตัวแปรหลักถือวัตถุลูก (ประสบความสำเร็จเสมอ)
หมายเหตุ: เนื่องจากอ็อบเจ็กต์มีลักษณะที่หลากหลายจึงเป็นไปได้ที่ตัวแปรของประเภทคลาสพาเรนต์จะมีประเภทลูก
Person p = new Employee(); // p actually is Employee
Employee e = (Employee)p; // Casting allowed
สรุป:หลังจากอ่านข้างต้นแล้วหวังว่าตอนนี้มันจะสมเหตุสมผลเหมือนกับการเปลี่ยนพ่อแม่เป็นลูก (กรณีที่ 3)
ตอบคำถาม:
คำตอบของคุณคือในกรณีที่ 2 OOP ไม่อนุญาตให้แคสติ้งดังกล่าวและคุณพยายามละเมิดกฎพื้นฐานข้อใดข้อหนึ่งของ OOP ดังนั้นควรเลือกเส้นทางที่ปลอดภัยเสมอ
ยิ่งไปกว่านั้นเพื่อหลีกเลี่ยงสถานการณ์พิเศษเช่นนี้. net แนะนำให้ใช้คือ / เป็นตัวดำเนินการซึ่งจะช่วยคุณในการตัดสินใจอย่างมีข้อมูลและจัดเตรียมการหล่ออย่างปลอดภัย
มีบางกรณีที่นักแสดงจะเข้าท่า
กรณีของฉันฉันได้รับคลาส BASE ผ่านเครือข่ายและฉันต้องการคุณสมบัติเพิ่มเติม ดังนั้นการได้รับมันมาเพื่อจัดการกับระฆังและนกหวีดทั้งหมดที่ฉันต้องการและการโยนคลาส BASE ที่ได้รับไปยัง DERIVED นั้นไม่ใช่ทางเลือก (โยน InvalidCastException ของหลักสูตร)
วิธีแก้ปัญหาแบบคิดนอกกรอบที่ใช้ได้จริงวิธีหนึ่งคือการประกาศคลาส EXTENSION Helper ที่ไม่ได้สืบทอดคลาส BASE จริง ๆ แต่รวมถึงการเป็นสมาชิกด้วย
public class BaseExtension
{
Base baseInstance;
public FakeDerived(Base b)
{
baseInstance = b;
}
//Helper methods and extensions to Base class added here
}
ถ้าคุณมีเพศสัมพันธ์หลวมและเพียงแค่ต้องคู่ของคุณสมบัติพิเศษในการชั้นฐานโดยไม่มีจริงๆมีความจำเป็นที่แน่นอนของมาว่าอาจจะเป็นวิธีแก้ปัญหาที่รวดเร็วและง่าย
BaseExtensionอย่างน้อยคุณอาจต้องการให้คุณที่นี่นำไปIBaseใช้ในบริบทที่คล้ายกัน หรือไม่สำคัญสำหรับความต้องการของคุณ?
นั่นจะเป็นการละเมิดหลักการเชิงวัตถุ ฉันจะบอกว่าโซลูชันที่สวยงามที่นี่และที่อื่น ๆ ในโครงการคือการใช้กรอบการทำแผนที่วัตถุเช่นAutoMapperเพื่อกำหนดค่าการฉายภาพ
นี่คือการกำหนดค่าที่ซับซ้อนกว่าที่จำเป็นเล็กน้อย แต่มีความยืดหยุ่นเพียงพอสำหรับกรณีส่วนใหญ่:
public class BaseToChildMappingProfile : Profile
{
public override string ProfileName
{
get { return "BaseToChildMappingProfile"; }
}
protected override void Configure()
{
Mapper.CreateMap<BaseClass, ChildClassOne>();
Mapper.CreateMap<BaseClass, ChildClassTwo>();
}
}
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<BaseToChildMappingProfile>();
});
}
}
เมื่อแอปพลิเคชันเริ่มโทรAutoMapperConfiguration.Configure()จากนั้นคุณสามารถฉายได้ดังนี้:
ChildClassOne child = Mapper.Map<BaseClass, ChildClassOne>(baseClass);
คุณสมบัติถูกแม็ปโดยแบบแผนดังนั้นหากคลาสได้รับการสืบทอดชื่อคุณสมบัติจะเหมือนกันทุกประการและการแม็ปจะถูกกำหนดค่าโดยอัตโนมัติ คุณสามารถเพิ่มคุณสมบัติเพิ่มเติมได้โดยปรับแต่งการกำหนดค่า ดูเอกสาร
พอลคุณไม่ได้ถามว่า 'ทำได้ไหม' - ฉันสมมติว่าคุณอยากรู้วิธีทำ!
เราต้องทำสิ่งนี้ในโปรเจ็กต์ - มีคลาสมากมายที่เราตั้งขึ้นในแบบทั่วไปเพียงครั้งเดียวจากนั้นเริ่มต้นคุณสมบัติเฉพาะสำหรับคลาสที่ได้รับ ฉันใช้ VB ดังนั้นตัวอย่างของฉันจึงอยู่ใน VB (noogies ที่ยาก) แต่ฉันขโมยตัวอย่าง VB จากไซต์นี้ซึ่งมีเวอร์ชัน C # ที่ดีกว่าด้วย:
โค้ดตัวอย่าง:
Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Text
Imports System.Diagnostics
Module ClassUtils
Public Sub CopyProperties(ByVal dst As Object, ByVal src As Object)
Dim srcProperties() As PropertyInfo = src.GetType.GetProperties
Dim dstType = dst.GetType
If srcProperties Is Nothing Or dstType.GetProperties Is Nothing Then
Return
End If
For Each srcProperty As PropertyInfo In srcProperties
Dim dstProperty As PropertyInfo = dstType.GetProperty(srcProperty.Name)
If dstProperty IsNot Nothing Then
If dstProperty.PropertyType.IsAssignableFrom(srcProperty.PropertyType) = True Then
dstProperty.SetValue(dst, srcProperty.GetValue(src, Nothing), Nothing)
End If
End If
Next
End Sub
End Module
Module Module1
Class base_class
Dim _bval As Integer
Public Property bval() As Integer
Get
Return _bval
End Get
Set(ByVal value As Integer)
_bval = value
End Set
End Property
End Class
Class derived_class
Inherits base_class
Public _dval As Integer
Public Property dval() As Integer
Get
Return _dval
End Get
Set(ByVal value As Integer)
_dval = value
End Set
End Property
End Class
Sub Main()
' NARROWING CONVERSION TEST
Dim b As New base_class
b.bval = 10
Dim d As derived_class
'd = CType(b, derived_class) ' invalidcast exception
'd = DirectCast(b, derived_class) ' invalidcast exception
'd = TryCast(b, derived_class) ' returns 'nothing' for c
d = New derived_class
CopyProperties(d, b)
d.dval = 20
Console.WriteLine(b.bval)
Console.WriteLine(d.bval)
Console.WriteLine(d.dval)
Console.ReadLine()
End Sub
End Module
เรื่องนี้ไม่หล่อจริงๆ เป็นการสร้างวัตถุที่ได้รับใหม่และคัดลอกคุณสมบัติจากพาเรนต์โดยปล่อยให้คุณสมบัติลูกว่างไว้ นั่นคือทั้งหมดที่ฉันต้องทำและดูเหมือนว่าคุณต้องทำทั้งหมด โปรดทราบว่าจะคัดลอกคุณสมบัติเท่านั้นไม่ใช่สมาชิก (ตัวแปรสาธารณะ) ในชั้นเรียน (แต่คุณสามารถขยายเพื่อทำเช่นนั้นได้หากคุณต้องการความอัปยศในการเปิดเผยสมาชิกสาธารณะ)
การแคสต์โดยทั่วไปจะสร้างตัวแปร 2 ตัวที่ชี้ไปที่วัตถุเดียวกัน (บทแนะนำเล็ก ๆ ที่นี่โปรดอย่าโยนข้อยกเว้นกรณีมุมใส่ฉัน) มีการแบ่งส่วนที่สำคัญสำหรับสิ่งนี้ (แบบฝึกหัดสำหรับผู้อ่าน)!
แน่นอนฉันต้องบอกว่าทำไมคนขี้เกียจไม่ยอมให้คุณไปจากฐานไปหาอินสแตนซ์ แต่ทำในทางอื่น ลองนึกภาพกรณีที่คุณสามารถนำอินสแตนซ์ของกล่องข้อความ winforms (ที่ได้มา) และเก็บไว้ในตัวแปรประเภทควบคุม Winforms แน่นอนว่า 'control' สามารถย้ายวัตถุไปรอบ ๆ OK และคุณสามารถจัดการกับ 'controll-y' ทุกอย่างเกี่ยวกับ textbox ได้ (เช่นคุณสมบัติด้านบนซ้าย. text) ไม่สามารถมองเห็นสิ่งที่เฉพาะเจาะจงของกล่องข้อความ (เช่น. multiline) ได้หากไม่ส่งตัวแปรประเภท 'ควบคุม' ที่ชี้ไปที่กล่องข้อความในหน่วยความจำ แต่ยังคงอยู่ในหน่วยความจำ
ตอนนี้ลองนึกภาพว่าคุณมีตัวควบคุมและคุณต้องการกำหนดตัวแปรประเภท textbox ให้กับมัน การควบคุมในหน่วยความจำไม่มี 'multiline' และ textboxy อื่น ๆ หากคุณพยายามอ้างอิงสิ่งเหล่านี้การควบคุมจะไม่เพิ่มคุณสมบัติหลายสายอย่างน่าอัศจรรย์! คุณสมบัติ (ดูที่มันเหมือนตัวแปรสมาชิกที่นี่ที่เก็บค่าจริง - เนื่องจากมีอยู่ในหน่วยความจำของอินสแตนซ์กล่องข้อความ) ต้องมีอยู่ เนื่องจากคุณกำลังแคสต์จำไว้ว่าต้องเป็นวัตถุเดียวกันกับที่คุณกำลังชี้ไป ดังนั้นจึงไม่ใช่ข้อ จำกัด ด้านภาษาจึงเป็นไปไม่ได้ในทางปรัชญาที่จะกล่าวถึงลักษณะดังกล่าว
ควรสร้างอินสแตนซ์ของอ็อบเจ็กต์โดยใช้ประเภทของคลาสย่อยคุณไม่สามารถส่งอินสแตนซ์ประเภทพาเรนต์เป็นประเภทลูกได้
สำหรับฉันมันก็เพียงพอแล้วที่จะคัดลอกฟิลด์คุณสมบัติทั้งหมดจากคลาสฐานไปยังพาเรนต์ดังนี้:
using System.Reflection;
public static ChildClass Clone(BaseClass b)
{
ChildClass p = new ChildClass(...);
// Getting properties of base class
PropertyInfo[] properties = typeof(BaseClass).GetProperties();
// Copy all properties to parent class
foreach (PropertyInfo pi in properties)
{
if (pi.CanWrite)
pi.SetValue(p, pi.GetValue(b, null), null);
}
return p;
}
คุณสามารถดูโซลูชันสากลสำหรับวัตถุใด ๆ ได้ที่นี่
ใน C # 7.0 คุณสามารถใช้คำหลัก isเพื่อทำสิ่งนี้:
ด้วยคลาสเหล่านั้นที่กำหนดไว้:
class Base { /* Define base class */ }
class Derived : Base { /* Define derived class */ }
จากนั้นคุณสามารถทำบางสิ่งได้เช่น:
void Funtion(Base b)
{
if (b is Derived d)
{
/* Do something with d which is now a variable of type Derived */
}
}
ซึ่งจะเทียบเท่ากับ:
void Funtion(Base b)
{
Defined d;
if (b is Derived)
{
d = (Defined)b;
/* Do something with d */
}
}
ตอนนี้คุณสามารถโทร:
Function(new Derived()); // Will execute code defined in if
เช่นเดียวกับ
Function(new Base()); // Won't execute code defined in if
ด้วยวิธีนี้คุณจะมั่นใจได้ว่าดาวน์แคสต์ของคุณถูกต้องและจะไม่เกิดข้อยกเว้น!
ในการร่ายวัตถุที่แท้จริงจะต้องเป็นประเภทที่เท่ากับหรือได้มา จากประเภทที่คุณพยายามที่จะโยนไป ...
หรือหากต้องการระบุในทางตรงกันข้ามประเภทที่คุณพยายามจะร่ายจะต้องเหมือนกับหรือคลาสพื้นฐานของประเภทที่แท้จริงของวัตถุ
หากวัตถุจริงของคุณเป็นประเภทBaseclassคุณจะไม่สามารถส่งไปยังคลาสที่ได้รับประเภท ...
รูปแบบของวิธีการทำให้เป็นอนุกรมสำหรับผู้ที่ใช้ ServiceStack:
var child = baseObject.ConvertTo<ChildType>();
หรือรายละเอียดเพิ่มเติม:
var child = baseObject.ToJson().FromJson<ChildType>();
การทำให้เป็นอนุกรมของ ServiceStack อาจเร็วมากและทั้งหมดนี้ไม่ใช่วิธีแก้ปัญหาสำหรับการแปลงจำนวนมากในการถ่ายโอนเวลาแฝงต่ำหรือสำหรับประเภทที่มีความซับซ้อนสูง น่าจะชัดเจนสำหรับทุกคนที่ใช้ ServiceStack แต่คิดว่าฉันจะชี้แจงเพื่อคาดหวังความคิดเห็น