Laden...

"Zeiger" oder Kopie

Erstellt von Kevka vor 16 Jahren Letzter Beitrag vor 15 Jahren 17.706 Views
Kevka Themenstarter:in
83 Beiträge seit 2007
vor 16 Jahren
"Zeiger" oder Kopie

Hi,
Wie weiß ich, ob ich einen "Zeiger" (Referenz?) oder eine Kopie einer Variable übergebe?
Und wie kann ich darauf Einfluss nehmen?

MfG Kevka

2.921 Beiträge seit 2005
vor 16 Jahren

vielleicht interessiert dich dazu auch:

Kopie ohne IClonable

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

N
335 Beiträge seit 2006
vor 16 Jahren

Hi!

Du solltest dich mal über Wert- und Referenz-Typen informieren 📗

Werttypen (structs, primitive Typen wie int, float, etc.) werden immer By-Value und Referenz-Typen (Klassen, Interfaces, etc.) By-Reference übergeben.

Du kannst weiter über die Keywords ref und out Einfluss auf den Übergabemechanismus nehmen 🛈

Mfg NeuroCoder

5.742 Beiträge seit 2007
vor 16 Jahren

"Zeiger" (Referenz?)

Nur der Vollständigkeit wegen: Du meinst sicherlich Referenzen. Zeiger existieren zwar auch in C#, werden aber als "unsicherer Code" bezeichnet und eigentlich nur in den seltesten Fällen benötigt.

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo NeuroCoder,

Werttypen (structs, primitive Typen wie int, float, etc.) werden immer By-Value und Referenz-Typen (Klassen, Interfaces, etc.) By-Reference übergeben.

um es mal genau zu nehmen: auch Referenztypen werden normalerweise by-Value übergeben, nur das die Values eben die Referenzen sind. Das ist deshalb wichtig so genau zu trennen, weil man auch Referenzen by-Reference übergeben kann. Du hast hast die entsprechenden Schlüsselworte out und ref ja schon genannt.

Wie weiß ich, ob ich einen "Zeiger" (Referenz?) oder eine Kopie einer Variable übergebe?

Die Antwort wurde ja schon gegeben. In alle Kürze: struct = Wert, class = Referenz.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren
ByVal?

Das würde mich auch interessieren.

über Ref und out(die ja laut Hilfe ähnlich funktionieren) kann man aber nicht als Wert übergeben.

Ich bräuchte sowas wie in VB, da kann man bei der Methodendefinition vor ein Argument einfach "ByVal" schreiben, und man bekommt einen wert.
Sowas brauch ich auch für ein C#-Programm, aber irgendwie scheint es das nicht zu geben...

Dann müsste ich ja die Nutzer meiner Klasse zwingen, dass das betreffende Argument IClonabale implementiert, damit ich mir selbst eine Kopie davon "machen" kann...oder?

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

sowas gibt es auch nicht, es sei denn, du machst du aus der Klasse eine Struktur.

Aber Klonen ist ohne hin meistens böse. Siehe Kopie ohne ICloneable

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren
Dadraus werd ich nicht schlau...

Was willst du mit dem Link sagen (ich hab ihn verständlicherweise nicht ganz gelesen, der is ja ellenlang...). Das Klonen nicht der richtige Weg ist?

Mein konkretes Problem ist folgendes:

Ich will ein Dialogfeld schreiben, mit dem man alle Arten von Auflistungen bearbeiten kann. Funktioniert auch wunderbar, aber es gibt ein Problem: Wenn der Nutzer das Dialogfeld "abbricht" sollen die Änderungen natürlich nicht übernommen werden(wie bei Quallo in dem von dir referenzierten Thread)

Wenn ich ein ByVal-Schlüsselwort in C# hätte, würde ich das so machen:


//Folgenden Code in einer Klasse, die von Form erbt
public List<Object> ShowDialog(ByVal IEnumerable A)
{
  IEnumerable OldA = A;
  If (base.ShowDialog() == DialogResult.OK)//Hier kann der User über die Steuerelemente des Forms dann Änderungen vornehmen. Die Änderungen werden in A übernommen
  {
    return A
  }
  return OldA
}


Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

Das Klonen nicht der richtige Weg ist?

ja, dass Klonen oft nicht der richtige Weg ist.

Wenn ich ein ByVal-Schlüsselwort in C# hätte, würde ich das so machen:

Naja, aber darüber brauchen wir nicht diskutieren. Das gibt es (glücklicherweise) in C# nicht.

Abgesehen davon würde dein Code so nicht funktionieren, weil du in jedem der beiden Fälle ja dieselbe Collection zurückgeben würdest. Die Ursprungs-Collection würde zwar durch das ByVal kopiert, aber die so entstandene Kopie würde durch IEnumerable OldA = A nicht erneut kopiert werden, so dass A und OldA auf dieselbe Collection verweisen. Daher ist es vollkommen egal, ob A oder OldA zurückgegeben wird. So würde es also nicht funktionieren. Das ByVal würde dir nichts nützen.

