C# Auf Variable in FOR schleife von außerhalb zugreifen und ausgeben

Synytrix

Cadet 1st Year
Registriert
Juni 2015
Beiträge
14
Guten Morgen, guten Mittag oder auch guten Abend wann auch imemr die liebe Community das liest.

Ich fange gerade damit an C# zu lernen. Nun wollte ich ein Programm erstellen das nach einer Zahl fragt und daraufhin, je nachdem welche Zahl man angegeben hat, x mal nach einem Namen und einer Adresse fargt (Hänge schon am Namen fest deswegen nciht wundern das Adresse im Code fehlt.)

Nun gut die Schleife funktioniert. Er fargt x mal nach einem Namen und gibt den angegebenen Namen direkt mit einer Indexnummer aus.

Nun soll man sich nachdem die Schleife abgelaufen ist für eine der Namen entscheiden (mithilfe der Indexnummern). Daraufhin möchte ich die Variable aus der FOR Schleife die gewält wurde ausgeben. Da er mich nicht x mal nach der Nummer fragen soll sondern am Ende lediglich eine gewählt werden soll ist das was ich geschrieben habe der für mich als Neuling einzige logische Weg. Mir ist klar das auf eine Variable innerhalb einer FOR Schleife nicht von außerhalb zugegriffen werden kann deswegen frage ich hier nach einer guten Lösung. (Google Ergebnisse schlagen mir nur vor z.B. die Variable vor der Schleife zu definieren aber dann müsste man nur einen Namen angeben und nicht x Namen oder?)

Code:
class Program
{
    static void Main(string[] args)
    {   
        //Es wird nach einer Wunsch Zahl gefragt.
        Console.Out.WriteLine("Wie viele Adressen wollen Sie angeben?");
        int i = int.Parse(Console.In.ReadLine());

        //Nun wird man aufgefordert die gewählte Anzahl an Namen & Adressen anzugeben.
        Console.Out.WriteLine("Bitte geben Sie " + i + " Name(n) und die dazugehörigen Adresse(n) an.");
        for ( int a = 0; a < i; a += 1)
        {
                //Die Namen sollen angegeben und mit einer Index Nummer ausgegeben werden.
                Console.Out.WriteLine("Name: ");
                string[] names = new string[i];
                names[a] = Console.In.ReadLine();
                int c = a + 1;
                Console.Out.WriteLine("[" + c + "]" + names[a]);
        }

        //Man soll eine Indexnummer wählen. Daraufhin soll der Inhalt der gewählten Nummer ausgegeben werden.
        Console.Out.WriteLine("Wählen Sie bitte eine Nummer.");
        int w = int.Parse(Console.In.ReadLine());
        Console.Out.WriteLine(names[w]); //Liegt außerhalb der Schleife und kannd eswegen nicht auf "names[w]" zugreifen. Lösung?

        Console.In.Read();
    }
}

Ach und bitte helft mir nur bei dem beschriebenen Problem, sollten euch weitere Fehler auffallen bitte nicht verraten! Mein Motto ist Learning by Doing und ich will weitere Fehler gerne selber finden,erfahren und beheben. :D

Danke!

Mit freundlichen Grüßen,
David
 
Zuletzt bearbeitet:
Hi,

Mir ist klar das auf eine Variable innerhalb einer FOR Schleife nicht von außerhalb zugegriffen werden kann

exakt so ist es

Google Ergebnisse schlagen mir nur vor z.B. die Variable vor der Schleife zu definieren

genau das musst du tun

aber dann müsste man nur einen Namen angeben und nicht x Namen oder?

den Satz verstehe ich nicht. Erstelle deine Arrays oder wo auch immer du die Namen speicherst außerhalb und fertig. Ich sehe gerade nicht, wo das Problem an der Stelle ist, vielleicht übersehe ich etwas.

VG,
Mad
 
Da Du Anfänger bist sehe ich da nochmals darüber hinweg X)
string[] names = new string;
...
int c = a + 1;

Variablen NIE in einer Schleife deklarieren wenn nicht zwingend nötig, das macht Dir zum einen das Debuggen schwer weil die
Variablen ständig überschrieben werden und zum anderen wird bei großen Schleifen Dein Speicher irgendwann voll und vor allem ist es langsam wie die Hölle.
Deklariere Deine Variable Names einfach vor der Schleife dann kannst Du auch darauf zugreifen.
 
Du musst die Zeile

Code:
string[] names = new string[i];

nur nach außen, also direkt vor die for-Schleife verschieben.
 
Als nächster Schritt würde ich evtl auch das Array durch ne Liste ersetzten. Ist flexibler als ein Array.
 
