wake-up-neo.com

Kann ein Basisklassenobjekt einer abgeleiteten Klassenreferenz mit einer expliziten Typumwandlung zugewiesen werden?

Kann ein Basisklassenobjekt einer abgeleiteten Klassenreferenz mit einer expliziten Typumwandlung in C # zugewiesen werden?.

Ich habe es ausprobiert und es entsteht ein Laufzeitfehler.

68
Maddy.Shik

Ein Verweis auf eine abgeleitete Klasse muss tatsächlich auf eine Instanz der abgeleiteten Klasse (oder Null) verweisen. Wie würden Sie sonst erwarten, dass es sich benimmt?

Zum Beispiel:

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

Wenn Sie eine Instanz des Basistyps in den abgeleiteten Typ konvertieren möchten, sollten Sie eine Methode zum Erstellen einer geeigneten Instanz des abgeleiteten Typs schreiben. Oder schauen Sie sich Ihren Vererbungsbaum noch einmal an und versuchen Sie, ihn neu zu gestalten, so dass Sie dies gar nicht erst tun müssen.

81
Jon Skeet

Nein, das ist nicht möglich, da die Zuweisung zu einer abgeleiteten Klassenreferenz wie folgt aussieht: "Die Basisklasse ist ein vollwertiger Ersatz für die abgeleitete Klasse, sie kann alles, was die abgeleitete Klasse tun kann", was nicht der Fall ist, da abgeleitete Klassen im Allgemeinen anbieten mehr Funktionalität als ihre Basisklasse (zumindest ist das die Idee hinter der Vererbung).

Sie können einen Konstruktor in die abgeleitete Klasse schreiben, der ein Basisklassenobjekt als Parameter verwendet und die Werte kopiert.

Etwas wie das:

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

In diesem Fall würden Sie das Basisobjekt kopieren und ein voll funktionsfähiges abgeleitetes Klassenobjekt mit Standardwerten für abgeleitete Elemente erhalten. Auf diese Weise können Sie auch das Problem vermeiden, auf das Jon Skeet hingewiesen hat:

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!
40
Michael Klement

Ich hatte dieses Problem und löste es, indem ich eine Methode hinzufügte, die einen Typparameter übernimmt und das aktuelle Objekt in diesen Typ konvertiert.

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

Das bedeutet, dass Sie es wie folgt in Ihrem Code verwenden können:

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

Wie viele andere geantwortet haben, nein.

Ich verwende den folgenden Code in solchen unglücklichen Fällen, in denen ich einen Basistyp als abgeleiteten Typ verwenden muss. Ja, es ist ein Verstoß gegen das Liskov-Substitutionsprinzip (LSP) und ja, die meiste Zeit bevorzugen wir die Zusammensetzung gegenüber der Vererbung. Requisiten an Markus Knappen Johansson, dessen ursprüngliche Antwort darauf basiert.

Dieser Code in der Basisklasse:

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

Erlaubt:

    derivedObject = baseObect.As<derivedType>()

Da es Reflexion verwendet, ist es "teuer". Verwenden Sie entsprechend.

8
MEC

Nein, das ist nicht möglich, daher Ihr Laufzeitfehler.

Sie können einer Instanz des Basisklassentyps jedoch eine Instanz einer abgeleiteten Klasse zuweisen.

5
ybo

Sie können eine Variable , die als Basisklasse eingegeben wird, in den Typ einer abgeleiteten Klasse umwandeln. Dies führt jedoch notwendigerweise zu einer Laufzeitüberprüfung, um festzustellen, ob das tatsächlich betroffene Objekt den richtigen Typ hat.

Einmal erstellt, kann der type eines Objekts nicht geändert werden (nicht zuletzt hat er möglicherweise nicht dieselbe Größe). Sie können jedoch eine Instanz von convert erstellen, um eine new - Instanz des zweiten Typs zu erstellen. Sie müssen den Konvertierungscode jedoch manuell schreiben.

3
Marc Gravell

Wie jeder hier gesagt hat, ist das nicht direkt möglich.

Die Methode, die ich bevorzuge und ziemlich sauber ist, ist die Verwendung eines Object Mapper wie AutoMapper .

