Không thể truyền từ Lớp cha sang Lớp con


97

Tôi đang cố truyền từ lớp cha sang lớp con nhưng tôi nhận được lỗi không hợp lệ. Lớp con chỉ có một thuộc tính kiểu int. Có ai biết tôi cần phải làm gì không?


Cũng cần biết rằng, bạn không thể sử dụng ép kiểu rõ ràng cho các lớp liên quan cơ sở / dẫn xuất.
Rzassar

Câu trả lời:


133

Một cách đơn giản để downcast trong C # là tuần tự hóa phần tử gốc và sau đó giải mã hóa nó thành phần con.

 var serializedParent = JsonConvert.SerializeObject(parentInstance); 
 Child c  = JsonConvert.DeserializeObject<Child>(serializedParent);

Tôi có một ứng dụng bảng điều khiển đơn giản để biến động vật thành chó, sử dụng hai dòng mã trên ở đây


19
Chà, tôi sẽ do dự khi gọi đây là "sự thất vọng".
Kirk Woll

Chỉ cần lưu ý, các tên biến không giống nhau ở trên.
Jake Gaston

5
Tôi thích khi ai đó nghĩ bên ngoài và im lặng với những người nói với OP rằng điều đó không thể được thực hiện (tiết kiệm cho một hoặc hai troll)! Cảm ơn vì sự hỗ trợ này. Tôi đã cố gắng tìm ra điều này cho các cặp vợ chồng cuối cùng của giờ :)
derekmx271

3
Đây là một giải pháp tuyệt vời. Tôi đã gặp một trường hợp trong đó lớp con của tôi chỉ là lớp bao bọc dành cho cha mẹ không có chức năng bổ sung. Tôi đã làm điều đó để không phải nhập tham chiếu web vào ứng dụng của mình vì nó nằm trong thư viện trợ giúp của tôi. Điều này cho phép tôi chuyển đổi cha mẹ thành lớp trình bao bọc của tôi. Cảm ơn bạn!
BrianVPS

1
Bạn là một thiên tài! :)
Yablargo

118

Bạn không thể biến động vật có vú thành chó - nó có thể là mèo.

Bạn không thể đúc thực phẩm thành bánh sandwich - đó có thể là bánh mì kẹp pho mát.

Bạn không thể đúc một chiếc xe thành một chiếc Ferrari - đó có thể là Honda, hoặc cụ thể hơn, Bạn không thể đúc một chiếc Ferrari 360 Modena thành Ferrari 360 Challange Stradale - có những bộ phận khác nhau, mặc dù cả hai đều là Ferrari 360.


17
Các rào cản có thể hiểu được, do đó không thể thực sự 'truyền' theo cách này. Nhưng nếu anh ta muốn một con chó có cùng màu mắt / cân nặng / kiểu lông / tuổi, v.v. với con mèo đang được giữ trong đồ vật có vú thì sao? Thực chất là sao chép các thuộc tính chung.
FastAl

7
FastAl, đây chính là lý do tại sao chúng ta có giao diện. Động vật có vú phải thực hiện IMammal và có màu mắt, cân nặng, v.v. Bây giờ bạn có thể chuyển cả chó và mèo sang IMammal.
Tom Deloford

1
Bạn có thể biến động vật có vú thành chó. Nếu nó là một con chó, nó là một con chó. Nếu không, nó sẽ trở thành null. các hàm "quá tải" có thể khiến việc chuyển đổi từ mèo sang chó không thể thực hiện được, nếu mèo có các hàm quá tải này cho phép điều này. Nhưng nhiệm vụ của bạn là xử lý việc mất dữ liệu và điều chỉnh dữ liệu không tồn tại. Giống như chuyển đổi móng vuốt để móng tay, đuổi theo chuỗi để đuổi theo quả bóng, vv ...
TamusJRoyce

Tôi nghĩ rằng các ví dụ là một chút cực đoan và chọn lọc và có lẽ ép kiểu là một phím tắt cho một hàm tạo bản sao. Ví dụ: đang xây dựng một ferrari với các thuộc tính được xác định trong ô tô đối tượng cơ sở. Hoặc, bắt đầu với một con người và tạo ra một Cậu bé. Đúc và sử dụng trực tiếp? Đồng ý đó là không-không. Nhưng nếu nó là một phần của hàm tạo hoặc thứ gì đó, có thể hoạt động. Câu trả lời tuần tự hóa dưới đây là một liên hệ tốt đẹp.
sirthomas

