Я пытаюсь выполнить приведение родительского класса к дочернему, но получаю исключение InvalidCastException. У дочернего класса есть только одно свойство типа int. Кто-нибудь знает, что мне нужно делать?
Я пытаюсь выполнить приведение родительского класса к дочернему, но получаю исключение InvalidCastException. У дочернего класса есть только одно свойство типа int. Кто-нибудь знает, что мне нужно делать?
Ответы:
Простой способ понижения в C # - сериализовать родительский элемент, а затем десериализовать его в дочерний элемент.
var serializedParent = JsonConvert.SerializeObject(parentInstance);
Child c = JsonConvert.DeserializeObject<Child>(serializedParent);
У меня есть простой консоли приложение , которое бросает животное в собаке, используя вышеупомянутые две строки кода над здесь
Вы не можете бросить млекопитающее в собаку - это может быть кошка.
Нельзя бросать еду в бутерброд - это может быть чизбургер.
Вы не можете превратить машину в Ferrari - это может быть Honda или, точнее, вы не можете превратить 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
Чтобы приведение было успешным, экземпляр, который вы понижаете, должен быть экземпляром класса, к которому вы понижаете (или, по крайней мере, класс, который вы понижаете, должен находиться в иерархии классов экземпляра), в противном случае cast не удастся.
Я видел, как большинство людей говорили, что явное приведение родительского к дочернему невозможно, что на самом деле неверно. Возьмем новое начало и попробуем доказать это на примерах.
Как мы знаем, в .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. Если вы видите, что такое приведение не разрешено ООП, и вы пытаетесь нарушить одно из основных правил ООП, всегда выбирайте безопасный путь.
Более того, во избежание таких исключительных ситуаций .net рекомендует использовать операторы is / as, которые помогут вам принимать обоснованные решения и обеспечить безопасное преобразование.
Бывают случаи, когда такой слепок имеет смысл.
В моем случае я получал по сети класс 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, чтобы вы могли использовать его в аналогичных контекстах? Или это было неважно для ваших нужд?
Это нарушило бы объектно-ориентированные принципы. Я бы сказал, что элегантное решение здесь и в другом месте проекта использует структуру сопоставления объектов, такую как 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 (жесткие ниггеры), но я украл образец 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. Конечно, «элемент управления» может перемещать объект вокруг ОК, и вы можете иметь дело со всеми «элементами управления», связанными с текстовым полем (например, верхнее, левое, свойства .text). Материал, специфичный для текстового поля (например, .multiline), не может быть просмотрен без преобразования переменной типа control, указывающей на текстовое поле в памяти, но она все еще находится в памяти.
Теперь представьте, что у вас есть элемент управления, и вы хотите добавить к нему переменную типа textbox. В элементе управления в памяти отсутствуют «многострочные» и другие текстовые элементы. Если вы попытаетесь ссылаться на них, элемент управления не будет волшебным образом увеличивать многострочное свойство! Свойство (посмотрите на него как на переменную-член здесь, которая на самом деле хранит значение - потому что в памяти экземпляра текстового поля есть on) должно существовать. Помните, что поскольку вы выполняете кастинг, это должен быть тот же объект, на который вы указываете. Следовательно, это не языковое ограничение, это невозможно с философской точки зрения.
Лично мне было достаточно скопировать все поля свойств из базового класса в родительский следующим образом:
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 , вы не можете привести его к производному классу Type ...
Вариант подхода к сериализации для тех, кто использует ServiceStack:
var child = baseObject.ConvertTo<ChildType>();
или, что более подробно:
var child = baseObject.ToJson().FromJson<ChildType>();
Сериализация ServiceStack может быть очень быстрой и все такое, но очевидно, что это не решение ни для массовых преобразований при передаче данных с малой задержкой, ни для очень сложных типов. Вероятно, это очевидно для любого, кто использует ServiceStack, но подумал, что я уточню в ожидании комментариев.