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


101

Я пытаюсь выполнить приведение родительского класса к дочернему, но получаю исключение InvalidCastException. У дочернего класса есть только одно свойство типа int. Кто-нибудь знает, что мне нужно делать?


Также полезно знать, что вы не можете использовать явное приведение для базовых / производных классов.
— Rzassar

Ответы:


133

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

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

У меня есть простой консоли приложение , которое бросает животное в собаке, используя вышеупомянутые две строки кода над здесь


22
Что ж, я бы не стал называть это «подавленным».
— Кирк Уолл,

Просто обратите внимание, имена переменных не такие, как указано выше.
— Джейк Гастон,

5
Мне нравится, когда кто-то мыслит нестандартно и заставляет людей замолчать, говоря OP, что это невозможно (за исключением одного или двух троллей)! Спасибо за помощь в этом. Я пытался понять это последние пару часов :)
— derekmx271 07

3
Это отличное решение. У меня был случай, когда мой дочерний класс был просто оболочкой для родителя без дополнительных функций. Я сделал это, поэтому мне не пришлось импортировать веб-ссылку в мое приложение, поскольку она находилась в моей вспомогательной библиотеке. Это позволило мне преобразовать родительский элемент в класс-оболочку. Спасибо!
— BrianVPS

1
Ты гений! :)
— Yablargo

121

Вы не можете бросить млекопитающее в собаку - это может быть кошка.

Нельзя бросать еду в бутерброд - это может быть чизбургер.

Вы не можете превратить машину в Ferrari - это может быть Honda или, точнее, вы не можете превратить Ferrari 360 Modena в Ferrari 360 Challange Stradale - есть разные части, хотя они оба Ferrari 360.


17
Понятные препятствия, отсюда и невозможность такого «кастинга». Но что, если ему нужна собака того же цвета глаз / веса / шерсти / возраста и т. Д., Что и кошка, которая содержится в объекте-млекопитающем? По сути копирование общих свойств.
— FastAl

7
FastAl, именно поэтому у нас есть интерфейсы. Mammal должен реализовывать IMammal и содержать цвет глаз, вес и т. Д. Теперь вы можете преобразовать и dog, и cat в IMammal.
— Tom Deloford

1
Вы можете попробовать превратить млекопитающее в собаку. Если это собака, это собака. В противном случае он становится нулевым. Функции "перегрузки" могут сделать невозможное преобразование из кошки в собаку возможным, если у кошки есть эти перегруженные функции, позволяющие это. Но ваша задача - справиться с потерей данных и адаптировать несуществующие данные. Например, превращение когтей в гвозди, погоня за веревкой в ​​погоню за мячом и т. Д.
— TamusJRoyce 01

Я думаю, что примеры немного резкие и избирательные, и, возможно, приведение типов - это ярлык для конструктора копирования. Например, создание феррари со свойствами, определенными в базовом объекте car. Или начните с человека и создайте мальчика. Кастинг и прямое использование? Согласитесь, это нет-нет. Но если это часть конструктора или чего-то еще, может сработать. Ответ на сериализацию ниже - приятный штрих.
— sirthomas

1
Аналогия с Ferrari NICE
— Лорд Дарт Вейдер

59

Экземпляр, на который ссылается ваша ссылка на базовый класс, не является экземпляром вашего дочернего класса. Нет ничего плохого.

Более конкретно:

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

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

Чтобы приведение было успешным, экземпляр, который вы понижаете, должен быть экземпляром класса, к которому вы понижаете (или, по крайней мере, класс, который вы понижаете, должен находиться в иерархии классов экземпляра), в противном случае cast не удастся.


или потенциально Base otherDerived = new OtherDerived (); Производный otherFail = (Производный) otherDerived;
— Блэр Конрад,

class Base {} class Derived: Base {} // В основной базе метода DerivedInstance = new Derived (); База baseInstance = новая База (); Производный товар = (Производный) производный экземпляр; Производный fail = (Производный) baseInstance; Это компилируется без ошибок в .NET 3.5. В чем проблема, о которой вы говорите?
— pradeeptp

8
@pradeeptp: Конечно, строит. Кто что сказал об ошибке компиляции?
— Greg D

18

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

Как мы знаем, в .net все кастинги делятся на две большие категории.

  1. Для типа значения
  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, которые помогут вам принимать обоснованные решения и обеспечить безопасное преобразование.


17

Бывают случаи, когда такой слепок имеет смысл.
В моем случае я получал по сети класс BASE, и мне нужно было больше функций для него. Таким образом, получение его, чтобы обрабатывать его на моей стороне со всеми наворотами, которые я хотел, и преобразование полученного класса BASE в класс DERIVED было просто не вариантом (бросает InvalidCastException, конечно)

Одним из практических нестандартных РЕШЕНИЙ было объявить вспомогательный класс EXTENSION, который на самом деле НЕ наследует класс BASE, но ВКЛЮЧАЕТ его в качестве члена.

public class BaseExtension
{
   Base baseInstance;

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

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

Если у вас слабая связь и вам просто нужна пара дополнительных функций для базового класса, ДЕЙСТВИТЕЛЬНО не имея абсолютной потребности в деривации, это может быть быстрым и простым решением.


Прав ли я, думая, что вы, вероятно, хотите, чтобы ваш BaseExtensionздесь, по крайней мере, был реализован так IBase, чтобы вы могли использовать его в аналогичных контекстах? Или это было неважно для ваших нужд?
— tobriand

иногда включение может быть подходящей заменой наследования
— Вахид Гадири

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 control. Конечно, «элемент управления» может перемещать объект вокруг ОК, и вы можете иметь дело со всеми «элементами управления», связанными с текстовым полем (например, верхнее, левое, свойства .text). Материал, специфичный для текстового поля (например, .multiline), не может быть просмотрен без преобразования переменной типа control, указывающей на текстовое поле в памяти, но она все еще находится в памяти.

Теперь представьте, что у вас есть элемент управления, и вы хотите добавить к нему переменную типа textbox. В элементе управления в памяти отсутствуют «многострочные» и другие текстовые элементы. Если вы попытаетесь ссылаться на них, элемент управления не будет волшебным образом увеличивать многострочное свойство! Свойство (посмотрите на него как на переменную-член здесь, которая на самом деле хранит значение - потому что в памяти экземпляра текстового поля есть on) должно существовать. Помните, что поскольку вы выполняете кастинг, это должен быть тот же объект, на который вы указываете. Следовательно, это не языковое ограничение, это невозможно с философской точки зрения.


1
Я знаю, что это постфактум, но вы должны включить «AndAlso dstProperty.CanWrite» в свой тест «Если dstProperty IsNot Nothing», чтобы убедиться, что это свойство не только для чтения.
— JamesMLV

@JamesMLV - спасибо, хороший улов. «постфактум» - не похоже, что ОП в любом случае примет какой-либо ответ :-( так что нет фактов, о которых можно было бы говорить. Ну что
— ж

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.