1
Ferrari suy Nice
Lord Darth Vader

57

Ví dụ mà tham chiếu lớp cơ sở của bạn đang đề cập đến không phải là một thể hiện của lớp con của bạn. Không có gì sai.

Cụ thể hơn:

Base derivedInstance = new Derived();
Base baseInstance = new Base();

Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException

Để truyền thành công, cá thể mà bạn đang dự báo phải là một phiên bản của lớp mà bạn đang dự báo xuống (hoặc ít nhất, lớp bạn đang dự báo phải nằm trong phân cấp lớp của cá thể đó), nếu không cast sẽ thất bại.


hoặc có khả năng Base otherDerived = new OtherDerived (); Derived otherFail = (Bắt nguồn) otherDerived;
Blair Conrad

class Base {} class Derived: Base {} // Trong Phương thức chính Cơ sở có nguồn gốcInstance = new Derived (); Cơ sở baseInstance = new Base (); Có nguồn gốc tốt = (Derived) originInstance; Derived fail = (Derived) baseInstance; Quá trình biên dịch này không có bất kỳ lỗi nào trong .NET 3.5. Vấn đề bạn đang nói là ở đâu?
pradeeptp

7
@pradeeptp: Tất nhiên là nó xây dựng. Ai đã nói gì về lỗi biên dịch?
Greg D

17

Có một số trường hợp khi một dàn diễn viên như vậy sẽ có ý nghĩa.
Trường hợp của tôi, tôi đang nhận được một lớp BASE qua mạng và tôi cần thêm các tính năng cho nó. Vì vậy, việc tạo ra nó để xử lý nó về phía tôi với tất cả các chuông và còi tôi muốn, và truyền lớp BASE đã nhận vào lớp DERIVED đơn giản không phải là một lựa chọn (Ném không hợp lệCastException của khóa học)

Một giải pháp thực tế có thể nghĩ ra là khai báo một lớp Trình trợ giúp MỞ RỘNG thực tế KHÔNG kế thừa lớp BASE, mà BAO GỒM nó như một thành viên.

public class BaseExtension
{
   Base baseInstance;

   public FakeDerived(Base b)
   {
      baseInstance = b;
   }

   //Helper methods and extensions to Base class added here
}

Nếu bạn có khớp nối lỏng lẻo và chỉ cần một vài tính năng bổ sung cho lớp cơ sở mà KHÔNG THỰC SỰ cần dẫn xuất, đó có thể là một giải pháp nhanh chóng và đơn giản.


Tôi có đúng khi nghĩ rằng bạn có thể muốn BaseExtensionở đây của bạn ít nhất phải triển khai IBasesao cho bạn có thể sử dụng nó trong các bối cảnh tương tự? Hay không quan trọng đối với nhu cầu của bạn?
tobriand

một số lần trong đó có thể là một thay thế thích hợp cho các thừa kế
Vahid Ghadiri

17

Tôi đã thấy hầu hết mọi người nói rằng không thể chọn cha mẹ cho con một cách rõ ràng , điều đó thực sự không đúng. Hãy bắt đầu sửa đổi và thử chứng minh điều đó bằng các ví dụ.

Như chúng ta biết trong .net, tất cả các vật đúc đều có hai loại lớn.

  1. Đối với loại giá trị
  2. Đối với loại Tham chiếu (trong trường hợp của bạn là loại tham chiếu của nó)

Loại tham chiếu có thêm ba trường hợp tình huống chính trong đó bất kỳ tình huống nào cũng có thể nói dối.

Con với cha mẹ (Truyền ngầm - Luôn thành công)

Trường hợp 1. Con với bất kỳ cha mẹ trực tiếp hoặc gián tiếp nào

Employee e = new Employee();
Person p = (Person)e; //Allowed

Parent to Child (Truyền rõ ràng - Có thể thành công)

Trường hợp 2. Biến cha giữ đối tượng cha (Không được phép)

Person p = new Person();  // p is true Person object
Employee e = (Employee)p; //Runtime err : InvalidCastException <-------- Yours issue

Trường hợp 3. Biến cha giữ đối tượng con (Luôn thành công)

Lưu ý: Vì các đối tượng có tính chất đa hình, nên một biến của kiểu lớp cha có thể chứa kiểu con.

