Autor Beitrag
bamba
Hält's aus hier
Beiträge: 13



BeitragVerfasst: Mo 25.08.14 14:01 
Hallo,

ich frage mich, ob eine Ref-Deklaration im folgenden Fall sinnvoll ist, um nicht eine Kopie des Wertes anzulegen:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
class Program {

        int zahl = 0;

        void eineMethode() {
            string wert = "hh";
            andere Methode(ref wert);
        }

        void andereMethode(ref string argument1) {
           
           if (argument == "hh"){
              zahl++;
           }else{
              zahl--;
           }           

        }
    }


Macht es Sinn das Argument als "ref" zu deklarieren, und spart man sich somit auch eine Kopie?
Danke
PantherX
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 43
Erhaltene Danke: 7

Win XP, Win 7
Delphi 7, VS2010 C#
BeitragVerfasst: Mo 25.08.14 14:14 
Hallo,

also in der Regel macht Ref dann Sinn wenn aus einer Funktion mehrere Ergebnisse zurückgeliefert werden müssen.
Denn mittels Ref könnten dann mehreren Variablen die Ergebnisse aus der Funktion zugewiesen werden.

In dem Fall deines Beispielcodes würde es für mich jetzt keinen Sinn machen, da der Wert nicht verändert wird.

Besten Gruß
Patrick

_________________
Grafische Oberflächen sind wie U-Boote. Kaum macht man ein Fenster auf, fangen die Probleme an!
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 25.08.14 14:47 
string ist bereits ein Referenztyp. Da wird also eh nix kopiert. Schon gar nicht der string.

Bei jedem üblichen Wertetyp würde ich ref auch schon als sinnfrei empfinden. Da kann man bei komplexen structs mal drüber nachdenken (aus Spass nicht aus irgendeiner Relevanz) ob das Einfluss hätte. Denke eher nicht. Wenn das ein Methode ist die millionfach aufgerufen wird würde man eher versuchen die zu inlinen.
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mo 25.08.14 18:59 
Das stimmt so nicht ganz, String ist kein Referenztyp.
String ist zwar eine Klasse, wird aber als Wertetyp behandelt. Habe ich gerade eben noch getestet ^^
Eigentlich ist das sogar ein Mix aus Beidem, aber dazu unten mehr.
[Edit: Ich hatte mit dem Obigen Unrecht, mehr dazu einen Beitrag weiter unten.]

Ich habe bisher noch nie eine sinnvolle Verwendung von ref bei Werttypen gesehen, bei Referenztypen allerdings schon.
Und zwar benutzen wir für ViewModels eine ViewModelBase-Klasse, die SetValue-Methoden bereitstellt.
Diese haben unter anderem die Aufgabe, INotifyPropertyChanged zu implementieren. So kann dann in der Property SetValue aufgerufen werden und der Wert wird gesetzt.
Das hat aber den Nachteil, dass sich die Variable nicht ändern lässt, da sie nicht existiert, sondern in einem Dictionary in der Basisklasse liegt.
Daher gibt es eine Überladung mit einem ref-Parameter, wo eben diese Variable angegeben wird. Die Variable wird dann in SetValue geprüft und erhält eine neue Referenz.
Ohne ref würde das nur in der Methode eine Auswirkung haben, mit ref auch außerhalb.

Im Falle von mehreren Rückgabewerten würde ich mich erst einmal fragen: Warum brauche ich mehrere Rückgabewerte?
In den meisten Fällen lässt sich die Methode auf splitten und dann brauche ich das nicht mehr.
Es gibt aber selten auch sinnvolle Situationen, dann verwende ich nicht ref, sondern out. Der Unterschied ist der, dass die Referenz für den out-Parameter nicht instanziiert sein muss.
So habe ich z.B. eine Methode, die eine Lambda-Expression analysiert. Ich verwende das, um Informationen über eine Property der SetValue-Methode von oben mit zu geben und keine reinen "Magic-Strings" schreiben zu müssen. Daher übergebe ich dann einer Methode folgende Lambda-Expression: () => this.MyProperty
Aus dieser Expression kann ich jetzt enorm viele Informationen erhalten, unter anderem die PropertyInfo und auch das Objekt, von dem die Property aufgerufen wurde. Letzteres habe ich noch nicht gebraucht, allerdings habe ich mir die Methode testweise mal geschrieben und da finde ich das ganz sinnvoll.