Eine Collection kann man natürlich ohne schlechtes Gewissen klonen. Wenn du den Dialog also nicht so schreiben kannst oder willst, dass er die Collection erst beim Druck auf OK ändert, dann erstelle dir eine Kopie und zwar genau statt dieser Zeile: IEnumerable OldA = A;. ByVal braucht du da zu nicht, da es - so wie dein Code ist - eh nichts nützen würde.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren
Sry

Ich habe gerade gemerkt, dass ich den obigen Code aus einer veralteten Quelldatei kopiert hab...tut mir leid!

So soll er eigentlich aussehen:

//Folgenden Code in einer Klasse, die von Form erbt
public List<Object> ShowDialog(ByVal IEnumerable A)
{
  If (base.ShowDialog() == DialogResult.OK)//Hier kann der User über die Steuerelemente des Forms dann Änderungen vornehmen. Die Änderungen werden in A übernommen
  {
    return A
  }
  return null
}

und zwar, damit der "Empfänger" des Ergebnisses dieses nicht mit der Auflistung vergleichen muss, die er übergeben hat, denn das kann dauern...

Natürlich kann man das auch anders lösen, aber wieso sagst du:

Das gibt es (glücklicherweise) in C# nicht.

In dem von dir zitierten Thread werden ja durchaus einige Szenarien beschrieben, in denen man das gebrauchen könnte

[Eigentlich wollte ich meinen obigen Post editieren, aber das ging irgendwie nicht, die ForenSoftware hat mich nicht rangelassen, is das normal?]

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

Natürlich kann man das auch anders lösen, aber wieso sagst du:

weil Klonen eben oft nicht der richtige Weg ist und wenn es das Schlüsselwort gäbe, würde sich sicher die Anzahl der Fälle, in denen unsinnigerweise/fälschlicherweise geklont würde, deutlich erhöhen. Die wenigen Fälle, in denen der Einsatz sinnvoll wäre, gingen sicher in dem Wust der falschen Verwendungen unter.

Eigentlich wollte ich meinen obigen Post editieren, aber das ging irgendwie nicht, die ForenSoftware hat mich nicht rangelassen, is das normal?

Nö, sollte gehen.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

Eine Collection kann man natürlich ohne schlechtes Gewissen klonen. Wenn du den Dialog also nicht so schreiben kannst oder willst, dass er die Collection erst beim Druck auf OK ändert, dann erstelle dir eine Kopie und zwar genau statt dieser Zeile: IEnumerable OldA = A;

Und wie mach ich das? Ich bekomme ja nicht explizit eine Collection, sondern nur ein Objekt, das IEnumerable implementiert.

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

vorneweg, du solltest besser IEnumerable<T> verwenden.

Eine Collection mit allen Elementen der Aufzählung A bekommst du dann mit

List<T> list = new List<T> (A);

Da List<T> selbst IEnumerable<T> implementiert, hast du dann genau, was du willst.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

hi herbivore,

Die Idee ist klasse!
Aber auf die Gefahr hin, dass das eine dumme Frage ist:

vorneweg, du solltest besser IEnumerable<T> verwenden.