Person p = new Employee(); // p actually is Employee
Employee e = (Employee)p; // Casting allowed

Kết luận: Sau khi đọc xong tất cả, hy vọng bây giờ nó sẽ có ý nghĩa như cách chuyển đổi từ cha mẹ sang con cái có thể thực hiện được (Trường hợp 3).

Trả lời câu hỏi :

Câu trả lời của bạn là trong trường hợp 2. Nơi bạn có thể thấy việc truyền như vậy không được OOP cho phép và bạn đang cố gắng vi phạm một trong những quy tắc cơ bản của OOP. Vì vậy, hãy luôn chọn con đường an toàn.

Hơn nữa, để tránh những trường hợp ngoại lệ như vậy .net đã khuyến nghị sử dụng là / như các toán tử sẽ giúp bạn đưa ra quyết định sáng suốt và cung cấp quá trình truyền an toàn.


13

Điều đó sẽ vi phạm các nguyên tắc hướng đối tượng. Tôi muốn nói rằng một giải pháp thanh lịch ở đây và ở những nơi khác trong dự án là sử dụng khung lập bản đồ đối tượng như AutoMapper để định cấu hình một phép chiếu.

Đây là một cấu hình phức tạp hơn một chút so với mức cần thiết nhưng đủ linh hoạt cho hầu hết các trường hợp:

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

Khi ứng dụng bắt đầu cuộc gọi AutoMapperConfiguration.Configure()và sau đó bạn có thể chiếu như thế này:

ChildClassOne child = Mapper.Map<BaseClass, ChildClassOne>(baseClass);

Các thuộc tính được ánh xạ theo quy ước vì vậy nếu lớp được kế thừa thì các tên thuộc tính hoàn toàn giống nhau và ánh xạ được cấu hình tự động. Bạn có thể thêm các thuộc tính bổ sung bằng cách điều chỉnh cấu hình. Xem tài liệu .


Sử dụng Automapper để ánh xạ một loại có một thuộc tính sang một thuộc tính khác (như OP đã mô tả) giống như dùng búa tạ để đập một quả trứng. Tại sao không chỉ tạo mới kiểu dẫn xuất và tự gán thuộc tính của nó (là 1 dòng mã).
bytedev

9

Paul, bạn đã không hỏi 'Tôi có thể làm được không' - Tôi cho rằng bạn muốn biết cách làm điều đó!

Chúng tôi phải làm điều này trong một dự án - có nhiều lớp chúng tôi thiết lập theo kiểu chung chỉ một lần, sau đó khởi tạo các thuộc tính cụ thể cho các lớp dẫn xuất. Tôi sử dụng VB nên mẫu của tôi là VB (những người quen thuộc), nhưng tôi đã lấy trộm mẫu VB từ trang web này, trang này cũng có phiên bản C # tốt hơn:

http://www.eggheadcafe.com/tutorials/aspnet/a4264125-fcb0-4757-9d78-ff541dfbcb56/net-reflection--copy-cl.aspx

Mã mẫu:

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

Tất nhiên đây không thực sự là diễn viên. Đó là tạo một đối tượng dẫn xuất mới và sao chép các thuộc tính từ cấp độ gốc, để trống các thuộc tính con. Đó là tất cả những gì tôi cần làm và có vẻ như đó là tất cả những gì bạn cần làm. Lưu ý rằng nó chỉ sao chép các thuộc tính, không sao chép các thành viên (biến công khai) trong lớp (nhưng bạn có thể mở rộng nó để làm điều đó nếu bạn xấu hổ để lộ các thành viên công khai).

Nói chung, đúc tạo ra 2 biến trỏ đến cùng một đối tượng (hướng dẫn nhỏ ở đây, vui lòng không ném ngoại lệ trường hợp góc cho tôi). Có những phân nhánh đáng kể cho điều này (bài tập cho người đọc)!

Tất nhiên tôi phải nói tại sao sự uể oải không cho phép bạn đi từ cơ sở đến ví dụ dẫn xuất, nhưng lại làm theo cách khác. hãy tưởng tượng một trường hợp mà bạn có thể lấy một phiên bản của hộp văn bản winforms (dẫn xuất) và lưu trữ nó trong một biến kiểu Winforms control. Tất nhiên 'điều khiển' có thể di chuyển đối tượng xung quanh OK và bạn có thể giải quyết tất cả những điều 'điều khiển-y' về hộp văn bản (ví dụ: thuộc tính trên cùng, bên trái, .text). Không thể nhìn thấy nội dung cụ thể của hộp văn bản (ví dụ: .multiline) nếu không truyền biến kiểu 'điều khiển' trỏ đến hộp văn bản trong bộ nhớ, nhưng nó vẫn ở đó trong bộ nhớ.