Du hast das Problem doch schon entdeckt. Das Stichwort heißt "Scope". Da du
Code:
string[] names
in der Schleife deklarierst, ist es nur in der Schleife gültig. Setzt du diese Deklaration aber VOR die schleife und rufst dann in der schleife nur noch die variable auf
Code:
names = ...
so bleibt sowohl die variable als auch der inhalt nach der schleife erhalten.

Minimalbeispiele (deinen code passe ich nicht an, du willst ja was lernen):

Code:
for(int i = 1; i < 9; i++){
  string[] names = new string[i]; //hier werden übrigens jedes mal wieder alle werte in names gelöscht, also dumm
  names[i-1] = "hodor";
}
//würde einen fehler erzeugen:
//Console.WriteLine(names[0])

Richtig wäre:
Code:
string[] names = new string[8];
for(int i = 0; i < 8; i++) {
  names[i] = "hodor";
}
Console.WriteLine(names[0]) //gibt "hodor" auf der Konsole aus
 
Ohman vielen dank, mein Denkfehler ^^'

Ich dachte wenn ich die Zeile
Code:
string[] names = new string[i];
vor die FOR Schleife setze müsse ich die Zeilen darunter auch außerhalb der Schleife halten was dann dazu geführt hätte das das Programm mcih nur einmal nach einem Namen fragt.

Aber so ergibts endlich einen Sinn.
Vielen Dank! ^-^
 
Ne Schleife ist keine Funktion. Variablen von "außerhalb" kannst du auch "innerhalb" nutzen.
 
alxtraxxx schrieb:
Variablen NIE in einer Schleife deklarieren wenn nicht zwingend nötig

Das hast du dir doch gerade aus den Fingern gesogen.

alxtraxxx schrieb:
das macht Dir zum einen das Debuggen schwer weil die Variablen ständig überschrieben werden

Was ist daran schlimm? In wie fern macht das das Debuggen schwierig?

alxtraxxx schrieb:
und zum anderen wird bei großen Schleifen Dein Speicher irgendwann voll und vor allem ist es langsam wie die Hölle.

Der Speicher wird voll? Eine Fee hat mir mal geflüstert, C# hätte so was wie einen garbage collector und würde nicht mehr referenzierte Objekte der Zerstörung zuführen, aber vielleicht hat sie mich angeschwindelt.

Ich würde im Gegenteil sogar dazu raten, den Scope von Variablen immer auf den kleinstmöglichen Rahmen zu beschränken, es sei denn, es gibt einen guten Grund, dies in bestimmten Fällen nicht zu tun.
 
Zuletzt bearbeitet:
Der Speicher wird voll? Eine Fee hat mir mal geflüstert, C# hätte so was wie einen garbage collector und würde nicht mehr referenzierte Objekte der Zerstörung zuführen, aber vielleicht hat sie mich angeschwindelt.
Programmierst wohl nicht oft C# oder nichts was viel Speicher belegt? Zwigt alleine schon Dein Ratschlag immer alles im
Scope zu deklarieren.
Mach einfach mal eine Schleife und male mal ein paar tausend Punkte per Graphics und nimm dafür immer einen Brush der mit NEW erstellt wird. Schau dann mal in den Taskmanager was passiert. Richtig der GC räumt da nix auf.

Klar, man könnte bei JEDEM Loop GC.Collect() und GC.WaitForPendingFinalizers(); machen, hebt nicht zwingend die Perfomance.

ch würde im Gegenteil sogar dazu raten, den Scope von Variablen immer auf den kleinstmöglichen Rahmen zu beschränken, es sei denn, es gibt einen guten Grund, dies in bestimmten Fällen nicht zu tun.
Wenn man 1000000x new auf eine gleiche Variable macht, zeigt das wie wenig man darüber nachgedacht hat was man da überhaupt programmiert.
Ich vermute Du hast mit PHP oder sowas angefangen, da sieht man ständig solche furchtbaren Dinge
 
alxtraxxx schrieb:
Programmierst wohl nicht oft C# oder nichts was viel Speicher belegt? Zwigt alleine schon Dein Ratschlag immer alles im
Scope zu deklarieren.

Nö, habe mit C++ angefangen, wo so was in der Regel kein Problem ist. Klar gibt's Ausnahmen, wie wenn das zyklische Erstellen / Zerstören des jeweiligen Typs besonders rechenlastig ist und es einen simplen Weg gibt, ein Objekt dieses Typs zu "resetten" (also quaso in den Urzustand zurückzuversetzen).