Warum ich jetzt so viel geschrieben habe:
Das sind Beispiele um zu zeigen, dass ref und out zumindest bei mir nur in eher skurrilen und speziellen Fällen sinnvoll waren. Das sind wirklich die einzigen Beispiele, die mir einfallen, alle anderen möglichen Beispiele in der Vergangenheit haben sich am Ende doch noch als Architektur-Fehler heraus gestellt und ließen sich sauberer lösen.
Ich will damit sagen, dass das Feature ref und out zwar ganz praktisch ist und sich damit viel spielen lässt, aber man sollte sich überlegen, ob man es wirklich braucht.
Sonst könnte es im Extremfall passieren, dass eine Methode eine bunt gemischte Anzahl von ref- und out-Parametern hat und nicht wirklich klar ist, welche Variable wann und wie sich verändert.
In der Regel gilt nämlich, dass eine Methode nur eine Aufgabe hat und Parameter, die einer Methode übergeben werden, danach genauso wieder raus kommen, wie sie waren. Halbe Ausnahmen sind Referenztypen, deren Eigenschaften dürfen sich ändern, allerdings die Referenz selber nicht.
Dieses Vorgehen kommt daher, dass ein Entwickler immer davon ausgehen muss, das beim Aufruf der Methode, die er gerade schreibt, nicht zwingend die Funktionsweise und das Verhalten der Methode bekannt ist. Entweder, weil mehrere Entwickler am selben Projekt arbeiten, weil der Entwickler Monate später diese Methode aufruft und sich nicht mehr an den Inhalt erinnert, oder weil die Methode in einer DLL von Drittanbietern liegt.
Ziel ist es daher immer, keine Nebeneffekte zu produzieren, sondern nur und wirklich nur diese eine einzige Aufgabe der Methode zu erfüllen.


Zu der konkreten Frage:
Strings stellen eine Ausnahme dar, sie sind ein Mix aus Referenz- und Wertetyp.
Wird ein String erstellt, landet er im Speicher. Bei jedem Methodenaufruf wird nur die Referenz weiter gegeben, es wird keine Kopie erstellt.
Wird nun aber in der Methode der String geändert, DANN wird im Speicher ein neuer String angelegt. Du kannst also 5000 String-Variablen mit exakt dem gleichen Inhalt haben, der existiert im Speicher aber nur ein einziges mal. Wenn du 5000 Variablen mit unterschiedlichem Inhalt hast, hast du auch 5000 unterschiedliche Strings im Speicher.
Aus dem Grund sollte man auch nicht fleißig selber Strings zusammen bauen, sondern StringBuilder benutzen, da die das umgeht. Wenn z.B. sehr viele Kleinteile Stück für Stück aus zusammen gesetzt werden, existiert am Ende jeder Zwischenschritt im Speicher, was bei sehr vielen und komplizierten String-Operationen auch mal zu Performance-Problemen führen kann. Zum einen, weil immer neuer Speicher frei geräumt werden muss und zum anderen, weil sich da langsam aber sicher tausende Strings ansammeln.

Ich muss zu meinem letzten Absatz aber noch sagen:
Ich bin mir da nicht ganz 100% sicher und testen kann ich es auch nicht (zumindest weiß ich nicht wie), daher würde ich mich noch über Bestätigungen oder Korrekturen von Anderen freuen.
Die Absätze weiter oben sind aber mit bestem Wissen und Gewissen getestet und die Beispiele sind nach meiner Auffassung auch gutes Vorgehen.


PS:
Noch als Tipp, mach dir nicht über Performance in so kleinen Rahmen Gedanken.
Aus Interesse ist das ganz gut, dann versteht man das Verhalten der Features von C#, aber wenn du wirklich an jeder winzigen Ecke nach Optimierungsmöglichkeiten suchst, dann verbringst du am Ende mehr Zeit mit Optimieren, als mit Entwickeln und kommst nicht voran.


