Laden...

Filter Liste mit Linq

Erstellt von Nejox vor 2 Jahren Letzter Beitrag vor 2 Jahren 318 Views
N
Nejox Themenstarter:in
19 Beiträge seit 2016
vor 2 Jahren
Filter Liste mit Linq

Hallo zusammen,

ich hab eine kleine Blockade und hoffe Ihr könnt mir helfen 🙂

ich möchte gerne in einer Liste alle items suchen die meinen string ähneln mittels linq und ein Count() machen.

Dabei habe ich eine Liste aus meiner Klasse Person.


public class Person
{
        public string Name{ get; set; }

        public string Vorname{ get; set; }
}

Mein string ist eine Variable die über eine TextBox gesetzt wird.


string name = TextBox.Text;

Nun möchte ich mit alle Namen in meiner Liste Personen holen die %name% enthalten. Also quasi wie im SQL ein LIKE.


//Man müsste sich vorstellen die Liste wäre hier schon gefüllt :-)
List<Person> liste = new List<Person>()

//Das wäre mein Ansatz. Funktioniert aber so nicht.
var CountPerson = liste.Select(s => s.Name).Contains(name).toList().Count();

Hoffe ihr könnt mir helfen 🙂

D
261 Beiträge seit 2015
vor 2 Jahren

var CountPerson = liste.Where(s => s.Name.Contains(name)).Count();

bzw vereinfacht:


var CountPerson = liste.Count(s => s.Name.Contains(name));

16.835 Beiträge seit 2008
vor 2 Jahren

Als Hinweis für die Mikro-Optimierer: die "vereinfachte" Schreibweise ist i.d.R. nicht die schnellste.
.Where(predicate).First() ist schneller und erzeugt weniger Allocations als .First(predicate); das gilt für alle Anwendungen von .Where().
Hintergrund ist (vereinfacht), dass zB First() auf einer Schleifen-Implementierung basiert und .Where() auf die Iteratoren der Collections zurück greift.

Hier ein Benchmark Beispiel:


public class LinqTest
{
    private List<string> _list;

    [GlobalSetup]
    public void Setup()
    {
        _list = Enumerable.Range(0, 100).Select(c => c.ToString()).ToList();
    }

    [Benchmark]
    public int WherePredicate() => _list.Where(e => e.Contains("a")).Count();

    [Benchmark]
    public int CountPredicate() => _list.Count(e => e.Contains("a"));
}


BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
.NET Core SDK=5.0.300-preview.21180.15

|         Method |       Mean |    Error |   StdDev |
|--------------- |-----------:|---------:|---------:|
| WherePredicate |   504.7 ns | 11.90 ns | 11.13 ns |
| CountPredicate | 1,166.8 ns | 11.30 ns | 10.57 ns |

Der Unterschied 504 vs 1166 ns ist schon enorm 🙂

