Можно ли присвоить объект базового класса ссылке на производный класс с явным приведением типов?

StackOverflow https://stackoverflow.com/questions/729527

Вопрос

Можно ли присвоить объект базового класса ссылке на производный класс с явным приведением типов в C#?

Я попробовал это, и это создает ошибку во время выполнения.

Это было полезно?

Решение

Нет.Ссылка на производный класс должна фактически ссылаться на экземпляр производного класса (или на ноль).В противном случае, как бы вы ожидали, что он поведет себя?

Например:

object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?

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

Другие советы

Нет, это невозможно, поскольку присвоение его ссылке на производный класс было бы равносильно утверждению: «Базовый класс является полностью функциональной заменой производного класса, он может делать все, что может делать производный класс», что неверно, поскольку производные классы в целом предлагают больше функциональности, чем их базовый класс (по крайней мере, в этом заключается идея наследования).

Вы можете написать конструктор в производном классе, принимая объект базового класса в качестве параметра и копируя значения.

Что-то вроде этого:

public class Base {
    public int Data;

    public void DoStuff() {
        // Do stuff with data
    }
}

public class Derived : Base {
    public int OtherData;

    public Derived(Base b) {
        this.Data = b.Data;
        OtherData = 0; // default value
    }

    public void DoOtherStuff() {
        // Do some other stuff
    }
}

В этом случае вы скопируете базовый объект и получите полнофункциональный объект производного класса со значениями по умолчанию для производных членов.Таким образом вы также можете избежать проблемы, на которую указал Джон Скит:

Base b = new Base();
Dervided d = new Derived();

b.DoStuff();    // OK
d.DoStuff();    // Also OK
b.DoOtherStuff();    // Won't work!
d.DoOtherStuff();    // OK

d = new Derived(b);  // Copy construct a Derived with values of b
d.DoOtherStuff();    // Now works!

У меня была эта проблема, и я решил ее, добавив метод, который принимает параметр типа и преобразует текущий объект в этот тип.

public TA As<TA>() where TA : Base
{
    var type = typeof (TA);
    var instance = Activator.CreateInstance(type);

     PropertyInfo[] properties = type.GetProperties();
     foreach (var property in properties)
     {
         property.SetValue(instance, property.GetValue(this, null), null);
     }

     return (TA)instance;
}

Это означает, что вы можете использовать его в своем коде следующим образом:

var base = new Base();
base.Data = 1;
var derived = base.As<Derived>();
Console.Write(derived.Data); // Would output 1

Как ответили многие другие, нет.

Я использую следующий код в тех неудачных случаях, когда мне нужно использовать базовый тип в качестве производного типа.Да, это нарушение принципа замены Лискова (LSP), и да, большую часть времени мы предпочитаем композицию наследованию.Респект Маркусу Кнаппену Йоханссону, на чьем оригинальном ответе основан этот ответ.

Этот код в базовом классе:

    public T As<T>()
    {
        var type = typeof(T);
        var instance = Activator.CreateInstance(type);

        if (type.BaseType != null)
        {
            var properties = type.BaseType.GetProperties();
            foreach (var property in properties)
                if (property.CanWrite)
                    property.SetValue(instance, property.GetValue(this, null), null);
        }

        return (T) instance;
    }

Позволяет:

    derivedObject = baseObect.As<derivedType>()

Поскольку он использует отражение, он «дорогой».Используйте соответственно.

Нет, это невозможно, отсюда и ваша ошибка во время выполнения.

Но вы можете присвоить экземпляр производного класса переменной типа базового класса.

Как все здесь сказали, это невозможно напрямую.

Метод, который я предпочитаю и довольно чист, заключается в использовании Object Mapper, например АвтоМаппер.

Он автоматически выполнит задачу копирования свойств из одного экземпляра в другой (не обязательно того же типа).

Вы можете разыграть переменная который типизирован как базовый класс по типу производного класса;однако по необходимости будет выполнена проверка во время выполнения, чтобы убедиться, что фактический объект имеет правильный тип.

После создания тип объекта не может быть изменен (не в последнюю очередь, он может быть другого размера).Однако вы можете конвертировать экземпляр, создавая новый экземпляр второго типа — но код преобразования нужно писать вручную.

Расширяя ответ @ybo - это невозможно, потому что имеющийся у вас экземпляр базового класса на самом деле не является экземпляром производного класса.Он знает только о членах базового класса и ничего не знает о членах производного класса.