Wie meinst du das ? In der Signatur?
Dann müsste ich ja den Typ explizit angeben oder ?((Mit diesem Typparameter steh ich Verständnis-technisch auf Kriegsfuß)

public List<object> ShowDialog(IEnumerable<object>)

Das führt aber bei einigen Auflistungen zu Problemen (z.B. bei ListBox.Items) und das darf nicht sein...es muss doch noch eine flexiblere Möglichkeit geben.
Als VB.NET-Fan steh ich schon knapp davor, das Ding nach VB.NET zu portieren, denn dort hab ich mein geliebtes "ByVal" und muss nicht nach Tricks und Kniffen suchen, an eine Kopie von A ranzukommen

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

In der Signatur?

Ja

public List<object> ShowDialog(IEnumerable<object>)  

Eher

public IEnumerable<T> ShowDialog<T>(IEnumerable<T>)

Das führt aber bei einigen Auflistungen zu Problemen (z.B. bei ListBox.Items)

Wenn du Collections hast, die nur IEnumerable, aber nicht IEnumerable<T> implementieren, kannst du eine entsprechende Überladung der Methode definieren. Grundsätzlich finde ich es aber wichtig, wenn irgend möglich, nur noch die generischen Collections zu verwenden.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

Hab das jetzt so:

    public IEnumerable<T> ShowDialog<T>(IEnumerable<T> A)
    {
      List<T> lis = new List<T>(A);

      base.ShowDialog();

      if (this.DialogResult == DialogResult.OK)
      {
        return lis;
      }
      return null;
    }

Funktioniert aber immer noch nicht: Egal, ob ich auf "Abbrechen" oder "OK" klicke, er übernimmt die Änderungen.
**
Kann es vielleicht sein**, dass die Liste die ich an die Funktion übergebe, in ihren einzelnen Elementen Referenzen und gar keine "richtigen" Werte enthält? Dann wär ja klar, wieso das nicht funktioniert...

Die Liste wird auf diese Weise erstellt und an die Funktion übergeben (is zur Abwechslung mal in VB, wieso gibts dafür keine Tags?):

   
  Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load    
    Dim W1 As New Worker
    W1.Name = "Fritz Schnell"
    Dim W2 As New Worker
    W2.Name = "Michael Stark"
    Dim W3 As New Worker
    W3.Name = "Walter Schlau"

    WorkerList.Add(W1)
    WorkerList.Add(W2)
    WorkerList.Add(W3)
  End Sub
  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim L As List(Of Object) = CE.ShowDialog(WorkerList)
    'Hier prüft er, ob L Nothing (in C# null) ist und wenn dem so ist macht er nichts mehr
End Sub

PS: Hab das Dialogfeld mittlerweile auch in VB 1:1 nachgebaut...liefert (auch mit ByVal) das selbe Ergebnis.

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

klar, mit new List<T>(A); bekommst du nur eine flache Kopie. Eine tiefe Kopie müsstest du manuell erstellen. Dann sind wir aber wieder beim Thema, dass es oft nicht passend ist. Objekte zu Klonen. Einfach ist es, wenn du den Mechanismus aus Kopie ohne ICloneable verwendest und wenn die Objekte in der Liste BusinessObjects sind. Dann kann kannst nämlich einfach durch die Liste gehen und für jedes eine eigene Transaktion aufmachen.

Du könntest sogar _StartTransaction so ändern, dass es es automatisch Listen nach Business-Objekte durchsucht und diese in die laufende Transaktion mit aufnimmt.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

klar, mit new List<T>(A); bekommst du nur eine flache Kopie. Eine tiefe Kopie müsstest du manuell erstellen.

Und wie mach ich das?

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

habe ich ja beschrieben.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

Das mit den BusinessObjects hab ich nicht so ganz gecheckt, bei msdn- und google-suche kam auch nicht wirklich was bei rum...
Aber es kommt mir so vor, als würde ich da was zweckentfremden, es muss doch mit einer sauteuren (sowohl für den Hersteller, als auch für den Kunden) Programmiersprache auch noch eine andere Möglichkeit geben, eine tiefe Kopie zu erstellen...

Dafür sollte es ein Schlüsselwort geben ("Clone"?), das einfach ALLES kopieren kann.

Auch, wenn vielleicht ein paar Idioten das Ding zum "bösen" Klonen von Objekten einsetzen würden, wäre die Sprache so um einiges mächtiger...

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

Dafür sollte es ein Schlüsselwort geben ("Clone"?), das einfach ALLES kopieren kann.

gibt es aber nicht. Ich finde auch klar, warum nicht. Aber da hat jeder seine Meinung.

Das mit den BusinessObjects hab ich nicht so ganz gecheckt, bei msdn- und google-suche kam auch nicht wirklich was bei rum...

BusinessObject ist die (Ober-)Klasse aus Kopie ohne ICloneable um Klonen von Objekten zu vermeiden.

herbivore

R
402 Beiträge seit 2005
vor 16 Jahren

Das mit den BusinessObjects hab ich nicht so ganz gecheckt, bei msdn- und google-suche kam auch nicht wirklich was bei rum...
Aber es kommt mir so vor, als würde ich da was zweckentfremden, es muss doch mit einer sauteuren (sowohl für den Hersteller, als auch für den Kunden) Programmiersprache auch noch eine andere Möglichkeit geben, eine tiefe Kopie zu erstellen...

Dafür sollte es ein Schlüsselwort geben ("Clone"?), das einfach ALLES kopieren kann.

Auch, wenn vielleicht ein paar Idioten das Ding zum "bösen" Klonen von Objekten einsetzen würden, wäre die Sprache so um einiges mächtiger...

etwas solltest du schon auch selbst machen! nicht nur vorgefertigte methoden, bzw. schlüsselwörter verwenden!

rizi

S
8.746 Beiträge seit 2005
vor 16 Jahren

Dafür sollte es ein Schlüsselwort geben ("Clone"?), das einfach ALLES kopieren kann.

Ja, in VB macht das vielleicht Sinn, aber nicht in einem System mit Referenztypen.

O
778 Beiträge seit 2007
vor 16 Jahren

@codestar:
Als jemand, der auch VB.NET bevorzugt wollte ich mal ein paar Dinge klarstellen:

  1. VB.NET hat als Sprache kaum etwas, was C# nicht auch hätte, aber das sind im Wesentlichen parametrisierbare Properties, die Unterstützung von LateBinding und leider auch die Möglichkeit, explizite Typenkonversionen implizit durchzuführen (was man ja aber Gott sei Dank auch abschalten kann), ByVal gehört NICHT dazu. Alle VB.NET-"eigenen" Funktionen kann man durch Einbinden der Microsoft.VisualBasic.dll auch aus C# heraus nutzen (was aber keiner macht).

  2. C# hat als Sprache gleichfalls kaum etwas, was VB.NET nicht auch hätte, was sich aber im wesentlichen auf die Verwendung von unsafe beschränkt.

  3. Der IL-Code, der letztendlich über den JIT-Compiler zur Ausführung gebracht wird, ist bei VB.NET und C# meist identisch (sollte er zumindest, weiß nicht, evtl hat MS auch einen Compiler mit Optimierungen bevorzugt, aber das sollte an dieser Stelle keine Rolle spielen), es ist also völlig klar, das der VB.NET Code das selbe Ergebnis bringt, wie der C#-Code, und ein "portieren" (ich würde es eher übersetzen nennen) bringt rein gar nix.

Konkret sieht es im Falle des ByVal-Problems so aus, dass VB.NET immer zwischen ByVal und ByRef unterscheidet, während C# sich auch ohne Spezifizierer begnügt (ist 1:1 ByVal) oder man kann eben auch ref davor schreiben (entspricht ByRef). Man kann in VB.NET auch eine Entsprechung für out hinbekommen, das sieht allerdings dann hässlich aus (einfach ein Attribut an den Parameter).

Ich weiß, das gehört nicht in diese Diskussion, sondern eher in "Was sind die Unterschiede zwischen C# und VB.NET?", aber das musste jetzt mal sein.

5.941 Beiträge seit 2005
vor 16 Jahren

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo onlinegurke,

interessant. Dann macht das von codester hoch gepriesene ByVal bei Referenztypen gar nicht das, was er behauptet hat? Eigentlich logisch. Kam mir sowie komisch vor, dass es sowas geben sollte. Gut zu wissen. Es kann ja eigentlich auch genausowenig funktionieren, wie ein allgemeines Clone für beliebige Objekte.

herbivore

O
778 Beiträge seit 2007
vor 16 Jahren

Nein, macht es nicht. Zumindest nicht in VB.NET, vllt in VB6, aber das ist ja was fundamental anderes. Vielleicht hat es in VB6 so funktioniert, aber davon hab ich keine Ahnung (hab noch nie VB6 angeruehrt)

C
20 Beiträge seit 2008
vor 16 Jahren

OK, dann macht ByVal wirklich nicht das, was ich dachte, aber das steht in sehr vielen Dokumentationen falsch oder unvollständig...

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

O
778 Beiträge seit 2007
vor 16 Jahren

Da steht es wird eine Kopie uebergeben, das ist auch richtig, aber eben halt eine Kopie des Zeigers. Anders sieht die Sache bei Strukturen aus, da gibt es keine Referenz und die Struktur wird kopiert. Enthaelt die Struktur Referenzen werden auch da nur die Referenzen kopiert.

0
767 Beiträge seit 2005
vor 16 Jahren

Es gibt schon eine einfache Möglichkeit, eine tiefe Kopie von fast allem zu erstellen.

Mit dem BinaryFormatter serialisieren und danach deserialisieren, schon hat man eine tiefe Kopie.

Nachteile dieser extrem simplen Methode:

  • schlechtere Performance als selber implementiertes "manuelles" Clonen
  • Alle Objekte im zu serialisierenden Graph müssen [Serializeable] sein.

Ein allgemeines Clone ist schon deshalb schlecht, weil dann auch Singletons geklont werden könnten... was das Singleton ad absurdum führen würde.

loop:
btst #6,$bfe001
bne.s loop
rts

C
20 Beiträge seit 2008
vor 16 Jahren

Ein allgemeines Clone ist schon deshalb schlecht, weil dann auch Singletons geklont werden könnten... was das Singleton ad absurdum führen würde.

KÖNNTE - Niemand muss auch nur irgendwas klonen, aber man hätte die MÖGLICHKEIT

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

ja, aber wer will schon klonen? Das braucht man doch quasi nie. Meistens will man sich den Zustand eines Objekts merken (und bei Bedarf auf wiederherstellen), aber man will doch selten das gleiche Objekt mehr als einmal haben. Darum geht ja auch die ganze Diskussion in Kopie ohne ICloneable : Wie man sich den Zustand eines Objekts merken kann, ohne es zu klonen.

herbivore

0
767 Beiträge seit 2005
vor 16 Jahren

Ein allgemeines Clone ist schon deshalb schlecht, weil dann auch Singletons geklont werden könnten... was das Singleton ad absurdum führen würde.

KÖNNTE - Niemand muss auch nur irgendwas klonen, aber man hätte die MÖGLICHKEIT

Genau. Man hätte die Möglichkeit, weniger robuste Software zu schreiben, genau das passiert, wenn man Singletons klont, das kann nur schiefgehen.

@herbivore: ich glaub jetzt weiss ich worauf du mit "robust" wirklich rauswolltest (Wen's interessiert: IList<T> oder List<T> als Funktionsrückgabewert?)

edit: Name des verlinkten Threads korrigiert... doof

loop:
btst #6,$bfe001
bne.s loop
rts

C
20 Beiträge seit 2008
vor 16 Jahren

Ich versuche mittlerweile seit geraumer Zeit, das ganze mit BusinessObjects zu machen, sieht im Moment auch noch nicht mal schlecht aus, aber es gibt ein Problem:

Man soll der Auflistung (über das Dialogfeld) auch Elemente hinzufügen können (auch entfernen und innerhalb der Auflistung verschieben). Das müsste ich dann auch wieder rückgängig machen können, deshalb meine Frage:

Kann ich von BusinessObject erben und es so um eine Eigenschaft vom Typ List<T> erweitern? (Klar kann ich, aber funktioniert das Rollback dann noch?). Dann könnte ich beim Aufruf von ShowDialog nämlich diese Eigenschaft mit der übergebenen Auflistung belegen. Dann StartTransaction und ein bischen an der Auflistung rumgefummelt, wenns dem user dann doch nicht gefällt sage ich RollBackTransaction und die Auflistung ist wieder in ihrem Urzustand.
Ich habs auch schon probiert aber irgendwie hats nicht hingehauen und jetzt würd ich gern wissen, ob ich da einen Fehler gemacht hab oder ob Businessobject das sowieso nicht kann

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

Kann ich von BusinessObject erben und es so um eine Eigenschaft vom Typ List<T> erweitern? (Klar kann ich, aber funktioniert das Rollback dann noch?).

Ja, du müsstest nur _StartTransaction virtuell machen und dann so überschreiben, dass die Liste (flach) kopiert und in _dictFields unter dem Namen ihrer Variablen eingetragen wird.

Ist quasi das gleiche wie in settings verwerfen .

Du könntest sogar BusinessObject (also das originale _StartTransaction) so erweitern, dass es mit Listen (und anderen Collections) grundsätzlich so verfährt. Wäre eine sehr sinnvolle Erweiterung. Wenn du das machst, kannst du gleich gucken, ob in der Collection BusinessObjects enthalten sind und wenn ja, diese in die laufende Transaktion einschließen. Das wäre dann eine erst recht sinnvolle Erweiterung. Wenn du das tust, kannst du einen Code gerne hier posten.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

ok, ich versuchs mal

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

C
20 Beiträge seit 2008
vor 16 Jahren

Ich hoffe, ich mache das, was du meinst:

         
// Die Schleife ist notwending, weil man sonst nicht an die privaten
      // Felder der Oberklassen kommt.
      for (t = GetType(); t != typeof(BusinessObject); t = t.BaseType)
      {
        afi = t.GetFields(BindingFlags.GetField
                         | BindingFlags.Instance
                         | BindingFlags.NonPublic
                         | BindingFlags.DeclaredOnly
                         | BindingFlags.Public); // nur zur Sicherheit
        foreach (FieldInfo fi in afi)
        {
#if SW_VERBOSE
            Console.WriteLine (new String (' ', 3 * iLevel)
                               + fi.DeclaringType + "." + fi.Name);
#endif
          if (fi.FieldType.IsEnum)//Dieser if-Block ist neu
          { 
          _dictFields[fi]=new List<char>(fi.GetValue(this));//Zu dieser Zeile hab ich zwei fragen (siehe unten)
          }
          else
          {          _dictFields[fi] = fi.GetValue(this);//das hier stand vorher statt dem if-Block
           }

        }
        listfiAll.AddRange(afi);
      }

Zwei Fragen:

  1. Wie kann ich das Ergebnis von fi.GetValue(this) in einen Typ konvertieren, den der Kopierkonstruktor akzeptiert?
  2. Wie kann ich den richtigen Typparameter übergeben? Den genauen Typparameter der Auflistung zu ermitteln, ist nicht schwer, das geht mit GetGenericArguments, aber das kann ich nicht so einfach in die eckigen Klammern schreiben...

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

die Stelle ist richtig. Die if-Abfrage ist falsch. Du willst ja herausbekommen, ob du einen Listentyp hast, nicht ob du einen enum-Typ hast.

new List<**char**&gt; ist - wie du ja selbst sagst - ungünstig, weil der Code dann nicht für beliebige Elementtypen funktioniert. Vermutlich ist es besser hier Activator.CreateInstance zu verwenden und damit Punkt 2 zu lösen. Das enthebt dich gleichzeitig der der Notwendigkeit der Typkonvertierung des Arguments und löst damit auch gleich Punkt 2.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

Ich glaube das müsstes jetzt sein, aber bei der if-Anweisung bin ich mir noch nicht ganz sicher:

 
if (fi.FieldType.GetInterface("IEnumerable") != null)
  {
    _dictFields[fi]=Activator.CreateInstance(fi.FieldType,fi.GetValue(this));
  }
else
  {          
    _dictFields[fi] = fi.GetValue(this);
  }

Hab das ganze auch schon getestet (funktioniert!!!), aber um es in mein Dialogfeld einzubauen hab ich im Moment keine Zeit, werds heut nachmittag mal probieren.

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

C
20 Beiträge seit 2008
vor 16 Jahren

Mit dem code oben gibts ein Problem: Was mach ich mit nicht generischen Auflistungen?

Businessobject müsste ja erst mal zwischen generischen und nicht generischen unterscheiden, der if-Block sieht dann so aus:

 
if (fi.FieldType.GetInterface("IEnumerable") != null)
  {
    if (fi.FieldType.IsGenericType)
      {
        _dictFields[fi]=Activator.CreateInstance(fi.FieldType,fi.GetValue(this));
       }
    else
      {
         //Aber was mach ich hier???
      }
  }
else
  {          
    _dictFields[fi] = fi.GetValue(this);
  }

Wie aber erstelle ich eine flache Kopie einer nicht generischen Auflistung?

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

Was mach ich mit nicht generischen Auflistungen?

ich glaube die untypisierten Collections aus System.Collections implementieren alle (oder zumindest teilweise) ICloneable.

_dictFields[fi]=Activator.CreateInstance(fi.FieldType,fi.GetValue(this));  

Ob diese Anweisung funktioniert hängt davon ab, ob die Collection einen "Copy-Konstruktor" implementiert. Es mag sein, dass bei den meisten oder sogar allen Collections aus System.Collections.Generic der Fall ist, aber man kann es nicht für alle (eigenen/fremden) Collections, die IEnumerable implementieren, voraussetzen. Insofern solltest du das vorher prüfen oder aber die Exception fangen und dann auf das Kopieren verzichten und die normale Zuweisung _dictFields[fi] = fi.GetValue(this); durchführen.

Ich kann das momentan nicht selber testen oder verifizieren, aber du gehst auf jeden Fall in die richtige Richtung.

Schön wäre noch, wenn du den Typ der Elemente prüfen würdest, und wenn Business-Objekte enthalten sind, diese in die Transaktion miteinbezögest.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

Hier nochmal der komplette if-Block:


if (fi.FieldType.GetInterface("IEnumerable") != null)
          {
            if (fi.FieldType.IsGenericType)
            {
              try
                {
                  _dictFields[fi] = Activator.CreateInstance(fi.FieldType, fi.GetValue(this));
                }
              catch (Exception)
                { 
                    _dictFields[fi] = fi.GetValue(this);
                }
            }
            else
            {
              if (fi.FieldType.GetInterface("ICloneable") != null)
              {
                ICloneable L = (ICloneable)fi.GetValue(this);//Von diesem "(ICloneable)" vermute ich, dass es was mit Typkonversion zu tun hat... könnte aber auch sein, dass ich hier nen riesen Bock geschossen hab...
                _dictFields[fi] = L.Clone();
              }
              else
              {
                _dictFields[fi] = fi.GetValue(this);
              }
            }
           System.Collections.IEnumerable List=(System.Collections.IEnumerable)fi.GetValue(this);
           foreach (BusinessObject BO in List)
           {
             BO.StartTransaction();
           }
          }
          else
          {          
            _dictFields[fi] = fi.GetValue(this);
          }

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo codester,

sieht doch ganz gut aus.

Allerdings wird foreach (BusinessObject BO in List) { ... knallen, sobald er das erste Objekt findet, dass kein BusinessObject ist. Besser wäre foreach (Object obj in List) { if (obj is BusinessObject) { .... Außerdem müsstest du in der Schleife _StartTransaction verwenden.

herbivore

C
20 Beiträge seit 2008
vor 16 Jahren

Allerdings wird foreach (BusinessObject BO in List) { ... knallen, sobald er das erste Objekt findet, dass kein BusinessObject ist.

OK.

Werde das neue BusinessObject jetzt mal an ein paar Sachen testen und sehen obs auch wirklich geht. Dass es mit dem Dialogfeld weiter Probleme geben wird, ist jetzt schon klar, denn die Auflistung, für die ich das Rollback gegebenenfalls anwenden will, ist vom Typ ListBox.ObjectCollection(wieso gibt es solche Extrawürste?). Der implementiert kein ICloneable und ist auch nicht generisch. Ich werd mal versuchen, ob ich das durch ein bischen Erben hinkriege...

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

C
20 Beiträge seit 2008
vor 16 Jahren

OK, mein Dialogfeld ist jetzt fertig:

Mit der übergebenen Auflistung belege ich die Eigenschaft eines Erben der BusinessObject-Klasse, so dass die Aktionen Hinzufügen, Entfernen und Verschieben eines Elementes rückgängig gemacht werden können.

Aber beim Rückgängigmachen von Änderungen an den Eigenschaften von Elementen, konnte mir BusinessObject nicht helfen, obwohl ich drei Tage alle möglichen Varianten ausprobiert habe.
Ich habe mich dann entschlossen, das Problem ungelöst zu lassen(hätte ich schon nach einem Tag machen sollen) : Die Klasse und einige ihrer Member sind abstract, so dass der Benutzer der Klasse sich selbst darum kümmern muss, dass der Status eines Elementes gespeichert wird und wie das RollBack vonstatten geht. Das hat außerdem den Vorteil, dass nicht pauschal alle Eigenschaften der Elemente "konserviert" werden müssen, sondern nur die, die auch wirklich bearbeitet werden.
Ich danke für die Hilfe!

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

C
20 Beiträge seit 2008
vor 16 Jahren

Hier noch mal der gesamte Code von BusinessObject, nachdem ich die Änderungen vorgenommen habe, sonst muss man sich das mühsam zusammenklauben:


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
//*****************************************************************************
  abstract class BusinessObject
  {
    //--------------------------------------------------------------------------

    // In welcher Transaktion das Objekt enthalten ist
    private Guid _guidTransaction = Guid.Empty;

    // Bei welchem Objekt die Transaktion begonnen wurde
    private BusinessObject _boRoot = null;

    // Alle Objekte in der Transaktion; nur beim WurzelObjekt gesetzt
    private List<BusinessObject> _listScope = null;

    // Der gespeicherte Zustand des Objekts
    private Dictionary<FieldInfo, Object> _dictFields = null;



    //==========================================================================
    public void StartTransaction()
    {
      if (_listScope != null)
      {
        throw new Exception("Keine geschachtelten Transaktionen erlaubt.");
      }

      _listScope = new List<BusinessObject>();

      try
      {
        _StartTransaction(0, Guid.NewGuid(), this);
      }
      catch (Exception)
      {
        foreach (BusinessObject boCurr in _listScope)
        {
          boCurr._guidTransaction = Guid.Empty;
          boCurr._boRoot = null;
          boCurr._dictFields = null;
        }
        _listScope = null;
        throw;
      }
    }

    //==========================================================================
    private void _StartTransaction(int iLevel,
                                    Guid guidTransaction,
                                    BusinessObject boRoot)
    {
      if (_guidTransaction != Guid.Empty)
      {
        if (_guidTransaction == guidTransaction)
        {
          return; // zyklischen Struktur, Objekt schon besucht
        }
        throw new Exception("Objekt ist schon in einer anderen "
                           + "Transaktion enthalten.");
      }

      boRoot._listScope.Add(this);
      _guidTransaction = guidTransaction; // als besucht kennzeichnen
      _boRoot = boRoot;
      _dictFields = new Dictionary<FieldInfo, Object>();

      FieldInfo[] afi;
      List<FieldInfo> listfiAll = new List<FieldInfo>();
      BusinessObject boChild;

      Type t;

      // Die Schleife ist notwending, weil man sonst nicht an die privaten
      // Felder der Oberklassen kommt.
      for (t = GetType(); t != typeof(BusinessObject); t = t.BaseType)
      {
        afi = t.GetFields(BindingFlags.GetField
                         | BindingFlags.Instance
                         | BindingFlags.NonPublic
                         | BindingFlags.DeclaredOnly
                         | BindingFlags.Public); // nur zur Sicherheit
        foreach (FieldInfo fi in afi)
        {
#if SW_VERBOSE
            Console.WriteLine (new String (' ', 3 * iLevel)
                               + fi.DeclaringType + "." + fi.Name);
#endif
          if (fi.FieldType.GetInterface("IEnumerable") != null)//mit Aulistungen soll speziell verfahren werden
          {
            if (fi.FieldType.IsGenericType)//wenn Auflistung generisch, versuchen, sie per Kopier-Konstruktor zu kopieren.
            {
              try
                {
                  _dictFields[fi] = Activator.CreateInstance(fi.FieldType, fi.GetValue(this));
                }
              catch (Exception)
                {
                  if (fi.FieldType.GetInterface("ICloneable") != null)//wenn generische Auflisung Cloneable, flach kopieren
                  {
                    ICloneable L = (ICloneable)fi.GetValue(this);
                    _dictFields[fi] = L.Clone();
                  }
                  else//ansonsten normal kopieren
                  {
                    _dictFields[fi] = fi.GetValue(this);
                  }
                }
            }
            else//wenn Auflistung nicht generisch, nachsehen, ob sie Cloneable ist
            {
              if (fi.FieldType.GetInterface("ICloneable") != null)//wenn nicht generische Auflisung Cloneable, flach kopieren
              {
                ICloneable L = (ICloneable)fi.GetValue(this);
                _dictFields[fi] = L.Clone();
              }
              else//ansonsten normal kopieren
              {
                _dictFields[fi] = fi.GetValue(this);
              }
            }
           System.Collections.IEnumerable List=(System.Collections.IEnumerable)fi.GetValue(this);
           foreach (object O in List)//Alle BusinessObjects in der Auflistung in die Transaction aufnehmen
           {
             if (O is BusinessObject)
             {
               BusinessObject BO = (BusinessObject)O;
               BO._StartTransaction(iLevel+1,guidTransaction,boRoot);
             }
           }
          }
          else
          {          
            _dictFields[fi] = fi.GetValue(this);
          }

        }
        listfiAll.AddRange(afi);
      }

      // Rekursiver Abstieg
      foreach (FieldInfo fi in listfiAll)
      {
        if (fi.FieldType.IsSubclassOf(typeof(BusinessObject)))
        {
#if SW_VERBOSE
            Console.WriteLine ("=> " + new String (' ', 3 * iLevel)
                               + fi.DeclaringType  + "." + fi.Name);
#endif
          boChild = (BusinessObject)fi.GetValue(this);
          if (boChild != null)
          {
            boChild._StartTransaction(iLevel + 1, guidTransaction, boRoot);
          }
        }
      }
    }

    //==========================================================================
    public void CommitTransaction()
    {
      if (_guidTransaction == Guid.Empty)
      {
        throw new Exception("Objekt ist in keiner Transaktion enthalten.");
      }

      if (_listScope == null)
      {
        throw new Exception("Die Transaktion wurde nicht bei diesem "
                           + "Objekt begonnen");
      }

      foreach (BusinessObject boCurr in _listScope)
      {
        boCurr._guidTransaction = Guid.Empty;
        boCurr._boRoot = null;
        boCurr._dictFields = null;
      }
      _listScope = null;
    }

    //==========================================================================
    public void RollbackTransaction()
    {
      if (_guidTransaction == Guid.Empty)
      {
        throw new Exception("Objekt ist in keiner Transaktion enthalten.");
      }

      if (_listScope == null)
      {
        throw new Exception("Die Transaktion wurde nicht bei diesem "
                           + "Objekt begonnen");
      }

      foreach (BusinessObject boCurr in _listScope)
      {
#if SW_VERBOSE
         Console.WriteLine (boCurr.GetType ());
#endif
        foreach (FieldInfo fi in boCurr._dictFields.Keys)
        {
#if SW_VERBOSE
            Console.WriteLine ("..."  + fi.DeclaringType + "." + fi.Name);
#endif
          fi.SetValue(boCurr, boCurr._dictFields[fi]);
        }
        boCurr._guidTransaction = Guid.Empty;
        boCurr._boRoot = null;
        boCurr._dictFields = null;
      }
      _listScope = null;
    }

    //==========================================================================
    ~BusinessObject()
    {
      if (_listScope != null)
      {
        RollbackTransaction();
      }
      else if (_boRoot != null)
      {
        _boRoot.RollbackTransaction();
      }
    }
  }

Wir alle sind Untertanen unseres gütigen, weisen und unermesslich reichen Herrschers William Gates des Dritten

B
66 Beiträge seit 2008
vor 15 Jahren

Sorry, dass ich das Thema wieder rauskrame 😉

Guter Thread. Habe diese Klasse nun mal ausprobiert... funktioniert soweit ganz wunderbar - genau das was ich schon lange gesucht habe.

Nun wollte ich diese Klasse im Zusammenhang mit ADO.NET for Entities und LINQ verwenden. Wie kann ich denn nun meine automatisch generierten Klassen (z.B. Klasse "Person") als BusinessObjects definieren? C# unterstützt ja leider keine Mehrfachvererbung... 😦

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo be4all,

die einzige Alternative zu Mehrfachvererbung (da wo man wirklich Mehrfachvererbung brauchen würde), die ich kenne, die immer funktioniert, die aber leider etwas umständlich ist, ist eine der Oberklassen durch ein Interface und eine Hilfsklasse zu ersetzen.

Nimm an, du hast die Klassen

public class A         { public void Ma () { /* tue was */ } }
public class B         { public void Mb () { /* tue was */ } }

und willst einen Ersatz für

public class C : A, B { ...

finden, dann würde das schematisch so aussehen:

public interface IB    {        void Mb ();                  }
public class C : A, IB { public void Mb () { _b.Mb (); } private B _b = new B (); }

Man schafft also eine Interface, dass alle öffentlichen Methoden (Member) von B enthält und degradiert B zur Hilfsklasse, die benutzt wird, um die Interface-Methode in C durch den Aufruf der entsprechenden Methode in B zu implementieren.

Dadurch, dass man man das für jede Methode (jeden Member) machen muss, ist das eben etwas umständlich. Dafür funktioniert es nach festem Schema.

Wenn du auf die generierten Klassen selbst keinen Einfluss hast, dann musst du ggf. noch eine weitere Unterklasse einführen.

public class Person { ... } // kann man nicht beeinflussen
public class TransactionalPerson : Person, ITransactional 

wobei TransactionalPerson die neue Unterklasse ist, ITransactional das neue Interface und BusinessObject zur Hilfsklasse wird.

herbivore

B
66 Beiträge seit 2008
vor 15 Jahren

Ich kann nicht ganz folgen...? Sorry...

Ich habe folgende automatisch generierte Klasse (einfache Klasse) mit einer Eigenschaft Nachname und ID:


public partial class person : global::System.Data.Objects.DataClasses.EntityObject
{
     public static person Createperson(int id) { ... }
     public int ID { get { ... } { set { ... } }
     private int _ID;
     partial void OnIDChanging(int value);
     partial void OnIDChanged();     

     public string Nachname { get { ... } { set { ... } }
     private string _Nachname;
     partial void OnNachnameChanging(string value);
     partial void OnNachnameChanged();
}

Nun muss ich ja person von BusinessObject ableiten, um meine Methoden StartTransaction, CommitTransaction und RollBackTransaction für ein person-Object zur Verfügung zu stellen.

Was ist mit eventuell vorhandenen Listen oder Objekten, die auch wieder BusinessObject implementiert haben müssten (bezogen auf Klasse person)?

Kannst du mir das bitte nochmals anhand eines Beispiels (in Anlehnung an der Klasse person) demonstrieren?

Womöglich macht die Verwendung von ADO.NET Entities oder LINQ TO SQL im Zusammenhang mit BusinessObject wenig Sinn. Ich habe schließlich nicht nur eine Klasse, die BusinessObject implementieren müsste...

Danke dir!

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo be4all,

Kannst du mir das bitte nochmals anhand eines Beispiels (in Anlehnung an der Klasse person) demonstrieren?

das Beispiel für Person steht doch schon in meinem Beitrag.

herbivore