Orginal Array wird modifiziert ohne ByRef Parameter

  • C#
  • .NET (FX) 1.0–2.0

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Orginal Array wird modifiziert ohne ByRef Parameter

    Hey,

    Also ich verwende folgenden Algo (Quicksort) um ein byte[] zu sortieren:

    C#-Quellcode

    1. /// <summary>
    2. /// Quicksort algorithm to sort the elements in an array.
    3. /// https://github.com/Sajgoniarz/Algorithms-Unlocked/blob/master/1. Sorting And Searching/Quick Sort/QuickSort.cs
    4. /// </summary>
    5. /// <param name="a">Source array</param>
    6. /// <param name="p">Start index</param>
    7. /// <param name="r">End index</param>
    8. /// <returns>Sorted array</returns>
    9. public static byte[] Sort(byte[] a, int p, int r)
    10. {
    11. if (p >= r) return a;
    12. int q = Partition(a, p, r);
    13. Sort(a, p, q - 1);
    14. Sort(a, q + 1, r);
    15. return a;
    16. }
    17. private static int Partition(byte[] a, int p, int r)
    18. {
    19. int q = p;
    20. byte temp;
    21. for (int u = p; u < r; u++)
    22. {
    23. if (a[u] <= a[r])
    24. {
    25. temp = a[q];
    26. a[q] = a[u];
    27. a[u] = temp;
    28. q++;
    29. }
    30. }
    31. temp = a[q];
    32. a[q] = a[r];
    33. a[r] = temp;
    34. return q;
    35. }


    Das geht sogar, allerdings mehr als mir eigentlich lieb ist^^
    Ich passe keinen der Parameter by reference, trotzdem wird das original input array modifiziert.
    Das soll aber nicht so sein.
    Hier ein Bild des Problems :



    Eigentlich sollte x noch unsortiert sein und nur y sollte sortiert sein.
    Wie kann ich das lösen?
    C# Developer
    Learning C++
    nur primitive Datentypen und structs werden by-val übergeben, Klassen und Arrays werden by-ref übergeben.
    Deshalb geht auch das hier:

    C#-Quellcode

    1. void xy(List<int> bla){
    2. bla.Add(1);
    3. bla = new List<int>();//ist nur im lokalen Scope und verändert nach außen hin die List nicht
    4. }

    Um die Liste zu modifizieren wird also keine Referenz benötigt, da es sich bei List<T> um eine Klasse handelt.
    bla ist bereits ein Referenztyp, d.h. alle Änderungen innerhalb von bla werden nach außen hin auch übernommen. Jedoch ist bla selbst keine Referenz, wodurch die Veränderung der Variable keinen Effekt nach außen hat.

    C#-Quellcode

    1. void xy(ref List<int> bla){
    2. bla = new List<int>();//funktioniert
    3. }


    Du willst also, das unsortierte Array behalten, dann leg selbst eine Kopie davon an.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    Rikudo schrieb:

    Eigentlich sollte x noch unsortiert sein und nur y sollte sortiert sein.
    ist falsch.
    x und y sind doch dasselbe Array - Du übergibst es der Methode, und sie gibts dir wieder zurück (was nicht sonderlich viel Sinn macht, odr? ;) ).
    x und y sind dasselbe Array - und das ist numal entweder sortiert oder nicht, aber niemals beides.

    jvbsl schrieb:

    nur primitive Datentypen und structs werden by-val übergeben, Klassen und Arrays werden by-ref übergeben.
    nein, byref/byval wird übergeben, wie es in der Zielmethode deklariert ist - unabhängig davon, obs Verweis-typen sind oder Wert-Typen.

    Also auch Verweis-Typen werden bei byVal in die Parameterliste kopiert.
    Nur die Kopie eines Verweis-Typen verweist natürlich auf dieselbe Objekt-Instanz - worauf denn sonst?

    Also nochmal das Prinzip:
    Bei WertTypen enthält die Variable den Wert, und eine Zuweisung kopiert den Wert, und dann hat man also 2 Werte.
    Bei Verweis-Typen ist der eigliche "Wert" woanders im Speicher, und die Variable enthält nur einen Verweis auf den Wert. Hier kopiert eine Zuweisung nur den Verweis - der verwiesene Wert bleibt davon unberührt, und man hat 2 Verweise - auf denselben "Wert".
    (Bei Verweis-Typen spricht man dann aber nicht mehr von Wert, sondern nennt das Objekt-Instanz)



    nochmal zum gezeigten Code:
    1. fragwürdiges Design (wieso gibt die Methode zurück, was der Aufrufer bereits hat?)
    2. keinerlei sinnvolle Kommentation. Was a, b, r sind, würde eine selbsterklärende Benamung besser erklären.
      Und was bitterlich fehlt ist halt die Kommentation, wie Quicksort denn nu eiglich funktioniert - das ist ja nicht trivial.
    Das ist ein ganz typischer Effekt der "Laber-Kommentation" - dass dabei das Wesentliche zu erläutern unterlassen wird - das, was man dem Code direkt nicht auf dem ersten Blick ablesen kann.

    Jo, und insgesamt ist das keine gute Idee, mit was selbstgebasteltem zu sortieren, denn das Framework ist mit ausgezeichneten Sortier-Algos ausgestattet (verbessertes Quicksort).

    Ach, und der Partition()-Code ist logisch fehlerhaft, was die Laufzeit wesentlich verschlechtert - also das ist gar kein Quicksort.

    Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von „ErfinderDesRades“ ()

    @ErfinderDesRades Nein Referenztypen werden nicht kopiert, wie der Name schon sagt werden diese Typen durch ihre Referenz angegeben und somit wird ohne Angabe von ref lediglich die Referenz zu diesem Objekt kopiert und nicht das Objekt selbst.
    Außerdem wird der Hauptunterschied wohl sein, dass Referenztypen in .Net auf dem Heap sind und die Wertetypen auf dem Stack um natürlich einen Geschwindigkeitsvorteil zu erlangen.

    Bin mir jetzt nicht sicher ob es sich hier nur um ein Verständigungsproblem handelt oder nicht.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    jvbsl schrieb:

    Bin mir jetzt nicht sicher ob es sich hier nur um ein Verständigungsproblem handelt oder nicht.
    Es ist ein wesentlicher Unterschied - nicht nur ein Verständigungs-Problem.

    Mir ist der Unterschied, ob Heap oder Stack, vollkommen schnuppe, für mich ist entscheidend, dass eine Verweistyp-Variable auf einen Wert verweist, während eine Wert-Typ-Variable den Wert selbst enthält.

    Kopiert werden beide bei einer Zuweisung, aber wie gesagt: Beim Verweistyp verweist die Kopie auf denselben Wert, und das ist die "Überaschung", dass wenn sowohl x als auch y auf dasselbe Array verweisen, dass beide dann auf ein sortiertes Array verweisen, nachdem das Array sortiert wurde.

    Wäre Array ein Wert-Typ, so würden x und y je ein Array komplett enthalten, und das eine wäre sortiert, das andere nicht.

    Naja, vlt. doch ein Verständigungproblem: Also einen Typen kann man ja garnet zuweisen oder kopieren, man kann nur Variablen eines Typs zuweisen oder kopieren - noch genauer: Man kann den Wert der einen Variable an eine zweite Variable zuweisen - dabei wird er kopiert. Der Wert nun einer Verweistyp-Variablen ist aber nur ein Verweis.
    Ebenso wird bei ByVal kopiert - das ist einfach eine Zuweisung an die Parameter-Variable.

    Was anneres ist ByRef. ByRef stellt man sich am besten vor als Zurück-Zuweisung der Variable an den Aufrufer - quasi als zusätzlichen Return-Value.
    naja, ob das nu was erklärt oder eher Verwirrung schafft?

    Wie gesagt: Fürs proggen ist imo irrelevant, ob Stack oder Heap oder sonstwas, und was das im einzelnen nu eiglich ist.
    Relevant ist, dass ein Verweistyp verweist, und ein WertTyp den Wert selbst enthält.

    Vlt. noch relevant ist die Folgerung daraus, dass eine Verweis-Variable immer 32bit belegt - halt den Zeiger, wo der Wert ist.
    Eine Wert-Variable belegt unterschiedlich viel Speicher - je nach wie umfangreich die Structure ist.
    Also um eins noch mal Klarzustellen.
    Ein Referenztyp ist immer auch ein Wertetyp, jedoch ist es ein Wertetyp einer Referenz.
    Deshalb werden sogesehen bei Übergaben und Zuweisungen als Wertetyp behandelt.
    Bei einem direkten Wertetyp ändere ich somit den Wert.
    Bei einem indirekten(aka Referenztyp) setze ich auch den Wert, jedoch den Wert der Referenz, die Instanz(der Referenz), die vorher der Variable zugewiesen wurde bleibt erst mal vorhanden(wird ggf später vom GC aufgeräumt), jedoch verweist nun die Variable auf ein anderes Objekt.

    Man kann es auch anhand von C++ erklären:
    Alle Werttypen werden deklariert wie üblich.
    Die Referenztypen sind doch ab nun immer Pointer.

    C-Quellcode

    1. void xy(List<int> bla){//in C# nicht möglich(eigene struct List<T> evtl)
    2. bla.Add(1);//nur im lokalen Scope
    3. bla = new List<int>();//kann nicht funktionieren, da new auf dem Heap(Referenztyp) ist
    4. bla = List<int>();//wird nicht nach außen gegeben, nur im lokalen Scope
    5. }

    C-Quellcode

    1. void xy(List<int>* bla){//in C# void xy(List<int> bla) - da List<T> ein Referenztyp ist)
    2. bla->Add(1);//Dereferenzierung wird natürlich von C# direkt übernommen
    3. bla = new List<int>();//wird nicht nach außen gegeben, nur im lokalen Scope
    4. }

    C-Quellcode

    1. void xy(List<int>** bla){//in C# void xy(ref List<int> bla) - da List<T> ein Referenztyp ist und wir auf dießen eine Referenz haben)
    2. (*bla)->Add(1);//Dereferenzierung wird natürlich von C# direkt übernommen
    3. *bla = new List<int>();//wird nach außen gegeben, im globalen Scope
    4. }


    Edit: nur weil für deine Programmierungen Heap und Stack egal sind, heißt es nicht, dass dies Allgemein der Fall ist. Heap und Stack sind sehr wichtige Unterscheidungen, sobald es um Performanz geht.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    @jvbsl
    Ich glaube, du gehst da viel zu sehr als C(++)-Programmierer dran.

    Referenz-Typen heißen Referenz-Typen, weil man immer nur eine Referenz auf ein Objekt hat. Eine lokale Variable oder ein Feld vom typ Form beinhaltet immer eine Referenz auf ein Form-Objekt.
    Werte-Typen dagegen heißen Werte-Typen, weil man immer den Wert selbst hat. Eine lokale Variable oder ein Feld vom Typ Point beinhaltet immer den Point selbst, keine Referenz auf ein Point-Objekt.

    Die Unterscheidung zwischen Call-By-Reference und Call-By-Value ist in .NET ganz klar:
    Ist ein Parameter ein Werte- oder Referenz-Typ T, dann ist es Call-By-Value.
    Ist ein Parameter eine Referenz auf einen Werte- oder Referenz-Typ T (die MSIL-Schreibweise ist T&), dann ist es Call-By-Reference. In VB schreibt man beim Parameter ByRef davor, in C# ref.

    Sehen wir uns diese vier Methoden an:

    C#-Quellcode

    1. private static void StructByVal(Point a) {}
    2. private static void StructByRef(ref Point a) {}
    3. private static void ClassByVal(Form b) {}
    4. private static void ClassByRef(ref Form b) {}

    VB.NET-Quellcode

    1. Private Shared Sub StructByVal(ByVal a As Point)
    2. End Sub
    3. Private Shared Sub StructByRef(ByRef a As Point)
    4. End Sub
    5. Private Shared Sub ClassByVal(ByVal b As Form)
    6. End Sub
    7. Private Shared Sub ClassByRef(ByRef b As Form)
    8. End Sub

    CIL-Quellcode

    1. .method private static void StructByVal (valuetype Point a) cil managed {}
    2. .method private static void StructByRef (valuetype Point& a) cil managed {}
    3. .method private static void ClassByVal (class Form b) cil managed {}
    4. .method private static void ClassByRef (class Form& b) cil managed {}

    Die Namen sollten ziemlich klar zeigen, worum es geht.
    Die Aufrufe sehen so aus:

    C#-Quellcode

    1. Point a = default(Point);
    2. Form b = null;
    3. StructByVal(a);
    4. StructByRef(ref a);
    5. ClassByVal(b);
    6. ClassByRef(ref b);

    VB.NET-Quellcode

    1. Dim a As Point = Nothing
    2. Dim b As Form = Nothing
    3. StructByVal(a)
    4. StructByRef(a)
    5. ClassByVal(b)
    6. ClassByRef(b)

    CIL-Quellcode

    1. .locals init (
    2. [0] valuetype [System.Drawing]System.Drawing.Point a,
    3. [1] class [System.Windows.Forms]System.Windows.Forms.Form b
    4. )
    5. // a = Nothing
    6. IL_0000: ldloca.s a
    7. IL_0002: initobj [System.Drawing]System.Drawing.Point
    8. // b = Nothing
    9. IL_0008: ldnull
    10. IL_0009: stloc.1
    11. // StructByVal(a)
    12. IL_000a: ldloc.0 // Inhalt von a kopieren (Point)
    13. IL_000b: call void WindowsApplication1.Form_Main::StructByVal(valuetype [System.Drawing]System.Drawing.Point)
    14. // StructByRef(a)
    15. IL_0010: ldloca.s a // Adresse von a (Referenz auf Point)
    16. IL_0012: call void WindowsApplication1.Form_Main::StructByRef(valuetype [System.Drawing]System.Drawing.Point&)
    17. // ClassByVal(b)
    18. IL_0017: ldloc.1 // Inhalt von b kopieren (Referenz auf Form)
    19. IL_0018: call void WindowsApplication1.Form_Main::ClassByVal(class [System.Windows.Forms]System.Windows.Forms.Form)
    20. // ClassByRef(b)
    21. IL_001d: ldloca.s b // Adresse von b (Referenz auf Referenz auf Form)
    22. IL_001f: call void WindowsApplication1.Form_Main::ClassByRef(class [System.Windows.Forms]System.Windows.Forms.Form&)

    Die Aufrufe unterscheiden sich in keiner Weise zwischen Werte- und Referenztypen. Bei Call-By-Value wird das, was als Argument da steht, kopiert. Bei Call-By-Reference wird eine Referenz auf das, was als Argument da steht, übergeben. Egal, ob es ein Werte- oder Referen-Typ ist.

    Und wegen Stack und Heap: Darüber sollte man sich in .NET wirklich keine großen Gedanken machen. Siehe auch ericlippert.com/2012/01/16/wha…stic-of-a-local-variable/

    Ich kann mich an einen ehemaligen Lehrer erinnern, der meinte, in Java gäb's Call-By-Reference, weil wegen Referenz-Typen. Das ist aber falsch.
    Das Thema scheint oft für Verwirrung zu sorgen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Mit dem C++ Code ging es mir darum um zu zeigen was Intern nachher passiert, da ist es dann vollkommen irrelevant, dass es im IL Code nicht direkt erkennbar ist(was es mMn ja auch ist).
    (Meistens kann man an die Dinge auch gar nicht stark genug als Cpp Dev angehen, da man dann versteht was intern passiert)

    Und deine Beschreibung der Referenztypen passt auch ziemlich exakt analog zu den Cpp Beispielen.

    Zum Thema Stack vs Heap: Alles was es mir sagt ist, dass es nicht immer ganz Klar ist was C# wann und wo macht, dennoch gibt es einen rießigen Unterschied zwischen Referenz und Wertetypen was Performanz anbelangt, egal was dann C# im Endeffekt damit macht.

    Nunja Java hat kein Sprachfeature für Call-by-ref, jedoch lässt sich das generisch sehr einfach nachbilden, was dann tatsächlich auch nichts anderes macht als ref in C#, mit dem Unterschied, dass wir in Java dann mehr overhead haben werden, da es eben kein direktes Sprachfeature ist.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Hi
    im Prinzip verhält es sich so: Die Parameterliste besteht aus Werten. Bei einem Referenztyp ist dieser Wert eine Referenz, die die Instanz des Typs angibt, bei Wertetypen ist sie der Wert (d.h. die Instanz) selbst (=by val).
    Das liegt daran, wie Daten hier abgelegt werden. Bei Wertetypen (=Strukturen) handelt es sich um Werte, d.h. diese liegen direkt auf dem Stack, bei Referenztypen liegt lediglich die Referenz (wie gesagt, als Wert) auf dem Stack.
    Bei by-reference-Parametern handelt es sich um Referenzen, die nicht in den Heap zeigen, wo die Daten von Referenztyp-Instanzen abgelegt werden, sondern auf den Stack. D.h. diese Parameter enthalten ebenfalls Werte, nämlich wieder die Referenzen.
    Referenzen kann man sich quasi als native Zeiger vorstellen. Effektiv kann es sich dank der Zwischenschicht der JITC aber auch um ein anderes System handeln. Außerdem habe ich bisher nicht überprüft, ob hier ein Token verwendet wird, das das verwaltete System vereinfacht, oder es wirklich ein Zeiger ist.
    Für Klassen gilt also bei by-ref:

    VB.NET-Quellcode

    1. Public Sub X()
    2. Dim pf As Form
    3. DoX(pf)
    4. End Sub
    5. Private Shared Sub DoX(ByRef p As Form)
    6. p = new Form()
    7. End Sub

    In DoX: p ist Referenz (Wert), der auf pf in X zeigt, pf ist ein Wert, der auf die Daten der Instanz in pf im Heap zeigt. "x zeigt auf y" sei hier "x verweist auf y".

    Wird also der Wert einer Variablen, eines Feldes oder eines Parameters geändert, so ändert sich lediglich diese Variable, dieses Feld oder dieser Parameter. Wenn hingegen mehrere Werte eine Referenz darstellen und ein Wert innerhalb des referenzierten Datums geändert wird, so ändert sich das Datum selbstverständlich für alle Referenzen. Das ist auch dann das Prinzip von Klassen und by-ref.

    Viele Grüße
    ~blaze~