Причина, по которой вы можете привести экземпляр производного класса к экземпляру базового класса, заключается в том, что производный класс на самом деле уже является экземпляром базового класса, поскольку у него уже есть эти члены.Обратное нельзя сказать.

Нет, это невозможно.

Рассмотрим сценарий, в котором ACBus является производным классом базового класса Bus.ACBus имеет такие функции, как TurnOnAC и TurnOffAC, которые работают с полем ACState.TurnOnAC включает ACState, а TurnOffAC выключает ACState.Если вы попытаетесь использовать функции TurnOnAC и TurnOffAC на шине, это не имеет смысла.

class Program
{
    static void Main(string[] args)
    {
        a a1 = new b();  
        a1.print();  
    }
}
class a
{
    public a()
    {
        Console.WriteLine("base class object initiated");
    }
    public void print()
    {
        Console.WriteLine("base");
    }
}
class b:a
{
    public b()
    {
        Console.WriteLine("child class object");
    }
    public void print1()
    {
        Console.WriteLine("derived");
    }
}

}

когда мы создаем объект дочернего класса, объект базового класса автоматически инициируется, поэтому ссылочная переменная базового класса может указывать на объект дочернего класса.

но не наоборот, потому что ссылочная переменная дочернего класса не может указывать на объект базового класса, поскольку объект дочернего класса не создан.

а также обратите внимание, что ссылочная переменная базового класса может вызывать только член базового класса.

На самом деле ЕСТЬ способ сделать это.Подумайте, как можно использовать Newtonsoft JSON для десериализации объекта из json.Он будет (или, по крайней мере, может) игнорировать недостающие элементы и заполнять все элементы, о которых ему известно.

Итак, вот как я это сделал.После моего объяснения последует небольшой пример кода.

  1. Создайте экземпляр вашего объекта из базового класса и заполните его соответствующим образом.

  2. Используя класс «jsonconvert» Newtonsoft json, сериализуйте этот объект в строку json.

  3. Теперь создайте объект подкласса путем десериализации с помощью строки json, созданной на шаге 2.Это создаст экземпляр вашего подкласса со всеми свойствами базового класса.

Это работает как шарм!Так..когда это полезно?Некоторые люди спрашивали, когда это будет иметь смысл, и предлагали изменить схему OP, чтобы учесть тот факт, что вы не можете сделать это с помощью наследования классов (в .Net).

В моем случае у меня есть класс настроек, содержащий все «базовые» настройки сервиса.У определенных сервисов больше возможностей, и они взяты из другой таблицы БД, поэтому эти классы наследуют базовый класс.У них у всех разный набор опций.Поэтому при получении данных для службы гораздо проще ПЕРВЫМ заполнить значения, используя экземпляр базового объекта.Один из способов сделать это с помощью одного запроса к БД.Сразу после этого я создаю объект подкласса, используя метод, описанный выше.Затем я делаю второй запрос и заполняю все динамические значения объекта подкласса.

Конечным результатом является производный класс со всеми установленными параметрами.Повторение этого для дополнительных новых подклассов занимает всего несколько строк кода.Это просто и использует очень проверенный и проверенный пакет (Newtonsoft), чтобы все волшебство сработало.

Этот пример кода — vb.Net, но вы можете легко преобразовать его в C#.

' First, create the base settings object.
    Dim basePMSettngs As gtmaPayMethodSettings = gtmaPayments.getBasePayMethodSetting(payTypeId, account_id)
    Dim basePMSettingsJson As String = JsonConvert.SerializeObject(basePMSettngs, Formatting.Indented)

    ' Create a pmSettings object of this specific type of payment and inherit from the base class object
    Dim pmSettings As gtmaPayMethodAimACHSettings = JsonConvert.DeserializeObject(Of gtmaPayMethodAimACHSettings)(basePMSettingsJson)

Возможно, это неуместно, но мне удалось запустить код на производном объекте, учитывая его основу.Это определенно более хакерски, чем мне хотелось бы, но это работает:

public static T Cast<T>(object obj)
{
    return (T)obj;
}

...

//Invoke parent object's json function
MethodInfo castMethod = this.GetType().GetMethod("Cast").MakeGenericMethod(baseObj.GetType());
object castedObject = castMethod.Invoke(null, new object[] { baseObj });
MethodInfo jsonMethod = baseObj.GetType ().GetMethod ("ToJSON");
return (string)jsonMethod.Invoke (castedObject,null);