alxtraxxx schrieb:
Mach einfach mal eine Schleife und male mal ein paar tausend Punkte per Graphics und nimm dafür immer einen Brush der mit NEW erstellt wird. Schau dann mal in den Taskmanager was passiert. Richtig der GC räumt da nix auf.

Klar, man könnte bei JEDEM Loop GC.Collect() und GC.WaitForPendingFinalizers(); machen, hebt nicht zwingend die Perfomance.

Ok, dann ist das (besonders hohe Anzahl an Iterationen) eben einer der Fälle, in denen es einen guten Grund gibt, das Objekt außerhalb der Schleife anzulegen. Die meisten Schleifen laufen aber durch eher wenige Iterationen, und dass in einer Schleife mal ein paar kB Speicher belegt werden, bevor der garbage collector wieder sein Ding tun kann, wäre für mich kein Grund, die Vorteile, die sich aus Minimierung des Scopes ergeben, alle kategorisch wegzuwerfen.

alxtraxxx schrieb:
Wenn man 1000000x new auf eine gleiche Variable macht, zeigt das wie wenig man darüber nachgedacht hat was man da überhaupt programmiert.
Ich vermute Du hast mit PHP oder sowas angefangen, da sieht man ständig solche furchtbaren Dinge

1. Unsinn.
2. Hab' noch nie einen Zeile PHP geschrieben.

EDIT: Den Scope von Variablen zu minimieren scheint auch in C# allgemein anerkannte best practice zu sein: http://stackoverflow.com/questions/3979493/scope-of-variables-in-c-sharp
 
Zuletzt bearbeitet:
alxtraxxx schrieb:
Wenn man 1000000x new auf eine gleiche Variable macht, zeigt das wie wenig man darüber nachgedacht hat was man da überhaupt programmiert.

Du versuchst hier gerade etwas vor einem Anfänger zu pauschalisieren, was ohne Kontext jedoch völlig für die Tonne ist.

Natürlich braucht man nichts neu erzeugen, was man wiederverwenden kann und möglicherweise auch noch einiges an Ressourcen kostet. Keiner würde beim Einfügen in ein File oder eine DB bei jedem Iterationsschritt der Schleife eine neue Verbindung aufmachen und wieder schließen, und dein Brush kannst du auch in diese Kategorie stecken.

"1000000x new auf die gleiche Variable" ist trotzdem völlig in Ordnung (oder gar der einzige Weg), wenn man damit Objekte erzeugt, die für den jeweiligen Iterationsschritt spezifisch sind (was der immer gleiche Brush nicht ist).

antred hat außerdem recht, wenn er sagt, dass der Scope immer möglichst klein gehalten werden sollte. Das ist guter Stil und die Gründe hierfür liegen auf der Hand. Dass der Scope einer Schleife nicht zwingend das beste Beispiel ist, steht auf einem anderen Blatt.

alxtraxxx schrieb:
Schau dann mal in den Taskmanager was passiert. Richtig der GC räumt da nix auf.

Dein Programm stürzt also mit einer Out of Memory Exception ab? Interessant.
 
Zuletzt bearbeitet:
alxtraxxx schrieb:
Mach einfach mal eine Schleife und male mal ein paar tausend Punkte per Graphics und nimm dafür immer einen Brush der mit NEW erstellt wird. Schau dann mal in den Taskmanager was passiert. Richtig der GC räumt da nix auf.
System.Drawing.Brush? Für den solltest du sowieso nach der Benutzung Dispose() aufrufen, da der native Ressourcen benutzt. Das hat mit Schleifen erst mal wenig zu tun.
 
@Synytrix: An der Stelle kannst du dir auch gleich mal angewöhnen, Variablen aussagekräftige ("sprechende") Namen zu geben, damit man vor allem später im Code auch weiß, wofür die gut sind. Sowas wie a oder i ist für einen Loop-Counter in Ordnung, für alles adere aber in aller Regel nicht.

Schlecht:
Code:
        Console.Out.WriteLine("Wie viele Adressen wollen Sie angeben?");
        int i = int.Parse(Console.In.ReadLine());

Besser:
Code:
        Console.Out.WriteLine("Wie viele Adressen wollen Sie angeben?");
        int numAddresses = int.Parse(Console.In.ReadLine());

alxtraxxx schrieb:
Variablen NIE in einer Schleife deklarieren wenn nicht zwingend nötig,
Das sehe ich eher wie andred - Variablen möglichst immer da deklarieren, wo man sie gerade braucht, wenn damit nicht gerade unnötige Heap-Allokationen verbunden sind. Und wenn man eine Variable bei der Deklaration nicht mit einem sinnvollen Wert initialisieren kann (nein, int x = 0; ist nicht immer sinnvoll), dann sollte man sich drei Mal überlegen, ob sie wirklich an der richtigen Stelle steht. Das gilt insbesondere für Objekte, die auf dem Stack liegen.

