Announcement

Collapse
No announcement yet.

Referenz ode nun doch nicht?

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Referenz ode nun doch nicht?

    Hallo,

    ich bin relativ neu im arbeiten mit c# und habe nun ein problem mit der inkonsistenten referenz geschichte in c#.

    also mein problem ist das ich eine list habe in die ich objecte eintrage

    also im prinzip sowas:

    Code:
    class A
    {
    
    }
    
    //... main function
    A a1;
    A a2;
    A a3;
    
    List <A> l = new List<A>();
    l.add(a1);
    l.add(a2);
    l.add(a3);
    
    //...
    wenn ich mir mir nun mit der find funktion ein object zurückgeben lasse, sollte ich doch eigentlich wieder eine referenze auf das object zurück bekommen. oder erzeugt "Add()" eine kopie meines objektes? falls dem so ist wie kann ich mit referenzen arbeiten?

    in c++ würde ich an die liste einen pointer auf das object übergeben.

    noch einfacher wäre es wenn ich schon in einer foreach schleife in der ich über die list iteriere direkt das object bearbeiten könnte was anscheinend leider auch nicht geht was ich zimlich dämlich finde und das eigetnlich referenzen problem nicht lösen würde.

    hat jemand eine idee? habe ich in der c# sprache was übersehen?


    mfg
    Zebes

  • #2
    Hallo Zebes,

    ich glaube, Du erleichterst Dir das Verständnis erheblich, wenn Du nicht an Referenzen und Pointer denkst. Ein Objekt befindet sich "irgendwo" im Arbeitsspeicher; Du benutzt immer das Objekt; wenn Du es einer Liste hinzufügst, dann steht das Objekt in der Liste. Punkt. (NET benutzt intern natürlich einen Zeiger auf die Position des Objekts im Arbeitsspeicher; aber das braucht Dich nicht zu kümmern.)

    Mit Find bekommst Du mit Sicherheit "Dein" Objekt zurück; das Problem besteht eher darin, die Suchbedingung zu formulieren oder zu programmieren; siehe in der Doku das Beispiel unter List.Find-Methode. Die Deklaration sieht so aus:
    Code:
    public T Find (Predicate<T> match)
    Das beweist bereits, dass der Rückgabewert vom Typ T ist; aber auch die Auswertung muss <T> benutzen.

    foreach liefert vermutlich deshalb einen nur-Lesen-Zugriff auf die Liste, weil sie unsortiert ist. Die for-Schleife sollte aber helfen (ich kann mich jetzt nicht erinnern, wo ich das schon gemacht habe; deshalb konstruiere ich aus Deiner Klasse ein Beispiel):
    Code:
    A item;
    for(int x1 = 0; x1 < l.Count; x1++) {
        item = l[x1];
        //  item kann (sicherlich?) bearbeitet werden
    }
    Für schnelleren Zugriff gibt es auch noch Dictionary<int, T> oder HashTables.

    Ich hoffe, ich habe Dich nicht zusätzlich verwirrt. Jürgen

    Nachtrag: Ich habe mich vor allem auf die allgemeinen Fragen konzentriert und auf konkrete Fehler nicht geachtet. Die Hinweise und Fehlermeldungen von Ralf Jansen und Olaf Steffen sind in der Tat richtig.
    Zuletzt editiert von Jürgen Thomas; 09.06.2007, 10:11. Reason: Nachtrag ergänzt, nachdem ich die späteren Posts gelesen habe.

    Comment


    • #3
      Add erzeugt dir keine Kopie. Und foreach sollte auch eigentlich funktionieren wenn du denn Inhalt von A änderst.

      Kann es sein das es in deinen Test keine Klasse A gibt sondern du einen Standarddatentyp wie string oder DateTime benutzt hast?
      Dann kommt es zu dem von dir beschrieben Effekt.

      Zum Beispiel

      Code:
        string s1 = "1";
        string s2 = "2";
        string s3 = "3";
      
        List<string> l = new List<string>();
        l.Add(s1);
        l.Add(s2);
        l.Add(s3);
      
        foreach (string item in l)
          item = "4";
      Dieser Code geht nicht da "4" ein string Objekt ist und du quasi den Pointer den item darstellt auf dieses Objekt drehen willst. Das ist in foreach nicht erlaubt.

      Folgendes geht aber (Annahme A ist eine Klasse mit einer string Property Namens value)

      Code:
        A a1 = new A();
        A a2 = new A();
        A a3 = new A();
      
        a1.value = "1";
        a2.value = "2";
        a3.value = "3";
      
        List<A> l = new List<A>();
        l.Add(a1);
        l.Add(a2);
        l.Add(a3);
      
        foreach (string item in l)
          item.Value = "4";  
      
        // jetzt steht auch in a1,a2,a3 4

      Comment


      • #4
        Hallo,

        ich würde sagen, dass eigentlich alles richtig aussieht, allerdings hast Du nur Variablen definiert, aber keine Instanzen erstellt:
        Code:
        class A
        {
        
        }
        
        //... main function
        A a1 = new A(); //!
        A a2 = new A(); //!
        A a3 = new A(); //!
        
        List <A> l = new List<A>();
        l.add(a1); // oder auch schneller l.add(new A()); in z.B. einer Schleife
        l.add(a2);
        l.add(a3);
        
        //...
        Dann sollte alles laufen.

        Gruß
        Olaf

        Comment


        • #5
          also es handelt sich tatsächlich um structuren die ich verwende, ich instanziere die auch mit new. wenn ich in der foreach schleife den wert von item ändern will bekomme ich die fehler meldung

          "Fehler CS1654: "item" ist "foreach-Iterationsvariable", daher können die zugehörigen Member nicht geändert werden."

          benutze ich eine for schleife so bekomme ich den fehler

          Fehler CS1612: Der Rückgabewert "System.Collections.Generic.List<ReferenceOrNot.B> .this[int]" kann nicht geändert werden, da er keine Variable ist.

          mfg
          Zebes

          Comment


          • #6
            also ich habe es mal mit einer hashtable ausprobiert und das klapt, vorausgesetzt ich verwende eine klasse und nicht zb string, einwandfrei.

            jetzt muss ich nur noch herausfinden warum das mit einer liste nicht geht.

            angenommen ich wollte string verwenden, gibt es da die möglich keit eine referenz an den container weiterzu geben?

            mfg
            Zebes

            Comment


            • #7
              List<T>.Add() packt nur eine Referenz in die Liste auch bei strings.

              Dein Problem ist das man den Inhalt von strings nicht direkt bearbeiten kann. Jede Änderung eines strings über Methoden der string-Klasse gibt dir eine neue string Instanz zurück.

              Zitat aus der Hilfe zur Stringklasse

              A String object is called immutable (read-only) because its value cannot be modified once it has been created. Methods that appear to modify a String object actually return a new String object that contains the modification. If it is necessary to modify the actual contents of a string-like object, use the System.Text.StringBuilder class.

              Vielleicht erzählst du einfach mal warum du das machen willst und wir schlagen dir dann ein paar Alternative Methoden vor das zu lösen. Und wo ich gerade von lösen spreche versuche dich von bestimmten C++ Paradigmen zu lösen im Einflußbereich der CLR funktioniert vieles etwas anders.

              Comment


              • #8
                naja ob etwas eine referenz ist oder nicht hat ja an sich nichts mit c++ zu tun ich finde einfach das ist in c# etwas inkonsequent gelöst worden.

                also ob ich nun eine neue instanz des strings zurückbekomme ist doch eigentlich egal. wichtig ist das der verweis auf diesen sting sich auf den neuen ändert. ich habe jetzt mal die schlüsselwörter struct gegen class getauscht und siehe da es funktioniert.

                sry die c# jungs haben irgend wie was geraucht als die sich das ausgedacht haben. entweder es wird mit referenzen gearbeitet oder nur mit kopien, diesen mischmasch finde ich mal absolut albern.

                naja aber vielen dank für eure hilfe und die vielen antworten

                mfg
                Zebes
                Zuletzt editiert von Zebes; 11.06.2007, 12:25.

                Comment


                • #9
                  Hallo Zebes,

                  nein, das ist nicht inkonsequent gelöst, sondern in der OO-Welt einfach nicht anders zu handhaben.

                  Hier mal eine Erklärung dazu:

                  Es gibt Referenztypen und Wertetypen in .NET. Je nachdem mit welchem Typ man arbeitet, je nachdem liegt das Objekt an einer anderen Stelle im Speicher.

                  Kurz gesagt Int, Double, Strings etc... und auch Strukturen sind Wertetypen, und werden auch so behandelt. Das heißt bei einer Übergabe an eine Methode über Parameter (oder als Wert in einer Liste) werden Wertetypen "byValue" übergeben.

                  Alles andere sind Referenztypen, und daher werden diese Objekte "byReference" weitergegeben.

                  Daher sind auch unterschiedliche Verhaltensweisen zu beobachten. Das ist übrigens bei Java genauso. Die Sprache braucht ein paar wenige Datentypen, mit denen sie wirklich praktisch arbeiten kann (Werte eben).


                  Ich hoffe ich konnte Klarheit verschaffen?

                  Grüße
                  _ntr_

                  Comment


                  • #10
                    also das es anders möglich ist zeigt ruby dort wird alles als referenz übergen da alles objecte sind. dort gibt es keine primitiven datentypen.

                    das problem ist einfach das ich eine ansammlung von strings habe. den wert dieser strings muss ich mir von einem server auslesen. dh ich schicke eine anfrage an den server und stecke den verweis auf den string mit einer request id in eine list oder direkt hashtable. nun habe ich aber keine chance den wert an meine eigentliche sting variable weiterzu geben da ich die variable nicht als referenz übergeben kann. also was ich will ist ja nicht den string an sich weiter zu geben sondern nur eine referenz auf die variable die auf den string verweist. also ine c gesprochen einen pointer auf den pointer.

                    spontane lösung ist einen string in eine klasse zu packen und dann mit de rklasse zu arbeiten was aber in meinen augen schwasinn ist.

                    mfg
                    Zebes

                    Comment


                    • #11
                      Hallo Zebes,

                      mal abgesehen davon, dass ich persönlich Ruby nicht in eine REIN objektorientierte Sprache einordnen würde, vielleicht hilft Dir folgender Code?

                      Code:
                      using System;
                      using System.Collections.Generic;
                      using System.Text;
                      
                      namespace TestConsole
                      {
                          class Program
                          {
                              static void Main(string[] args)
                              {
                                  Program app = new Program();
                      
                                  Console.WriteLine();
                                  Console.WriteLine("Press [Enter] to exit...");
                                  Console.ReadLine();
                              }
                      
                              public Program()
                              {
                                  // local varible
                                  string sTest = "Initial value";
                      
                                  Console.WriteLine("Current value: " + sTest);
                                  ChangeStringRef(ref sTest);
                                  Console.WriteLine("Current value: " + sTest);
                              }
                      
                              private void ChangeStringRef(ref string p_sString)
                              {
                                  p_sString = "Changed value";
                              }
                          }
                      }
                      Damit ist es problemlos möglich auch Referenzen für Wertetypen weiterzugeben ;-)

                      Grüße
                      _ntr_

                      Comment


                      • #12
                        ja bei functions aufrufen geht es generell mit dem schlüsselwort "ref" das weiß ich. aber das bieten mir wenn ich das richtig sehe die List und die Hashtable klassen nicht an oder? oder kann ich einfach belibig das schlüsselwort "ref" hinzufügen damit eine referenz verwendet wird?

                        bin zwar kein experte für ruby aber das ist eigentlich mehr objektorientiert als c# gerade weil es keine primitive kennt sondern ausschließlich objekte. es gibt zb aufrufe wenn man etwas mehrfah machen will so könnte man schreiben (ich hoffe ich vergreife mich nciht in der syntax)

                        4.times

                        das geht nur weil die "4" als objekt betrachtet wird und nicht als primitive

                        an welcher stelle woll es denn nciht objektorientiert sein?
                        mfg
                        Zebes

                        Comment


                        • #13
                          Hallo Zebes,

                          ich möchte nicht bestreiten, dass Ruby objektorientiert ist, allerdings möchte ich sagen, dass sie mehr ist wie das, und dann relativ schlecht mit rein objektorientierten Sprachen zu vergleichen ist. Zumal Ruby interpretiert wird, C# wird kompiliert (Gut Java wird auch teilweise interpretiert) -> lassen wir Ruby weg, und konzentrieren uns auf Dein Problem ;-).

                          Also jetzt habe ich Deine Kernproblematik verstanden. Ja es gibt in der Tat 3 Möglichkeiten Dein Problem zu lösen:

                          A) Du machst tatsächlich ein Referenztyp aus Deinen List-Werten, sprich also tatsächlich eine Klasse, die einen String-Wert speichert.
                          B) Du übergibst die Liste als Parameter, dann kann der Empfänger darauf arbeiten, und ändert eventuell die Referenzen der einzelnen Elemente.
                          C) Du übergibst eine Kopie an den Empfänger (was bei Wertetypen automatisch passiert) und sorgst dafür, dass eine neue Kopie zurückkommt, die Du mit dem bisherigen Element ersetzt.

                          Hier Beispielcode für die genannten 3 Möglichkeiten:
                          Code:
                          using System;
                          using System.Collections.Generic;
                          using System.Text;
                          
                          namespace TestConsole
                          {
                              class StringValue
                              {
                                  private string m_sStringValue = String.Empty;
                          
                                  public StringValue()
                                  {
                                  }
                          
                                  public StringValue(string p_sInitialValue)
                                  {
                                      m_sStringValue = p_sInitialValue;
                                  }
                          
                                  public string Value
                                  {
                                      get
                                      {
                                          return m_sStringValue;
                                      }
                          
                                      set
                                      {
                                          m_sStringValue = value;
                                      }
                                  }
                              }
                          
                              class Program
                              {
                                  static void Main(string[] args)
                                  {
                                      Program app = new Program();
                          
                                      Console.WriteLine();
                                      Console.WriteLine("Press [Enter] to exit...");
                                      Console.ReadLine();
                                  }
                          
                                  public Program()
                                  {
                                      // local varible
                                      List<string> lstStringContainer = new List<string>();
                                      List<StringValue> lstStringRefContainer = new List<StringValue>();
                          
                                      // add some values
                                      for (int i = 0; i < 10; i++)
                                      {
                                          string sValue = String.Concat("Item", i.ToString());
                          
                                          lstStringContainer.Add(sValue);
                                          lstStringRefContainer.Add(new StringValue(sValue));
                                      }
                          
                                      // make changes whereelse
                                      ChangeStringRefListItem(lstStringRefContainer[0]);                  // <- Variante A
                                      ChangeStringList(lstStringContainer);                               // <- Variante B
                                      lstStringContainer[1] = ChangeStringListItem(lstStringContainer[1]);// <- Variante C
                          
                                      Console.WriteLine("Items in value item Container:");
                                      foreach (string sItem in lstStringContainer)
                                      {
                                          Console.WriteLine(String.Concat(" - ", sItem));
                                      }
                          
                                      Console.WriteLine();
                          
                                      Console.WriteLine("Items in reference item Container:");
                                      foreach (StringValue sItem in lstStringRefContainer)
                                      {
                                          Console.WriteLine(String.Concat(" - ", sItem.Value));
                                      }
                                  }
                          
                                  private void ChangeStringList(List<string> p_lstStringContainer)
                                  {
                                      if (p_lstStringContainer.Count > 0)
                                      {
                                          p_lstStringContainer[0] = "Changed value";
                                      }
                                  }
                          
                                  private string ChangeStringListItem(string p_sStringItem)
                                  {
                                      return "Changed value";
                                  }
                          
                                  private void ChangeStringRefListItem(StringValue p_StringValue)
                                  {
                                      p_StringValue.Value = "Changed value";
                                  }
                              }
                          }
                          Mir ist keine andere Lösung bekannt. Tut mir leid, das ist wirklich etwas "umständlich".

                          Viele Grüße
                          _ntr_

                          P.S.: Kleiner Erfahrungstipp:
                          Ich habe die Erfahrung gemacht, nie Strukturen zu verwenden, ich mache immer sofort eine Klasse daraus, weil ich festgestellt habe, dass früher oder später bei einer Erweiterung eine Struktur unbrauchbar ist.

                          Comment


                          • #14
                            Ähm, du kannst die Strings in der Liste einfach immer neu zuweisen.
                            Da muss keine Wrapperklasse her.

                            Unschönheiten:
                            - foreach geht nicht (hast du ja schon festgestellt) ist aber glaube ich nicht wirklich ein Problem
                            - Performancemässig fraglich, werden ja ständig neu string Instanzen erzeugt und die alten vom GC zerstört. Wäre Performance ein Thema muss man sich halt mal mit der StringBuilder Klasse auseinander setzen.

                            Code:
                                        string a1 = " 1 ";
                                        string a2 = " 2 ";
                                        string a3 = " 3 ";
                            
                                        List<string> l = new List<string>();
                                        l.Add(a1);
                                        l.Add(a2);
                                        l.Add(a3);
                            
                                        for (int i = 0; i < l.Count; i++)
                                          l[i] = l[i].Trim();
                            @_ntr: Wertetyp<>Referenztyp ist hier nicht das Problem.

                            Strings sind immutable(readonly). Und der Grund warum das so ist wird wohl die Garbage Collection sein. In klassischen Sprachen ist man selber dafür verantwortlich das man genügend Speicher alloziert um einen String aufzunehmen. Man muss selbst dafür sorgen das man nicht über das Ende des allozierten Speicher hinaus im RAM rumschreibt. Alles kein Problem in C#.

                            Stell dir vor string wäre in der CLR nicht readonly. Du erzeugst ein 10 Zeichen lang string und kürzt in dann um 2 Zeichen. Wie soll der GC den entsprechend Speicher für diese 2 Zeichen finden um ihn freizugegeben?
                            C++Entwickler würden sowas in C++ bestimmt nie vergessen

                            Comment


                            • #15
                              Hallo Ralf Jansen,

                              nun Variante C macht genau eine Neuzuweisung, ist also genau das, was Du gepostet hast.

                              Außerdem geht es sehr wohl um Wertetypen <--> Referenztypen, denn by Design werden Wertetypen "by Value" als Parameter bei Funktionen übergeben. Referenztypen eben "by Reference".

                              Weil strings immutable sind, werden sie wie Wertetypen gehandhabt, ansonsten gäbe es hier Schwierigkeiten das immutable zu halten.

                              Strings spielen im .NET sicherlich nochmal eine Sonderrolle, wenn Du aber mein Beispiel mit Integer anstatt Strings verwendest, dann wird es sich genauso verhalten, Integer werden auch kopiert bei der Übergabe an eine Funktion.

                              Deshalb sehe ich das Problem bei Wertetyp-Referenztyp, denn mein Beispiel weiter oben zeigt, dass ich bei einzelnen Strings in C# auch die Möglichkeit habe per "ref" Schlüsselwort eine Referenz auf einen String zu übergeben. Dass bei einer Werteänderung dann eine neue Referenz erzeugt wird, und nicht im Inhalt der alten Referenz gepfuscht wird ist in .NET gekapselt, und sollte den Entwickler nicht stören...

                              Edit: Das Problem spielt erst dann eine Rolle, wenn ich die .NET Welt verlasse und z.B. COM-Methoden aufrufe, dann macht es sehr wohl einen Unterschied, ob ich die Referenz eines Strings übergebe, aber dafür gibt es dann P/Invoke und Marshalling.

                              Viele Grüße
                              _ntr_

                              Comment

                              Working...
                              X