Bây giờ hãy tưởng tượng, bạn có một điều khiển và bạn muốn đặt một biến kiểu hộp văn bản vào nó. Điều khiển trong bộ nhớ bị thiếu 'multiline' và những thứ textboxy khác. Nếu bạn cố gắng tham chiếu chúng, điều khiển sẽ không phát triển một cách kỳ diệu thuộc tính nhiều dòng! Thuộc tính (nhìn nó giống như một biến thành viên ở đây, thực sự lưu trữ một giá trị - vì nó nằm trong bộ nhớ của cá thể hộp văn bản) phải tồn tại. Vì bạn đang truyền, hãy nhớ rằng nó phải là cùng một đối tượng mà bạn đang trỏ tới. Do đó, nó không phải là hạn chế về ngôn ngữ, về mặt triết học thì không thể xử lý theo cách như vậy.


1
Tôi biết đây là cách sau khi thực tế, nhưng bạn nên bao gồm "AndAlso dstProperty.CanWrite" vào thử nghiệm "If dstProperty IsNot Nothing" của bạn, để đảm bảo rằng nó không phải là thuộc tính chỉ đọc.
JamesMLV

@JamesMLV - cảm ơn bạn đã nắm bắt tốt. 'after the fact' - không có vẻ như OP sẽ chấp nhận bất kỳ câu trả lời nào :-( vì vậy không có sự thật nào để sau. Ồ tốt.
FastAl

4

Thể hiện của đối tượng nên được tạo bằng kiểu của lớp con, bạn không thể truyền một thể hiện kiểu mẹ sang kiểu con


2

Đối với tôi, chỉ cần sao chép tất cả các trường thuộc tính từ lớp cơ sở sang lớp cha như thế này là đủ:

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

Một giải pháp phổ quát cho mọi đối tượng có thể được tìm thấy tại đây


2

Kể từ C # 7.0, bạn có thể sử dụng từ khóa is để thực hiện việc này:

Với những lớp được định nghĩa:

class Base { /* Define base class */ }
class Derived : Base { /* Define derived class */ }

Sau đó, bạn có thể làm một số việc như:

void Funtion(Base b)
{
    if (b is Derived d)
    {
        /* Do something with d which is now a variable of type Derived */
    }
}

Điều này sẽ tương đương với:

void Funtion(Base b)
{
    Defined d;
    if (b is Derived)
    {
        d = (Defined)b;
        /* Do something with d */
    }
}

Bây giờ bạn có thể gọi:

Function(new Derived()); // Will execute code defined in if

Cũng như

Function(new Base()); // Won't execute code defined in if

Bằng cách đó, bạn có thể chắc chắn rằng downcast của bạn sẽ hợp lệ và sẽ không có ngoại lệ!


1

Để ép kiểu, đối tượng thực tế phải có Kiểu bằng hoặc có nguồn gốc từ Kiểu mà bạn đang cố truyền tới ...

hoặc, nói theo cách ngược lại, Kiểu bạn đang cố truyền nó phải giống, hoặc một lớp cơ sở của kiểu thực tế của đối tượng.

nếu đối tượng thực tế của bạn thuộc loại Baseclass , thì bạn không thể truyền nó sang một loại lớp dẫn xuất ...


1

Một biến thể về cách tiếp cận tuần tự hóa cho những người sử dụng ServiceStack:

var child = baseObject.ConvertTo<ChildType>();

hoặc chi tiết hơn:

var child = baseObject.ToJson().FromJson<ChildType>();

Việc tuần tự hóa của ServiceStack có thể siêu nhanh và tất cả, nhưng rõ ràng, đây không phải là giải pháp cho các chuyển đổi lớn trong quá trình truyền có độ trễ thấp, cũng như cho các loại phức tạp. Điều đó có thể rõ ràng đối với bất kỳ ai sử dụng ServiceStack, nhưng tôi nghĩ rằng tôi sẽ làm rõ trước các bình luận.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.