Es wird die Aufgabe übernehmen, Eigenschaften automatisch von einer Instanz zu einer anderen zu kopieren (nicht notwendigerweise derselbe Typ).

3
Mahmoodvcs

Nein, das ist nicht möglich.

Stellen Sie sich ein Szenario vor, in dem ein ACBus eine abgeleitete Klasse der Basisklasse Bus ist. ACBus verfügt über Funktionen wie TurnOnAC und TurnOffAC, die auf einem Feld namens ACState arbeiten. TurnOnAC aktiviert ACState und TurnOffAC deaktiviert ACState. Wenn Sie versuchen, TurnOnAC- und TurnOffAC-Funktionen für Bus zu verwenden, ist dies nicht sinnvoll.

2
Rohit Dodle

Es gibt tatsächlich IS einen Weg, dies zu tun. Überlegen Sie, wie Sie mit Newtonsoft JSON ein Objekt von Json deserialisieren können. Es wird (oder kann zumindest) fehlende Elemente ignorieren und alle Elemente füllen, die es kennt.

So habe ich es gemacht. Ein kleines Codebeispiel folgt meiner Erklärung.

  1. Erstellen Sie eine Instanz Ihres Objekts aus der Basisklasse und füllen Sie sie entsprechend aus.

  2. Serialisieren Sie dieses Objekt mit der "jsonconvert" -Klasse von Newtonsoft json in einen json-String.

  3. Erstellen Sie nun Ihr Unterklassenobjekt durch Deserialisierung mit der in Schritt 2 erstellten Json-Zeichenfolge. Dadurch wird eine Instanz Ihrer Unterklasse mit allen Eigenschaften der Basisklasse erstellt.

Das funktioniert wie ein Zauber! Also .. wann ist das nützlich? Einige Leute fragten, wann dies sinnvoll wäre, und schlugen vor, das Schema des OP zu ändern, um der Tatsache Rechnung zu tragen, dass Sie dies nicht mit Klassenvererbung (in .NET) tun können.

In meinem Fall habe ich eine Einstellungsklasse, die alle "Basis" -Einstellungen für einen Dienst enthält. Für bestimmte Dienste gibt es mehr Optionen, und diese stammen aus einer anderen DB-Tabelle. Diese Klassen erben also die Basisklasse. Sie haben alle unterschiedliche Optionen. Beim Abrufen der Daten für einen Service ist es daher viel einfacher, die Werte zu ERSTEN, indem eine Instanz des Basisobjekts verwendet wird. Eine Methode, um dies mit einer einzelnen DB-Abfrage auszuführen. Gleich danach erstelle ich das Unterklassenobjekt mit der oben beschriebenen Methode. Ich stelle dann eine zweite Abfrage ein und fülle alle dynamischen Werte des Unterklassenobjekts auf.

Die endgültige Ausgabe ist eine abgeleitete Klasse mit allen gesetzten Optionen. Das Wiederholen für weitere neue Unterklassen erfordert nur wenige Codezeilen. Es ist einfach und verwendet ein sehr bewährtes Paket (Newtonsoft), um die magische Wirkung zu entfalten.

Dieser Beispielcode ist vb.Net, Sie können jedoch leicht in c # konvertieren.

' 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)
2
Mark Sauer

Erweitern Sie die Antwort von @ ybo - dies ist nicht möglich, da die Instanz der Basisklasse keine Instanz der abgeleiteten Klasse ist. Es kennt nur die Mitglieder der Basisklasse und nichts über die Mitglieder der abgeleiteten Klasse.

Der Grund, aus dem Sie eine Instanz der abgeleiteten Klasse in eine Instanz der Basisklasse konvertieren können, besteht darin, dass die abgeleitete Klasse tatsächlich bereits eine Instanz der Basisklasse ist, da sie bereits über diese Mitglieder verfügt. Das Gegenteil kann nicht gesagt werden.

2
Andy
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");
    }
}

}

wenn wir ein untergeordnetes Klassenobjekt erstellen, wird das Basisklassenobjekt automatisch initiiert, sodass die Referenzvariable der Basisklasse auf ein untergeordnetes Klassenobjekt verweisen kann.