Warum? Damit keine Zustandsdaten von einer Schleifeniteration in die nächste übertragen werden, wenn sie nicht zwingend benötigt werden. Stichwort Seiteneffekte - wahrscheinlich Hauptgrund für die allermeisten Programmfehler.

Dein Brush-Beispiel scheint insofern eine Ausnahme davon zu sein, als dass a) die Konstruktion aus irgendwelchen Gründen sehr aufwändig ist und b) die Klasse bzw. Unterklassen davon offensichtlich immutable sind oder zumindest in aller Regel so benutzt werden. Da fällt dann natürlich auch das Problem mit den ungewollten Seiteneffekten weg und wenn man nur eine Kopie braucht, sollte man auch nur eine anlegen. Aber das muss man auch dabei schreiben und nicht sinnlos pauschalisieren.

Für RAII ist es nebenbei zwingend notwendig, den richtigen Scope zu wählen. Hat zwar nicht direkt etwas mit C# zu tun, ist aber ein Grund, warum sowas hier in C++ eine absolute Krankheit und häufig gar nicht möglich ist:
Code:
int bla;      // ugh, undefinierter Wert
Blubb blubb;  // ugh, je nach Typ undefinierter Wert, Default-Initialisierung oder Compilerfehler
switch (condition) {
  case CASE_1:
    bla = ...;
    ...
    break;
  case CASE_2:
    blubb = Blubb(....);
    ...
    break;
}
// Hier braucht niemand mehr bla oder blubb
// und niemand weiß, was drin steht
 
Zuletzt bearbeitet:
Klar gibt's Ausnahmen, wie wenn das zyklische Erstellen / Zerstören des jeweiligen Typs besonders rechenlastig ist und es einen simplen Weg gibt, ein Objekt dieses Typs zu "resetten" (also quaso in den Urzustand zurückzuversetzen).
Ein "new" ist NIE resetten, es wird ein neues Objekt erzeugt! Resetten ist das zurücksetzen eines Stringbuilders z.B. statt einen neuen zu erzeugen.
Vermutlich haben das aber auch die 2 Nachfolgeposts nicht verstanden.

Dein Programm stürzt also mit einer Out of Memory Exception ab? Interessant.
Kam alles schon vor, weshalb meine Programme die Speicherauslastung selbst überwacht haben. Früher war auf bei 64bit bei 1.5GB Ram Schicht im Schacht bis MS das endlich behoben hat.


Keiner würde beim Einfügen in ein File oder eine DB bei jedem Iterationsschritt der Schleife eine neue Verbindung aufmachen und wieder schließen
Also Standard in 90% von z.B. PHP-Skripten im Netz ist es Prepared Statements in jeder Interation neu zu erzeugen. Und ja ich hab auch schon Schleifen gesehen in denen bei jedem Iterationsschritt der Buffer neu erzeugt wurde (ich weiß spezifisches Byte-Array pro Iteration? ;))

Ja, da hat einer aber noch nicht viel mit .NET gearbeitet, sonst wüßte man das Dispose oft nicht das tut, was in der Doku steht, offene Streams etc., verwaiste Referenzen in API-Aufrufen die nicht mehr für den GC sichtbar sind etc...
Der GC greift davon abgesehen auch nicht pro Dispose.

Übrigens habe ich folgendes geschrieben (und ihr könnt anscheinend nicht lesen)
Variablen NIE in einer Schleife deklarieren wenn nicht zwingend nötig
Somit hab ich nirgends gesagt, das man NIE etwas in einer Schleife deklarieren soll.
Natürlich muss man neue DataRows erzeugen, um diese in ein DataGridView einzufügen aber deshalb
muss ich mein Übergabearray nicht jedesmal neu deklarieren, außer ich steh da drauf
sinnvolles Rechnerzeit in den Konstruktur zu ballern.
Davon abgesehen kann man auch um eine Schleife ein Scope mit den Deklarationen festlegen, ist aber anscheinend
zu abwegig die Idee ;)
 
alxtraxxx schrieb:
Ein "new" ist NIE resetten, es wird ein neues Objekt erzeugt! Resetten ist das zurücksetzen eines Stringbuilders z.B. statt einen neuen zu erzeugen.
Vermutlich haben das aber auch die 2 Nachfolgeposts nicht verstanden.

