Laden...

Geht der folgende Code auch effizienter?

Erstellt von mchrd vor 5 Jahren Letzter Beitrag vor 5 Jahren 5.219 Views
M
mchrd Themenstarter:in
17 Beiträge seit 2018
vor 5 Jahren
Geht der folgende Code auch effizienter?

Hallo zusammen,
ich möchte Daten aus einer Datei einlesen. Diese möchte ich gerne in eine Liste<Person> eintragen lassen. Hinbekommen habe ich es, jedoch stehe ich noch am Anfang was meine Erfahrung in C# angeht und ich denke, dass man das folgende Beispiel effizienter lösen kann:

Die Datei hat folgenden Aufbau:


Vorname; Nachname;
Daniel; Müller
...
Hans; Schneider
class Person
{
public string Vorname{ get; set; }
public string Nachname{ get; set; }
}
string path = @"C:\Daten.csv";

            var csvData = File.ReadAllLines(path)
                   .Skip(1)
                   .Select(x => x.Split(';'))
                   .Select(x => new Person()
                   {
                       Vorname = x[0],
                       Nachname = x[1]
                   });

            var Person= new List<Person>();

            foreach (var item in csvData)
            {
                Person.Add(new Person () {   
                                                                Vorname = item.Vorname,
                                                                Nachname = item.Nachname
                                                            });
            }

Ich hatte probiert an die LinQ Abfrage wie folgt zu erstellen:

List<Person> csvData  = File.ReadAllLines(path)
                   .Skip(1)
                   .Select(x => x.Split(';'))
                   .Select(x => new Person()
                   {
                       Vorname = x[0],
                       Nachname = x[1]
                   }).ToList();

Was leider nicht erfolgreich war (auch andere diverse Formen haben nicht funktioniert).

Was mache ich falsch? Wie bekomme ich die Daten sofort in die List vom Typ Person?

Noch eine andere Fragen:

  • Mir ist beim Ausführen aufgefallen, dass ich mal eine Fehlermeldung bekommen habe, dass auf die Datei nicht zugegriffen werden kann da diese von einem anderen Prozess verwendet wird (ich hatte die Datei geöffnet). Daher würde es vermutlich Sinn machen zu prüfen ob die Datei "zur Verfügung" steht. Was würde man noch prüfen, bevor man mit dem File.ReadAllLines auf die Datei zugreift?

Danke für eure Hilfe!

1.040 Beiträge seit 2007
vor 5 Jahren

Schaue dir mal mit dem [Artikel] Debugger: Wie verwende ich den von Visual Studio? an, von welchem Typ csvData ist. =)

Der zweite C#-Code sollte so auch funktionieren, was kriegst du für einen Fehler?

S
248 Beiträge seit 2008
vor 5 Jahren

Was würde man noch prüfen, bevor man mit dem File.ReadAllLines auf die Datei zugreift?

Hallo mchrd,

eine vorangestellt Prüfung macht keinen Sinn, da selbst wenn deine Prüfung positiv ist, kann die Datei zwischen deiner Prüfung und deinem Zugriff gelöscht oder gelockt werden. In diesem Fall bekommst du wieder eine Exception ... bist also genau so weit wie vorher. Daher einfach versuchen die Datei zu öffnen, so wie du es bereits jetzt machst.

Kleiner Verbesserungsvorschlag:
Anstatt File.ReadAllLines könntest du File.ReadLines verwenden, so dass nicht erst die komplette Datei geladen werden muss.

Grüße
spooky

16.806 Beiträge seit 2008
vor 5 Jahren

Was ist effizienter für Dich? 😉

Linq ist nicht immer hübscher - und auch nicht immer performanter.

Daher würde es vermutlich Sinn machen zu prüfen ob die Datei "zur Verfügung

Das Prüfen ist nur möglich, indem Du die Exception abfängst. Davon abgesehen macht bei solchen Zugriffen ein vorheriges Prüfen kein Sinn, weil trotzdem die Situation auftreten könnte, dass zwischen Prüfung und Zugriff jemand anderes die Datei lockt - wenig wahrscheinlich aber theoretisch möglich.

Mein Rat: lass Linq erst mal weg und bau lieber erst mal stabilen, gut strukturierten Code.

M
mchrd Themenstarter:in
17 Beiträge seit 2018
vor 5 Jahren

Hallo,

ich danke euch herzlich für eure Antworten! Diese haben mir wirklich geholfen.

@p!lle
Danke für den Link zu dem guten Artikel. Dass der Typ csvData Person ist wusste ich, da ich aus dem anonymen Typ new ein new Person