Zuletzt bearbeitet von Palladin007 am Mo 25.08.14 21:00, insgesamt 2-mal bearbeitet
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 25.08.14 20:30 
Zitat:
Das stimmt so nicht ganz, String ist kein Referenztyp.

Du möchtest also Microsoft der Lüge bezichtigen ;)
Vielleicht klären wir gerade mal was der jeweilig andere unter Werte- bzw. Referenztyp in .Net versteht.

Wertetyp - struct, beim zuweisen wird kopiert.
Referenztyp - class, beim zuweisen wird referenziert.

Im konkreten Fall des strings liegt da noch das immutable Konzept drüber das ein string nicht änderbar sondern nur in einen neuen string transformierbar ist. Das ändert aber nix daran das string ein Referenztyp ist und nie kopiert wird. Das Interning mit dem deduplizieren von strings das du beschreibst wäre ja auch ziemlich sinnfrei wenn man einen string kopieren könnte ;)

Für diesen Beitrag haben gedankt: C#
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mo 25.08.14 20:38 
Bei Microsoft steht, dass object ein Referenztyp ist. Aber bei OOP basiert doch alles auf object, oder? Dann müssten ja eigentlich alle Typen auch Referenztypen sein ?!? :gruebel:

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mo 25.08.14 20:41 
Hm, okay, so betrachtet stimmt das mit dem Refernztyp.

Ich habe meine Behauptung daran fest gemacht, dass zwar eine Referenz der Methode übergeben wird, aber diese nicht rückwirkend bleibt, wie es bei "normalen" Referenztypen ist.
Mit anderen Worten: Mir war der Unterschied zwischen Referenztyp und Verweistyp nicht bekannt.
Wieder was gelernt, danke ^^


@C#:

Alle Wertetypen erben von ValueType, was seinerseits wieder von object erbt, aber einige Methode überschreibt.
Da liegt der Unterschied. Ich weiß nicht, ob alleine das Überschreiben das Verhalten von Objekten ändern kann, aber ich schau mal, ob ich dazu was im Source finde, das interessiert mich jetzt auch :D


PS:
Ok, im Source steht dazu nix, da steht nur eine ziemlich komplexe Implementierung von Equals.
Das ist wahrscheinlich wirklich ein Bruch mit der Objektorientierung, aber das sind Wertetypen ja allgemein. Schließlich ist C# multiparadigmatisch und geht nicht überall konform mit der OOP.
Notwendig ist das aber irgendwie schon, schließlich bietet sich hier eine allgemeine Basisklasse für Alles (wie Object es ja ist) an und wenn es die nicht gäbe, würden so viele Dinge nicht mehr funktionieren. Für Beides je eine eigene Basisklasse ist daher unpraktisch, da es dann zu viel duplizierten Code gäbe, der bei Beidem gleich ist.


Zuletzt bearbeitet von Palladin007 am Mo 25.08.14 20:58, insgesamt 2-mal bearbeitet
jfheins
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 918
Erhaltene Danke: 158

Win 10
VS 2013, VS2015
BeitragVerfasst: Mo 25.08.14 20:51 
Du kannst zumindest jeden Typen in eine object-Variable stopfen. Das nennst sich Boxing.
Aber es gibt da auch gewisse Grenzen, zum Beispiel dieses Testprogramm:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
using System;

public class Test
{
  public static void Main()
  {
    Teststruct x;
    x.a = 5;
    object y = x;
    Tester(y);
    Console.WriteLine(y);
  }
  public static void Tester(object b)
  {
    ((Teststruct)b).a = 3;
  }
}

public struct Teststruct
{
  public int a;
}

gibt einen Compilerfehler aus: "error CS0445: Cannot modify the result of an unboxing conversion"