Lies dir das Zitat von mir, auf das du dich hiermit beziehst noch mal genau durch und überlege dir, was es bedeutet, bevor du hier Leuten unterstellst, simpelste Sachverhalte nicht verstanden zu haben. Kein Mensch ist so blöd, anzunehmen dass eine new-Allozierung ein bereits bestehendes Objekt resettet.
 
alxtraxxx schrieb:
Ja, da hat einer aber noch nicht viel mit .NET gearbeitet, sonst wüßte man das Dispose oft nicht das tut, was in der Doku steht, offene Streams etc., verwaiste Referenzen in API-Aufrufen die nicht mehr für den GC sichtbar sind etc...
Dafür ist Dispose doch da, für Speicher den der GC nicht sieht.
Unabhängig von dem was in der Doku steht: Wenn man es nicht aufruft, darf man sich nicht über Speicherauslastung beschweren. Du lässt am Autoreifen ja auch nicht 3 Schrauben weg und beschwerst dich dann, dass die verbleibende Schraube mangelhaft ist und den Reifen nicht gehalten hat.

Hier mal harte Fakten:
(Kompiliert mit VS 2013, Any CPU, Release, Windows 7 x64)

Testprogramm:
Code:
class Program
{
    static void InLoop()
    {
        for (long i = 0; i < 1000000000; i++)
        {
            Foo f = new Foo();
            f.Bar(i);
        }
    }

    static void OutLoop()
    {
        Foo f;
        for (long i = 0; i < 1000000000; i++)
        {
            f = new Foo();
            f.Bar(i);
        }
    }
}

class Foo
{
    public long Bar(long fizz)
    {
        return fizz + 1;
    }
}
IL für InLoop:
Code:
.method private hidebysig static void  InLoop() cil managed
{
  // Code size       34 (0x22)
  .maxstack  2
  .locals init (int64 V_0,
           class ConsoleApplication1.Foo V_1)
  IL_0000:  ldc.i4.0
  IL_0001:  conv.i8
  IL_0002:  stloc.0
  IL_0003:  br.s       IL_0018
  IL_0005:  newobj     instance void ConsoleApplication1.Foo::.ctor()
  IL_000a:  stloc.1
  IL_000b:  ldloc.1
  IL_000c:  ldloc.0
  IL_000d:  callvirt   instance int64 ConsoleApplication1.Foo::Bar(int64)
  IL_0012:  pop
  IL_0013:  ldloc.0
  IL_0014:  ldc.i4.1
  IL_0015:  conv.i8
  IL_0016:  add
  IL_0017:  stloc.0
  IL_0018:  ldloc.0
  IL_0019:  ldc.i4     0x3b9aca00
  IL_001e:  conv.i8
  IL_001f:  blt.s      IL_0005
  IL_0021:  ret
} // end of method Program::InLoop
IL für OutLoop:
Code:
.method private hidebysig static void  OutLoop() cil managed
{
  // Code size       36 (0x24)
  .maxstack  2
  .locals init (class ConsoleApplication1.Foo V_0,
           int64 V_1)
  IL_0000:  ldnull
  IL_0001:  stloc.0
  IL_0002:  ldc.i4.0
  IL_0003:  conv.i8
  IL_0004:  stloc.1
  IL_0005:  br.s       IL_001a
  IL_0007:  newobj     instance void ConsoleApplication1.Foo::.ctor()
  IL_000c:  stloc.0
  IL_000d:  ldloc.0
  IL_000e:  ldloc.1
  IL_000f:  callvirt   instance int64 ConsoleApplication1.Foo::Bar(int64)
  IL_0014:  pop
  IL_0015:  ldloc.1
  IL_0016:  ldc.i4.1
  IL_0017:  conv.i8
  IL_0018:  add
  IL_0019:  stloc.1
  IL_001a:  ldloc.1
  IL_001b:  ldc.i4     0x3b9aca00
  IL_0020:  conv.i8
  IL_0021:  blt.s      IL_0007
  IL_0023:  ret
} // end of method Program::OutLoop
Wie man sieht, sieht man nichts.

Beide Methoden erzeugen praktisch den gleichen Code. Wenn die Variable außerhalb der Schleife deklariert wird, sind's sogar zwei Instruktionen mehr, da die Variable mit null initialisiert wird. In beiden Fällen ist die Variable auf Methodenebene.
Und das ist alles reine MSIL-Compiler-Optimierung. Der JIT-Compiler kann zur Laufzeit u. U. noch weitere Optimierungen vornehmen (hier wahrscheinlich nicht, aber prinzipiell schon).
 
Zurück
Oben