Неможливо передати з батьківського класу на дочірній клас


97

Я намагаюся передати з батьківського класу на дочірній клас, але я отримую InvalidCastException. Дочірній клас має лише одну властивість типу int. Хтось знає, що мені потрібно робити?


Також корисно знати, що ви не можете використовувати явний кастинг для класів, пов’язаних з базою / похідною.
Rzassar

Відповіді:


133

Простий спосіб зниження в C # - це серіалізація батьків, а потім десеріалізація дочірньої дитини.

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

У мене є простий консолі додаток , яке кидає тварина в собаці, використовуючи вищезгадані два рядки коду над тут


19
Ну, я б вагався назвати це "приниженим".
Kirk Woll,

Зауважимо, що назви змінних вище не однакові.
Джейк Гастон,

5
Я люблю, коли хтось думає нестандартно і замовчує людей, говорячи ОП, що цього не можна зробити (крім одного-двох тролів)! Дякуємо за допомогу щодо цього. Я намагався це зрозуміти останні пару годин :)
derekmx271

3
Це відмінне рішення. У мене був випадок, коли мій дитячий клас був просто обгорткою для батьків без додаткової функціональності. Я зробив це, тому мені не довелося імпортувати веб-посилання у свою програму, оскільки вона була в моїй допоміжній бібліотеці. Це дозволило мені перетворити батьків у мій клас обгортки. Дякую!
BrianVPS

1
Ти геній! :)
Ябларго,

118

Не можна кидати ссавця в собаку - це може бути кішка.

Не можна кидати їжу в бутерброд - це може бути чизбургер.

Ви не можете кинути автомобіль у Ferrari - це може бути Honda, або, більш конкретно, Ви не можете залити Ferrari 360 Modena на Ferrari 360 Challange Stradale - є різні частини, хоча вони обидва Ferrari 360.


17
Зрозумілі перешкоди, звідси і неможливість насправді "лити" таким чином. Але що, якщо він хоче собаку, яка має такий самий колір очей / вагу / тип волосся / вік тощо, як кішка, яку утримують на об’єкті ссавця? По суті копіюючи загальні властивості.
FastAl

7
FastAl, саме тому у нас є інтерфейси. Ссавці повинні застосовувати IMammal і містити колір очей, вагу і т.д.
Том Делофорд,

1
Ви можете спробувати ссавця в собаку. Якщо це собака, це собака. В іншому випадку воно стає нульовим. Функції "перевантаження" можуть зробити неможливим перетворення з кота на собаку, якщо кішка має ці перевантажені функції, що дозволяють це. Але ваша робота - усунути втрату даних та адаптувати неіснуючі дані. Як перетворення кігтів на цвяхи, переслідування струни в погоню за м’ячем тощо ...
TamusJRoyce

Я думаю, що приклади трохи екстремальні та вибіркові, і, можливо, акторський склад - це ярлик для конструктора копій. Наприклад, будуючи феррарі з властивостями, визначеними в базовому об'єкті автомобіля. Або почніть з людини і створіть Хлопчика. Кастинг і безпосереднє використання? Погодьтеся, це ні-ні. Але якщо це частина конструктора або щось інше, може спрацювати. Відповідь на серіалізацію нижче - приємний штрих.
sirthomas

1
Аналогія Ferrari NICE
лорд Дарт Вейдер

57

Екземпляр, на який посилається посилання на ваш базовий клас, не є екземпляром вашого дочірнього класу. Нічого поганого немає.

Більш конкретно:

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

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

Щоб приведення було успішним, екземпляр, який ви понижаєте, повинен бути екземпляром класу, до якого ви знижуєте (або, принаймні, клас, до якого ви знижуєте, повинен бути в межах ієрархії класів екземпляра), інакше акторський склад не вдасться.


або потенційно Base otherDerived = new OtherDerived (); Виведено otherFail = (Виведено) otherDerived;
Блер Конрад

class Base {} class Derived: Base {} // В Основному методі Base derivaInstance = new Derived (); Base baseInstance = new Base (); Виведене благо = (Виведене) derivaInstance; Виведена помилка = (Виведена) baseInstance; Це компілюється без помилок у .NET 3.5. Де проблема, про яку ви говорите?
pradeeptp

7
@pradeeptp: Звичайно, це будує. Хто щось сказав про помилку компіляції?
Greg D

17

Бувають випадки, коли такий акторський склад мав би сенс.
У моєму випадку я отримував клас BASE по мережі, і мені потрібні були інші функції для нього. Отже, вивести його, щоб обробити його на моїй стороні з усіма наворотами, які я хотів, і перекинути отриманий клас BASE в похідний просто не було можливості (кидає InvalidCastException курсу)

Практичне мозкової поза коробки Рішення було оголосити клас РОЗШИРЕННЯ Helper , який НЕ успадковує базовий клас на насправді, але в той числі ІТ в якості члена.