Man kommt eigentlich eher selten in die Verlegenheit, dass der Unterschied zwischen Wert- und Referenztyp wichtig wird. Für weitere Lektüre kann ich empfehlen: blogs.msdn.com/b/eri...out-value-types.aspx
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 25.08.14 21:05 
Valuetype ist übrigens eine Klasse und damit ein Referenztyp man muss nicht noch object bemühen um über das Paradox zu stolpern ;)
Da man Valuetype weder instanziieren noch davon ableiten darf sondern man das struct Schlüsselwort bemühen muss um die Magie wirken zu lassen gehen wir einfach mal davon aus das diese Magie aus Referenztypen Wertetypen machen kann.

Zitat:
Für weitere Lektüre kann ich empfehlen: blogs.msdn.com/b/eri...out-value-types.aspx

:zustimm: War ein trauriger Tag als er bei Microsoft aufgehört hat. Hat immer tolle Einsichten geliefert. Trollt sich Gott sei Dank aber noch regelmäßig bei Stackoverflow rum.


Edit:
Zitat:
Mir war der Unterschied zwischen Referenztyp und Verweistyp nicht bekannt.


Sicher dass du das meinst? :twisted:


Zuletzt bearbeitet von Ralf Jansen am Mo 25.08.14 21:11, insgesamt 1-mal bearbeitet
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mo 25.08.14 21:11 
Meinst du die Blog-EInträge von Eric Lippert?
Der Link ist ein bisschen zerfetzt, daher muss ich nachfragen.

Aber was sich bei Google alleine mit dem Namen schon finden lässt, lässt mein Herz höher schlagen :D
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 25.08.14 21:15 
Zitat:
Der Link ist ein bisschen zerfetzt, daher muss ich nachfragen.


Zitieren von Links klappt hier nicht so wirklich. Aber einfach auf das ~original~ im Beitrag drüber klicken ;)

Ansonsten da er bei Microsoft aufgehört hat am besten nicht mehr bei blogs.msdn.com schauen sondern direkt auf seiner Homepage ericlippert.com/

Für diesen Beitrag haben gedankt: Palladin007
Christian S.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Mo 25.08.14 21:26 
user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Zitieren von Links klappt hier nicht so wirklich.
Die Zitatfunktion macht das eigentlich ziemlich gut und baut auch noch einen Quote-Tag drum herum, der auf den zitierten Beitrag verweist ;-)

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 25.08.14 21:30 
Zitat:
Die Zitatfunktion macht das eigentlich ziemlich gut und baut auch noch einen Quote-Tag drum herum, der auf den zitierten Beitrag verweist ;-)


Dann ein Mea Culpa :flehan: Ich weiß nicht immer was meine Hände tun aber vermutlich hab ich einfach den Text kopiert und die Tags drumrum geschrieben.
bamba Threadstarter
Hält's aus hier
Beiträge: 13



BeitragVerfasst: Do 28.08.14 22:08 
Vielen Dank für die zahlreiche Antworten.
Mir ging dabei eigentlich um den Speicherverbrauch.

Wenn ich mit "ref" einen Wert übergebe, den Wert innerhalb der Funktion ändere, dann arbeite ich ja nicht mit der Kopie, sondern mit dem Original. Dann denke ich, dass wenn ich eine Kopie habe (also kein "ref" vor dem Argument), dann habe ich eine zweite Variable des jeweiligen Typs erstellt, die kurz auf dem Stack lebt.
Übergebe ich dagegen den Wert dem Argumenten mit dem "ref" davor, erstelle ich ja keine Kopie und belege somit nicht erneut Platz auf dem Stack ==> weniger Platzverbrauch auf dem Stack. Ist das korrekt?
(Es geht nicht um den Anwendungsfall, manchmal will man ja nicht, dass die Änderung auch in der Aufruf-Methode statt findet)
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Do 28.08.14 22:38 
Ein String existiert in einem internen Pool für die Anwendung bis die Anwendung beendet wird.
Sie wird dort bereit gehalten um bei Verwendung an anderer Stelle den selben String erneut zu verwenden ohne extra Speicherplatz zu benötigen.
Erstellst du einen neuen String, wird dieser neue String dort ebenfalls abgelegt, egal ob du nun ref benutzt, oder nicht.

