Laden...
C
Benutzerbeschreibung
Ich programmiere jetzt schon seit ein paar Jahren. Zuerst mit VBA , dann mit VB 6 und seit Mai 07 mit VB 2005. Weil ich mittlerweile ganz passabel programmieren kann versuche ich mich seit Januar 08 auch in C#

Forenbeiträge von codester Ingesamt 20 Beiträge

07.02.2008 - 16:59 Uhr

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

07.02.2008 - 16:49 Uhr

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!

04.02.2008 - 15:15 Uhr

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

04.02.2008 - 11:41 Uhr

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

03.02.2008 - 13:54 Uhr

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?

03.02.2008 - 11:02 Uhr

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.

02.02.2008 - 11:10 Uhr

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...
02.02.2008 - 10:08 Uhr

ok, ich versuchs mal

01.02.2008 - 20:56 Uhr

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

30.01.2008 - 19:57 Uhr

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

30.01.2008 - 16:05 Uhr

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

29.01.2008 - 19:51 Uhr

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

28.01.2008 - 16:49 Uhr

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?

28.01.2008 - 16:26 Uhr

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.

26.01.2008 - 14:36 Uhr

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

26.01.2008 - 11:16 Uhr

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.

25.01.2008 - 23:26 Uhr

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?]

25.01.2008 - 23:02 Uhr

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
}


25.01.2008 - 21:21 Uhr

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?

25.01.2008 - 20:42 Uhr

Ja, this entspricht dem "Me" aus VB. Base steht für die zugrundeliegende Klasse, von der geerbt wird.