aber nicht umgekehrt, da eine untergeordnete Klassenreferenzvariable nicht auf das Basisklassenobjekt verweisen kann, da kein untergeordnetes Klassenobjekt erstellt wird.

beachten Sie außerdem, dass die Referenzvariable der Basisklasse nur das Mitglied der Basisklasse aufrufen kann.

1

Sie können eine Erweiterung verwenden:

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

In Code:

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

}
1

Lösung mit JsonConvert (anstelle von typecast)

Heute stand ich vor dem gleichen Problem und fand mit JsonConvert eine einfache und schnelle Lösung des Problems.

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

Ich weiß, das ist alt, aber ich habe es schon eine ganze Weile erfolgreich genutzt. 

   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);
        }
    } 
1
Chris

Sie können dies mit generisch tun.

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

Mit diesem Ansatz haben Sie drei Vorteile.

  1. Sie duplizieren den Code nicht
  2. Sie verwenden keine Reflektion (was langsam ist)
  3. Alle Ihre Conversions befinden sich an einem Ort
0
adeel41

Die beste Möglichkeit, dem abgeleiteten Element alle Basiseigenschaften hinzuzufügen, ist die Verwendung der Reflektion im Konstruktor. Probieren Sie diesen Code aus, ohne Methoden oder Instanzen zu erstellen.

    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) { }
        }

    }
0
Uraitz

Möglicherweise nicht relevant, aber ich konnte unter Berücksichtigung seiner Basis Code für ein abgeleitetes Objekt ausführen. Es ist definitiv hackiger als ich möchte, aber es funktioniert:

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);
0
tstone2077

Ich bin nicht einverstanden, dass es nicht möglich ist. Du kannst es so machen:

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

Verwendungszweck:

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

Eine andere Lösung ist das Hinzufügen einer Erweiterungsmethode wie folgt:

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

dann haben Sie in jeder abgeleiteten Klasse einen Konstruktor, der die Basisklasse akzeptiert:

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

Optional werden auch Zieleigenschaften überschrieben, wenn sie bereits festgelegt wurden (nicht null) oder nicht.

0
d.popov

Kann ein Basisklassenobjekt einer abgeleiteten Klassenreferenz mit einer expliziten Typumwandlung in C # zugewiesen werden?.

Es sind nicht nur explizite, sondern auch implizite Konvertierungen möglich.

Die C # -Sprache erlaubt solche Konvertierungsoperatoren nicht, aber Sie können sie trotzdem mit reinem C # schreiben, und sie funktionieren. Beachten Sie, dass die Klasse, die den impliziten Konvertierungsoperator (Derived) und die Klasse, die den Operator verwendet (Program) definiert, in separaten Assemblys definiert werden muss (z. B. befindet sich die Derived-Klasse in einem library.dll, auf den program.exe verweist, der die Program-Klasse enthält).

//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.
    }
}

Wenn Sie mithilfe der Projektreferenz in Visual Studio auf die Bibliothek verweisen, zeigt VS bei Verwendung der impliziten Konvertierung Squiggles, die jedoch nur gut kompiliert werden können. Wenn Sie nur auf den library.dll verweisen, gibt es keine Schnörkel.

0
Ark-kun

Ich habe einige Teile der vorherigen Antworten (danke dieser Autoren) kombiniert und eine einfache statische Klasse mit zwei Methoden zusammengestellt, die wir verwenden. 

Ja, es ist einfach, nein es deckt nicht alle Szenarien ab, ja es könnte erweitert und verbessert werden, nein es ist nicht perfekt, ja es könnte effizienter gemacht werden, nein es ist nicht das Beste, was geschnittenes Brot ist, ja Voll auf robuste Nuget-Paketobjekt-Mapper, die viel besser für den intensiven Gebrauch usw. geeignet sind. 

Und natürlich wird versucht, die Werte eines beliebigen Objekts einem abgeleiteten oder nicht abgeleiteten Objekt zuzuordnen (nur die öffentlichen Eigenschaften, die natürlich gleich heißen - ignoriert den Rest).

VERWENDUNGSZWECK:

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

STATISCHE UTILITY-KLASSE:

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);
            }
        });
    }
}
0
AVH

Wie wäre es mit:

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