PS: ToList sollte bewusst eingesetzt werden, nicht Random.
Intermediate materialization (C#)

Einstieg zum Thema Linq, und wie es funktioniert:
101 LINQ samples - Code Samples

N
Nejox Themenstarter:in
19 Beiträge seit 2016
vor 2 Jahren

Hello Abt and dannoe,

thanks a lot 🙂
Now it works.

6.911 Beiträge seit 2009
vor 2 Jahren

Hallo zusammen,

Ergänzung für die Micro-Optimierungen:

Laufzeit ist eine Sache, aber je nach Kontext können auch die Allokationen eine Rolle spielen.
Hier im Thema vom OP geht es um eine Desktopanwendung, da wird wohl eher die Laufzeit eine Rolle spielen, da die GC-Arbeit sich nicht so bemerkbar machen wird.
Bei Server-Anwendungen hat der GC mMn mehr Gewichtung in der Entscheidung Laufzeit <-> Allokationen, da dort u.U. viel CPU-Zeit für das Aufräumen vom Speicher benötigt werden kann. Und der beste Weg dem GC weniger Arbeit zu machen ist einfach weniger zu Allozieren.

Wenn es eine Entscheidung / Tradeoff zwischen Laufzeit und Allokationen zu machen gilt, so sollte diese basierend auf Metriken wie Requests pro Sekunde, Antwortzeiten / Latenz, etc. getroffen werden. Also das größere Bild über eine längeren Zeitraum betrachtet werden und nicht nur ein Micro-Benchmark*. Letztere finden isoliert statt und v.a. wenn es Allokationen gibt, so wird das Zusammenspiel mit anderen Allokationen der Anwendung nicht real abgebildet. Daher können auch keine Pauschal-Aussagen getroffen werden, was eindeutig zu bevorzugen ist.

* Micro-Benchmarks sind ein Mittel um Varianten zu beurteilen, haben auch gewissen Einschränkungen die nicht außer Acht gelassen werden sollten. Neben den angesprochen Allokationen sind auch ein paar CPU-Effekte (Branch-Prediktor, Caches, ...) zu berücksichtigen und daher spiegeln sie oft nicht die Realität wider.

Wenn ich den obigen Benchmark ein wenig ergänze


using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<LinqTest>();

[MemoryDiagnoser]
[CategoriesColumn]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
public class LinqTest
{
    private List<string> _list;

    [GlobalSetup]
    public void Setup()
    {
        _list = Enumerable.Range(0, 100).Select(c => c.ToString()).ToList();
    }

    [Benchmark(Baseline = true, Description = "Count only")]
    [BenchmarkCategory("Count")]
    public int Count() => _list.Count(e => e.Contains("a"));

    [Benchmark(Description = "Where Count")]
    [BenchmarkCategory("Count")]
    public int WhereCount() => _list.Where(e => e.Contains("a")).Count();

    [Benchmark(Baseline = true, Description = "First only")]
    [BenchmarkCategory("First")]
    public string First() => _list.FirstOrDefault(e => e.Contains("a"));

    [Benchmark(Description = "Where First")]
    [BenchmarkCategory("First")]
    public string WhereFirst() => _list.Where(e => e.Contains("a")).FirstOrDefault();
}

so kommt bei mir dabei


|        Method | Categories |     Mean |     Error |    StdDev | Ratio | RatioSD |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------- |----------- |---------:|----------:|----------:|------:|--------:|-------:|------:|------:|----------:|
|  "Count only" |      Count | 2.184 μs | 0.0433 μs | 0.0405 μs |  1.00 |    0.00 | 0.0114 |     - |     - |      40 B |
| "Where Count" |      Count | 1.293 μs | 0.0249 μs | 0.0341 μs |  0.59 |    0.02 | 0.0229 |     - |     - |      72 B |
|               |            |          |           |           |       |         |        |       |       |           |
|  "First only" |      First | 2.350 μs | 0.0427 μs | 0.0399 μs |  1.00 |    0.00 | 0.0114 |     - |     - |      40 B |
| "Where First" |      First | 1.861 μs | 0.0364 μs | 0.0357 μs |  0.79 |    0.02 | 0.0229 |     - |     - |      72 B |

heraus.
Anmerkung: die absoluten Laufzeit-Werte sind uninterssant, es geht nur um das Verhältnis zwischen den Varianten.

Die Varianten Where + Count / First sind also gut 20-40% schneller, allozieren aber um 80% mehr (da zwei Linq-Enumeratoren benötigt werden).
Hier haben wir also das vorhin erwähnte Tradeoff-Problem zwischen Laufzeit und Allokationen.

Was würde ich nun tun?
Desktopanwendung: das Where-Count wie von Abt empfohlen verwenden. Das ist leserllich -- mindestens genauso gut wie nur Count -- und schneller. Dass ein wenig mehr alloziert wird stört nicht.
Serveranwendung: mit Where-Count starten. Sollte es in einem Profil (entweder CPU od. GC) als großer Einfluss auftauchen, so vermutlich auf Linq verzichten und selbst über die Auflistung iterieren, v.a. wenn ich weiß dass es eine List ist kann das performanter und ohne Allokation durchgeführt werden. Wenns aber nicht in einem Profil auftaucht, so erspare ich mir die Arbeit und gehe dein einfachen Weg.

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!"