public class BaseExtension
{
   Base baseInstance;

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

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

Якщо у вас вільне зчеплення і вам просто потрібна пара додаткових функцій для базового класу, не маючи справді абсолютної потреби у виведенні, це може бути швидким і простим обхідним шляхом.


Я не помиляюся, думаючи, що ви, мабуть, хочете, щоб ваш BaseExtensionтут принаймні реалізував IBaseтаке, щоб ви могли використовувати його в подібному контексті? Або це не було важливим для ваших потреб?
tobriand

іноді включення може бути відповідною заміною спадщини
Вахід Гадірі,

17

Я бачив, як більшість людей кажуть, що явний кастинг батьків не можна, це насправді неправда. Давайте переглянемо початок і спробуємо довести це на прикладах.

Як ми знаємо в .net, усі кастинги мають дві широкі категорії.

  1. Для типу Value
  2. Для довідкового типу (у вашому випадку це довідковий тип)

Тип посилання має ще три основні ситуаційні випадки, в яких може лежати будь-який сценарій.

Від дитини до батька (неявний кастинг - завжди успішно)

Випадок 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. Де ви можете побачити, що такий кастинг не дозволяється ООП, і ви намагаєтеся порушити одне з основних правил ООП. Тому завжди вибирайте безпечний шлях.

Більше того, щоб уникнути таких виняткових ситуацій .net рекомендував використовувати is / as оператори, які допоможуть вам приймати обґрунтовані рішення та забезпечити безпечний кастинг.


13

Це порушить об'єктно-орієнтовані принципи. Я б сказав, що елегантне рішення тут і в інших місцях проекту використовує фреймворк відображення об’єктів, такий як 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);

Властивості відображаються за домовленістю, тому, якщо клас успадковується, імена властивостей абсолютно однакові, і відображення налаштовується автоматично. Ви можете додати додаткові властивості, налаштувавши конфігурацію. Дивіться документацію .


Використання Automapper для зіставлення типу з однією властивістю до іншої (як описано в OP) - це все одно, що використовувати кувалду, щоб розбити яйце. Чому б просто не створити похідний тип і не призначити його властивість самостійно (це 1 рядок коду).
bytedev

9

Поле, ти не запитав: "Чи можу я це зробити"? Я припускаю, ти хочеш знати, як це зробити!

Нам довелося зробити це за проектом - є багато класів, які ми створили загальним чином лише один раз, а потім ініціалізуємо властивості, характерні для похідних класів. Я використовую VB, тому мій зразок знаходиться у VB (жорсткі нуги), але я викрав зразок VB з цього сайту, який також має кращу версію C #:

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

Приклад коду:

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. Звичайно, "елемент керування" може переміщати об'єкт в порядку, і ви можете мати справу з усіма речами "controlll-y" щодо текстового поля (наприклад, вгорі, зліва, властивості .text). Речі, що стосуються текстового поля (наприклад, .multiline), неможливо побачити без передачі змінної типу "control", що вказує на текстове поле в пам'яті, але вона все ще є в пам'яті.

А тепер уявіть, у вас є елемент керування, і ви хочете застосувати до нього змінну типу textbox. Елемент керування в пам'яті відсутній "багаторядковий" та інші текстові елементи. Якщо ви спробуєте зробити посилання на них, елемент керування чарівно не збільшить багаторядкову властивість! Властивість (подивіться на неї як на змінну-члена тут, яка насправді зберігає значення - оскільки воно є в пам’яті екземпляра текстового поля) повинна існувати. Оскільки ви кастуєте, пам’ятайте, це повинен бути той самий об’єкт, на який ви вказуєте. Отже, це не мовне обмеження, по-філософськи неможливо доводити такий випадок.


1
Я знаю, що це шлях фактично, але ви повинні включити "AndAlso dstProperty.CanWrite" до тесту "If dstProperty IsNot Nothing", щоб переконатися, що його властивість не лише для читання.
JamesMLV

@JamesMLV - дякую хороший улов. "після факту" - схоже, ОП у будь-якому випадку не прийме будь-яку відповідь :-( так що факту не слід після. Ну добре.
FastAl

4

Екземпляр об'єкта слід створювати з використанням типу дочірнього класу, ви не можете передати екземпляр батьківського типу до дочірнього типу


2

Що стосується мене, то було достатньо скопіювати всі поля властивостей з базового класу до батьківського так:

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

Універсальне рішення для будь-якого об’єкта можна знайти тут


2

Починаючи з 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

Таким чином ви можете бути впевнені в тому, що ваше зниження буде дійсним і не буде винятком!


1

Для трансляції фактичний об’єкт повинен мати тип, який дорівнює типовому типу, який ви намагаєтесь здійснити, або отриманий із нього ...

або, щоб вказати це протилежним чином, тип, до якого ви намагаєтесь його привести, повинен бути таким самим, як базовий клас фактичного типу об’єкта.

якщо ваш фактичний об'єкт має тип Baseclass , тоді ви не можете передати його у похідний клас Type ...


1

Варіація підходу серіалізації для тих, хто використовує ServiceStack:

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

або більш багатослівний:

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

Серіалізація ServiceStack може бути надзвичайно швидкою, і все, але очевидно, що це не рішення для масових перетворень у передачах із низькою затримкою, а також для дуже складних типів. Це, мабуть, очевидно кожному, хто використовує ServiceStack, але думав, що я пояснить це в очікуванні коментарів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.