Вы можете использовать расширение:

public static void CopyOnlyEqualProperties<T>(this T objDest, object objSource) where T : class
    {
        foreach (PropertyInfo propInfo in typeof(T).GetProperties())
            if (objSource.GetType().GetProperties().Any(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()))
                propInfo.SetValue(objDest, objSource.GetType().GetProperties().First(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()).GetValue(objSource));
    }

В коде:

public class BaseClass
{
  public string test{ get; set;}
}
public Derived : BaseClass
{
//Some properies
}

public void CopyProps()
{
   BaseClass baseCl =new BaseClass();
   baseCl.test="Hello";
   Derived drv=new Derived();
   drv.CopyOnlyEqualProperties(baseCl);
   //Should return Hello to the console now in derived class.
   Console.WriteLine(drv.test);

}

Я знаю, что это устарело, но я успешно использовал это уже довольно давно.

   private void PopulateDerivedFromBase<TB,TD>(TB baseclass,TD derivedclass)
    {
        //get our baseclass properties
        var bprops = baseclass.GetType().GetProperties();
        foreach (var bprop in bprops)
        {
            //get the corresponding property in the derived class
            var dprop = derivedclass.GetType().GetProperty(bprop.Name);
            //if the derived property exists and it's writable, set the value
            if (dprop != null && dprop.CanWrite)
                dprop.SetValue(derivedclass,bprop.GetValue(baseclass, null),null);
        }
    } 

Решение с помощью JsonConvert (вместо приведения типов)

Сегодня я столкнулся с той же проблемой и нашел простой и быстрое решение проблемы с использованием JsonConvert.

var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);

Другое решение — добавить метод расширения следующим образом:

 public static void CopyProperties(this object destinationObject, object sourceObject, bool overwriteAll = true)
        {
            try
            {
                if (sourceObject != null)
                {
                    PropertyInfo[] sourceProps = sourceObject.GetType().GetProperties();
                    List<string> sourcePropNames = sourceProps.Select(p => p.Name).ToList();
                    foreach (PropertyInfo pi in destinationObject.GetType().GetProperties())
                    {
                        if (sourcePropNames.Contains(pi.Name))
                        {
                            PropertyInfo sourceProp = sourceProps.First(srcProp => srcProp.Name == pi.Name);
                            if (sourceProp.PropertyType == pi.PropertyType)
                                if (overwriteAll || pi.GetValue(destinationObject, null) == null)
                                {
                                    pi.SetValue(destinationObject, sourceProp.GetValue(sourceObject, null), null);
                                }
                        }
                    }
                }
            }
            catch (ApplicationException ex)
            {
                throw;
            }
        }

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

  public class DerivedClass: BaseClass
    { 
        public DerivedClass(BaseClass baseModel)
        {
            this.CopyProperties(baseModel);
        }
    }

Он также может перезаписать свойства назначения, если они уже установлены (не равны нулю) или нет.

Можно ли присвоить объект базового класса ссылке на производный класс с явным приведением типов в C#?

Возможны не только явные, но и неявные преобразования.

Язык C# не поддерживает такие операторы преобразования, но вы все равно можете писать их на чистом C#, и они работают.Обратите внимание, что класс, определяющий оператор неявного преобразования (Derived) и класс, который использует оператор (Program) должны быть определены в отдельных сборках (например.тот Derived класс находится в library.dll на который ссылается program.exe содержащий Program сорт).

//In library.dll:
public class Base { }

public class Derived {
    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Implicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }

    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Explicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }
}

//In program.exe:
class Program {
    static void Main(string[] args) {
        Derived z = new Base(); //Visual Studio can show squiggles here, but it compiles just fine.
    }
}

Когда вы ссылаетесь на библиотеку, используя ссылку на проект в Visual Studio, VS показывает волнистые линии при использовании неявного преобразования, но компилируется нормально.Если вы просто ссылаетесь на library.dll, никаких закорючек нет.

Вы можете сделать это, используя общий.

public class BaseClass
{
    public int A { get; set; }
    public int B { get; set; }
    private T ConvertTo<T>() where T : BaseClass, new()
    {
         return new T
         {
             A = A,
             B = B
         }
    }

    public DerivedClass1 ConvertToDerivedClass1()
    {
         return ConvertTo<DerivedClass1>();
    }