Ich könnte mir sogar vorstellen, dass das minimal langsamer ist, irgendwas tut ref ja auch - ich weiß nur nicht wie und ob das extra Zeit braucht.
Am effektivsten sollte es sein, wenn du den string als globale Klassen-Variable hast. So sparst du dir den Parameter, der Zeit braucht.




Edit:

Ok, du hast recht, habs gerade mal getestet.
Manchmal ist es mit ref schneller, meistens aber andersherum.

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
static void Main(string[] args)
{
    for (int i = 0; i < 100; i++)
        Test();

    Console.ReadKey();
}

static void Test()
{
    var numberOfRuns = 10000000;

    var timeWithRef = TestRunWithRef(numberOfRuns);
    var timeWithoutRef = TestRunWithoutRef(numberOfRuns);

    Console.WriteLine("With ref:\t" + timeWithRef);
    Console.WriteLine("Without ref:\t" + timeWithoutRef);
    Console.WriteLine("Difference:\t" + (timeWithRef - timeWithoutRef));
    Console.WriteLine();
}

static long TestRunWithRef(int numberOfRuns)
{
    var watch = new Stopwatch();
    var text = "abc";

    watch.Start();
    for (int i = 0; i < numberOfRuns; i++)
        ChangeStringWithRef(ref text);
    watch.Stop();

    return watch.ElapsedMilliseconds;
}
static long TestRunWithoutRef(int numberOfRuns)
{
    var watch = new Stopwatch();
    var text = "abc";

    watch.Start();
    for (int i = 0; i < numberOfRuns; i++)
        text = ChangeStringWithoutRef(text);
    watch.Stop();

    return watch.ElapsedMilliseconds;
}

static void ChangeStringWithRef(ref string s)
{
    if (s == "123")
        s = "abc";
    else
        s = "123";
}
static string ChangeStringWithoutRef(string s)
{
    if (s == "123")
        return "abc";
    else
        return "123";
}


Allerdings beläuft sich der Unterschied erst so aber 900000 DUrchläufen auf mehr als eine Millisekunde, von daher ist das absolut zu vernachlässigen.
Erst ab einer Milliarde Durchläufe macht sich das bemerkbar, der Unterschied liegt bei run einer Sekunde +- 200 Millisekunden.
Aber auch da ist das zu verkraften, denn wenn tatsächlich Daten in den Mängen bearbeitet werden sollen, dann kann das auch mal einige Stunden laufen - Je nach Art der Bearbeitung natürlich.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Do 28.08.14 23:34 
Zitat:
Übergebe ich dagegen den Wert dem Argumenten mit dem "ref" davor, erstelle ich ja keine Kopie und belege somit nicht erneut Platz auf dem Stack


Es ist kein Wert sondern bereits eine Referenz (string ist ein Referenztyp). Per ref sorgst du nur dafür das die Referenz auf dem Stack steht und keine Kopie der Referenz. Der Platzbedarf der Referenz egal ob Original oder Kopie ist der gleiche.

@Paladin: In einem Testfall hast du auch den Returnparameter auf dem Stack und nicht nur die die Referenz auf den string. Du testest jetzt etwas funktional gleiches aber natürlich nicht nur den Unterschied durch das ref.
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Fr 29.08.14 00:14 
Stimmt, wenn ich bei der Methode mit ref noch den string zurück gebe, aber nicht verwende, ist sie langsamer.

Ich habe mal 10000000 bis 40000000 in 200000er Schritten hoch gezählt und jedes mal die Zahl als ANzahl für die Durchläufe genommen und das Messergebnis mit geschrieben.
Wenn ich dann alles zusammen rechne, bekomme ich folgende Differenz:

0.0000014367668187220120663732 Millisekunden

So viel schneller ist es, kein ref zu verwenden.

Ich finde ja, dass das komplett zu vernachlässigen ist :D
Wenn dann tatsächlich mal jemand 40 Millionen Strings hat, ist das auch in 0.861 Sekunden abgetan.
Sogar 100 Millionen Strings rattern da in nur 21.058 Sekunden durch.