.Select(x => new Person()

gemacht hatte. Nur wollte ich es gerne in einer generischen Liste haben (List<Person>) haben. Der Artikel hat mir aber dennoch geholfen, da ich jetzt einige Dinge dazu gelernt habe.

Ich habe das mit dem zweiten Code durchgespielt und es hat wirklich funktioniert. Ich hatte einen anderen Fehler (dort hatte ich eine Datei verwendet, welche eine Spalte weniger hatte. Danke für den Hinweis!

@Spook
Stimmt das ist ein wirklich guter Einwand (wenn zwischen der positiven Prüfung und dem Aufruf die Datei doch noch von einem anderen Prozess blockiert wird).

Das File.ReadLines zu verwenden ist ein super Hinweis! Danke! Ich habe noch ein wenig nachgelesen und es macht absolut Sinn. Danke!

@Abt
Nein das stimmt Linq ist nicht unbedingt immer performant. Bei SQL Abfragen habe ich das bereits mehrfach gelesen, dass komplexe Abfragen nicht unbedingt derart optimiert sind, dass sie mit einer guten SQL Abfrage mithalten können.

Kannst du mir noch einen Tipp geben, mit welcher Klasse ich die CSV Datei sonst einlesen kann? Da ich beim googlen i.d.R. immer nur Linq-Abfragen finde.
Danke auch für deine Hilfe!

++
Ich wünsche euch allen einen schönen Abend++

16.806 Beiträge seit 2008
vor 5 Jahren

LINQ ist nicht SQL.

Bei SQL Abfragen habe ich das bereits mehrfach gelesen, dass komplexe Abfragen nicht unbedingt derart optimiert sind, dass sie mit einer guten SQL Abfrage mithalten können.

Das sind zwei paar Stiefel, die erst mal nichts miteinander zutun haben.
Da solltest Du evtl. nochmal genauer lesen 😉

Kannst du mir noch einen Tipp geben, mit welcher Klasse ich die CSV Datei sonst einlesen kann? Da ich beim googlen i.d.R. immer nur Linq-Abfragen finde.

Linq liest Dir nicht Dein CSV ein. Auch bei Deinem Code nicht.
Du kannst problemlos auf Linq in Deinem Code verzichten und trotzdem identisch Dein CSV einlesen.

Und dass Du "nur Linq" in Zusammenhang mit CSV findest - bezweifle ich sehr stark.

Vielleicht solltest Du auch noch mal lesen, was Linq ist 😉 Gerade Einsteiger tun sich oft schwer mit Linq; weil das Wissen fehlt, was Linq ist - und was nicht.
-> Linq ist nichts anderes als ein funktionaler Ansatz in .NET, um gewisse Schreibweisen zu vereinfachen.
Alles in Linq besteht aber im Endeffekt aus Schleifen und Bedingungen.

Ein Einstieg dazu: Besserer C#-Code durch funktionale Programmierung
Aber wie gesagt; übersichtlicher oder schneller ist Linq nicht unbedingt; letzteres aber auch in dem Delta nicht relevant.

M
mchrd Themenstarter:in
17 Beiträge seit 2018
vor 5 Jahren

Guten Morgen Abt,
danke für deine Antwort.

LINQ ist nicht SQL.

Bei SQL Abfragen ....
Das sind zwei paar Stiefel, die erst mal nichts miteinander zutun haben.
Da solltest Du evtl. nochmal genauer lesen 😉

Ok, dann werde ich das tun. Wird aber sicher noch einige Zeit in Anspruch nehmen bis ich den Stand habe.

Kannst du mir noch einen Tipp geben, mit welcher Klasse ich die CSV Datei sonst einlesen kann? Da ich beim googlen i.d.R. immer nur Linq-Abfragen finde.
Linq liest Dir nicht Dein CSV ein. Auch bei Deinem Code nicht. Du kannst problemlos auf Linq in Deinem Code verzichten und trotzdem identisch Dein CSV einlesen. Und dass Du "nur Linq" in Zusammenhang mit CSV findest - bezweifle ich sehr stark.

Wenn ich es richtig verstanden habe, gehören die Funktionen Split(), Select(), Skip() zu LINQ. Dann ist es wirklich so, dass ich auf StackOverflow überall mindestes eine der Funktionen gefunden habe. Selbst wenn ich nach "C# read csv file without linq" eingebe, komme ich auf derarige Seiten mit linq Lösungen. Ich finde zwar auch so Ausdrücke wie:


var query = from line in File.ReadLines(filename)
    let csvLines = line.Split(';')
    from csvLine in csvLines
    where !String.IsNullOrWhiteSpace(csvLine)
    let data = csvLine.Split(',')
    select new
    {
        ID = data[0],
        FirstNumber = data[1],
        SecondNumber = data[2],
        ThirdNumber = data[3]
    };

Aber selbst hier ist ja das "Split" von LINQ oder verstehe ich das völlig falsch?

Vielleicht solltest Du auch noch mal lesen, was Linq ist 😉 Gerade Einsteiger tun sich oft schwer mit Linq; weil das Wissen fehlt, was Linq ist - und was nicht.
-> Linq ist nichts anderes als ein funktionaler Ansatz in .NET, um gewisse Schreibweisen zu vereinfachen.
Alles in Linq besteht aber im Endeffekt aus Schleifen und Bedingungen.

Ja wahrscheinlich habe ich da noch viel bedarf und muss noch einiges lernen um es wirklich zu verstehen.

Ein Einstieg dazu:
>

Aber wie gesagt; übersichtlicher oder schneller ist Linq nicht unbedingt; letzteres aber auch in dem Delta nicht relevant.[/csharp]
Danke für den super Link. Von der Basta Konferenz gibt es ja einige interessante Videos.

Danke für deine/eure Hilfe!

T
2.219 Beiträge seit 2008
vor 5 Jahren

Split ist eine Methode der String Klasse und gehört nicht zu Linq.
Aber den Code kannst du auch komplett ohne Linq mit einer einfachen Schleife bauen.


string[] lines = File.ReadLines(filename);

foreach(string line in lines)
{
    // Leere Zeile überspringen
    if(String.IsNullOrEmpty(line))
        continue;

    // String in Teile zerlegen
    string[] data = line.Split(';');

    // Anhand der Teile nun die Person erstellen
    Person person = new Person
    {
        ID = data[0],
        FirstNumber = data[1],
        SecondNumber = data[2],
        ThirdNumber = data[3]
    };
}

An einigen Stellen ist Linq wirklich sehr hilfreich, aber sobald man mit großen Listen/Arrays o.ä. arbeitet und viele Operationen darüber laufen lassen muss, sollte man sich überlegen ob Linq sinnvoll ist.
Das ganze Konzept von Linq basiert, wie von Abt schon angesprochen hat, auf Schleifen mit Iteratoren.

Es gibt Fälle wo man mit einer einfachen Schleife und somit nur einem einmaligem Durchlauf über die Liste eine bessere Lösung bekommt als mit n-Linq Operationen, da hier durch die einzelnen Zwischenergebnisse durchlaufen werden muss, was dann eben deutlich länger dauert.

Aber bei kleineren/mittleren Listen kann man auch mit Linq gut arbeiten.
Die Einsparungen ohne Linq wären dann für viele Fälle zu klein.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.806 Beiträge seit 2008
vor 5 Jahren

Aber selbst hier ist ja das "Split" von LINQ oder verstehe ich das völlig falsch?

Wenn sich Dir diese Frage stellt und Dir damit unsicher bist, dann schau in die Doku: String.Split Method
Da siehst Du schon an Namespace, dass diese eben NICHT zu Linq gehört, wie auch T-Virus schon sagte.

3.003 Beiträge seit 2006
vor 5 Jahren

Es gibt Fälle wo man mit einer einfachen Schleife und somit nur einem einmaligem Durchlauf über die Liste eine bessere Lösung bekommt als mit n-Linq Operationen, da hier durch die einzelnen Zwischenergebnisse durchlaufen werden muss, was dann eben deutlich länger dauert.

Es ist mitnichten so, dass pro Linq-"Operation" einmal die Liste und alle Zwischenergebnisse durchlaufen wird, wo hast du denn den Unsinn her?

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

16.806 Beiträge seit 2008
vor 5 Jahren

LaTino, der einzige Fall, der mir dazu aktuell einfällt den er meinen _könnte _wäre, wenn jedes Linq-Segment durch ein ToList aktiv materialisiert wird.

T
2.219 Beiträge seit 2008
vor 5 Jahren

Mein Fall bezieht sich auf die Methoden, die ein IEnumerable zurück liefern.
Also Methoden wie Select, Where, GroupBy etc.
Wenn man diese bei großen Listen, Arrays o.ä. zusammen hängt, muss auch zusätzlich iterriert werden.

Ich beziehe mich also nur auf die Fälle, wo Listen/Arrays eine Rolle spielen und man eben durch Abfragen Verschachtelung durch die entsprechenden Teilmengen durchlaufen muss.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.806 Beiträge seit 2008
vor 5 Jahren

Wenn man diese bei großen Listen, Arrays o.ä. zusammen hängt, muss auch zusätzlich iterriert werden.

Nein.

T
2.219 Beiträge seit 2008
vor 5 Jahren

Wenn ich mir den Referenz Code für die Erweiterungsmethoden anschaue, dann sehe ich dort die interne Verwendung der entsprechenden Iteratoren.
Damit also die Daten entsprechend bei Select, Where und GroupBy bereitstehen, muss immer über die jeweilige Untermenge gelaufen werden.
Das ist aber eben nur bei zwei Fällen ein Problem.

1.Großen Datenmengen, ab einigen Mio. Datensätzen
2.Beim durchlaufen von Schleifen mit entsprechenden Listen für Filterungen von Daten

In beiden Fällen hat man ab einem bestimmten Punkt einfach einen Overhead durch die ganzen Iteratoren.
Das ist aber eben nur dann der Fall, wenn es um Performance geht.
Dies sind dann aber eben Sonderfälle wo es wirklich um jede Sekunde geht.
In 99% der Fälle kann Linq ohne bedenken nutzen kann.

Link:
https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,0e5ab1a57b7e1438

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

3.003 Beiträge seit 2006
vor 5 Jahren

Entschuldige bitte, wenn ich das so offen sage, aber du hast ganz offenbar weder Iteratoren noch das Prinzip von Linq ansatzweise verstanden.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

6.911 Beiträge seit 2009
vor 5 Jahren

Hallo T-Virus,

ich denke du hast dich da in etwas verrannt bzw. falsch aufgefasst.
LinQ arbeitet wie ein Fließband und nach dem Pull-Prinzip, d.h. der jeweilige Verbraucher holt sich die nächsten Daten und dabei wird nur soweit iteriert wie nötig. LinQ arbeitet daher grundsäztlich nach O(n), wobei auch nur einmal über die gesamte Quelle iteriert wird -- wie schon angemerkt worden ist.

Grob schaut es dabei immer so aus:


public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> filter)
{
    foreach (T item in source)
        if (filter(item))
            yield return item;
}

Im Quellcode (entweder der Reference-Source od. jener in corefx auf GitHub) schaut es ein wenig anders aus, da sämtliche Optimierungen und Spezialisierungen (wie wenn IEnumerable<T> ein Array ist, etc.) angewandt worden sind. Genauso wird im Quellcode oft auf yield return verzichtet und stattdessen der Iterator selbst programmiert (yield return wird vom C#-Compiler in den entsprechenden Iterator umgeschrieben).

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

T
2.219 Beiträge seit 2008
vor 5 Jahren

@gfoidl
Hast recht.
Hab mir den Code nochmal angeschaut und ein 0815 Test gefahren.
Hier sehe ich auch, dass erst bei ToList das fertige Ergebnis gebaut wird.
Entsprechend lag ich da wirklich ganz falsch.

Ich bin bisher davon ausgegangen, dass dort Teilmengen durch die Where/Select und andere Methoden geliefert werden, die dann eben von der Folgeoperation wieder verarbeitet werden.
Aber mit meinem Test und etwas Debuggen, habe ich auch gesehen, dass ich falsch lag.
Die Daten werden dann erst beim ToList erst durch die Iteratoren gefiltert und geliefert.

T-virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

M
mchrd Themenstarter:in
17 Beiträge seit 2018
vor 5 Jahren

Hallo zusammen,

zunächst einmal vielen Dank T-Virus für das Beispiel!

Allerdings musste ich aus "string[] lines"

string[] lines = File.ReadLines(filename);

foreach(string line in lines)
{
    // Leere Zeile überspringen
    if(String.IsNullOrEmpty(line))
        continue;

    // String in Teile zerlegen
    string[] data = line.Split(';');

ein "var lines" machen, dass es compiliert wurde. Also implizit die Variable lines deklarieren und den Compiler entscheiden lassen, welchen Typ die Variable hatte.

Anhand eurer Diskussionen merke ich, wie wenig Ahnung ich von C# habe und wie viel ihr könnt!

Danke nochmal an alle die hier so ausführlich erklären!

T
2.219 Beiträge seit 2008
vor 5 Jahren

Stimmt, hatte ich mit ReadAllLines vertauscht, die meinte ich auch eigentlich.
Ist für den Einstieg verständlicher, wenn man ein String[] bekommt als das IEnumerable<string> 😃

Es wird zwar einige Zeit dauern, aber wenn du dran bleibst kommst du auch da hin wo wir heute sind.
Ist alles nur eine Frage der Zeit bis damn einen Stand hat, wo man die meisten Dinge im Alltag abdeckt.
Aber gerade in der Programmierung lernt man nie aus.
Selbst nach 10 Jahren muss man auch immer wieder neues Lernen, weil sich die Praxis hier teilweise sehr schnell verändert.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.