    public DerivedClass2 ConvertToDerivedClass2()
    {
         return ConvertTo<DerivedClass2>();
    }
}

public class DerivedClass1 : BaseClass
{
    public int C { get; set; }
}

public class DerivedClass2 : BaseClass
{
    public int D { get; set; }
}

Используя этот подход, вы получаете три преимущества.

  1. Вы не дублируете код
  2. Вы не используете отражение (что медленно)
  3. Все ваши конверсии находятся в одном месте

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

Да, это просто, нет, оно не охватывает все сценарии, да, его можно расширить и сделать лучше, нет, оно не идеально, да, возможно, его можно сделать более эффективным, нет, это не самая лучшая вещь со времен нарезанного хлеба, да, они есть. полнофункциональные надежные средства отображения объектов пакета Nuget, которые намного лучше подходят для интенсивного использования и т. д. и т. п., да-яда - но это работает для наших основных потребностей, хотя :)

И, конечно же, он попытается сопоставить значения любого объекта с любым объектом, производным или нет (конечно, только общедоступные свойства с одинаковыми именами - остальные игнорируются).

ИСПОЛЬЗОВАНИЕ:

SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };

// creates new object of type "RealPerson" and assigns any matching property 
// values from the puppet object 
// (this method requires that "RealPerson" have a parameterless constructor )
RealPerson person = ObjectMapper.MapToNewObject<RealPerson>(puppet);

// OR

// create the person object on our own 
// (so RealPerson can have any constructor type that it wants)
SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };
RealPerson person = new RealPerson("tall") {Name = "Steve"};

// maps and overwrites any matching property values from 
// the puppet object to the person object so now our person's age will get set to 5 and
// the name "Steve" will get overwritten with "Elmo" in this example
ObjectMapper.MapToExistingObject(puppet, person);

СТАТИЧЕСКИЙ КЛАСС ПОЛЕЗНОСТИ:

public static class ObjectMapper
{
    // the target object is created on the fly and the target type 
    // must have a parameterless constructor (either compiler-generated or explicit) 
    public static Ttarget MapToNewObject<Ttarget>(object sourceobject) where Ttarget : new()
    {
        // create an instance of the target class
        Ttarget targetobject = (Ttarget)Activator.CreateInstance(typeof(Ttarget));

        // map the source properties to the target object
        MapToExistingObject(sourceobject, targetobject);

        return targetobject;
    }

    // the target object is created beforehand and passed in
    public static void MapToExistingObject(object sourceobject, object targetobject)
    {
        // get the list of properties available in source class
        var sourceproperties = sourceobject.GetType().GetProperties().ToList();

        // loop through source object properties
        sourceproperties.ForEach(sourceproperty => {

            var targetProp = targetobject.GetType().GetProperty(sourceproperty.Name);

            // check whether that property is present in target class and is writeable
            if (targetProp != null && targetProp.CanWrite)
            {
                // if present get the value and map it
                var value = sourceobject.GetType().GetProperty(sourceproperty.Name).GetValue(sourceobject, null);
                targetobject.GetType().GetProperty(sourceproperty.Name).SetValue(targetobject, value, null);
            }
        });
    }
}

Как насчет:

public static T As<T>(this object obj)
    {
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
    }

Лучший способ добавить все базовые свойства к производному элементу — использовать отражение в кострукторе.Попробуйте этот код, не создавая методы или экземпляры.

    public Derived(Base item) :base()
    {

        Type type = item.GetType();

        System.Reflection.PropertyInfo[] properties = type.GetProperties();
        foreach (var property in properties)
        {
            try
            {
                property.SetValue(this, property.GetValue(item, null), null);
            }
            catch (Exception) { }
        }

    }

Я не согласен, что это невозможно.Вы можете сделать это следующим образом:

public class Auto 
{ 
    public string Make {get; set;}
    public string Model {get; set;}
}

public class Sedan : Auto
{ 
    public int NumberOfDoors {get; set;}
}

public static T ConvertAuto<T>(Sedan sedan) where T : class
{
    object auto = sedan;
    return (T)loc;
}

Использование:

var sedan = new Sedan();
sedan.NumberOfDoors = 4;
var auto = ConvertAuto<Auto>(sedan);

Нет, посмотрите этот вопрос, который я задал - Upcasting в .NET с использованием дженериков

Лучший способ — создать конструктор по умолчанию для класса, сконструировать и затем вызвать Initialise метод

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top