Entwickler-Ecke

Basistechnologien - Implizite Verweiskonvertierung nicht vorhanden


erfahrener Neuling - Di 16.10.18 20:20
Titel: Implizite Verweiskonvertierung nicht vorhanden
Hallo Leute,

Folgendes:
Ich hatte angefangen, mir Erweiterungsmethoden für Collections aller Art zu schreiben. Bei genauer Angabe des Typs (z. B. ControlColllection) funktioniert das auch ganz gut.

Jetzt wollte ich das aber verallgemeinern, z. B. so

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
public static int RemoveWhere<T1, T2>(this T1 collection, Func<T2, bool> condition) where T1 : ICollection<T2>

    // T2 muss zwingend mit dem Typen oder einem Interface der Objekte der Collection übereinstimmen
    int removeCount = 0;
    foreach (T2 obj in collection)
        if (condition.Invoke(obj))
        {
            collection.Remove(obj);
            removeCount++;
        }
    return removeCount;
}

bzw. so
public static int RemoveWhere<T1>(this T1 collection, Func<objectbool> condition) where T1 : ICollection<object
{
    ...
}

Die Einschränkung auf ICollection ist notwendig, damit meine collection die Methode Remove kennt (und das die Methode nur bei Listen, Collection, etc. auftaucht).
Nun erhalte ich beim Aufruf der Methode aber den Compilerfehler CS0311 [https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/compiler-messages/cs0311]

error1

Hat jemand eine Idee, was ich machen muss, damit es nach diesem Schema funktioniert?
Oder macht man das vielleicht prinzipiell anders?

MfG
Julian


Ralf Jansen - Di 16.10.18 20:57

DataGridViewRowCollection ist keine IList<T> sondern nur eine IList (nicht generisch). Insofern kann man das nicht einfach casten. Linq bringt aber schon eine ExtensionMethod mit die aus einer IList eine IList<T> macht. Die Cast<T> Methode.

Du müßtest also auf deiner DataGridViewRowCollection einmal Cast<DataGridViewRow>() aufrufen. Dadurch bekommst du nebenbei auch eine neue Liste. Was hier den Vorteil hat das dein Code auch funktioniert könnte wenn man das an der richtigen Stelle tut ;) Du versuchst in einem foreach die Liste zu ändern über die du gerade iterierst. Das ist nicht erlaubt und sollte zur Laufzeit knallen.

Edit: Ich korrigiere Cast erzeugt noch keine neue List sondern nutzt denn originalen Enumerator wird also auch knallen. Du mußt auch einmal ToList() aufrufen um eine neue Liste zu erzeugen.


erfahrener Neuling - Di 16.10.18 21:27

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Du versuchst in einem foreach die Liste zu ändern über die du gerade iterierst. Das ist nicht erlaubt und sollte zur Laufzeit knallen.

Also wenn ich die Methode so aufrufe:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
public static int RemoveWhere(this DataGridViewRowCollection rowCollection, Func<DataGridViewRow, bool> condition)
{
    int removeCount = 0;
    foreach (DataGridViewRow row in rowCollection)
        if (condition.Invoke(row))
        {
            rowCollection.Remove(row);
            removeCount++;
        }
    return removeCount;
}

dann gibt es keinen Laufzeitfehler und die korrekten Rows werden entfernt.

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Du mußt auch einmal ToList() aufrufen um eine neue Liste zu erzeugen.

Wo genau meinst du? DataGridViewRowCollection oder ControlCollection kennen bei mir kein ToList() oder andere Linq-Methoden

EDIT:
Achso vorher noch den Cast ausführen
also: dataGridViewData.Rows.Cast<DataGridViewRow>().ToList()...

EDIT 2:
Also ich seh schon, dass das hier glaub ich zu nichts führt. Eine neue Liste zu bearbeiten bringt mich nicht weiter, sondern macht es nur komplizierter. Dann muss ich eben für jeden Typen eine eigene Methode schreiben (siehe oben).

Trotzdem danke!


Ralf Jansen - Di 16.10.18 21:54

Zitat:
dann gibt es keinen Laufzeitfehler und die korrekten Rows werden entfernt.

Das ist ein Nebeneffekt der konkreten DataGridViewRowCollection Implementierung. Dort entspricht Remove eher einem verstecken. Da wird also nicht wirklich gelöscht womit dann foreach kein Problem hat. Im allgemeinen Fall wird es aber knallen und du versuchst ja eine allgemeine Lösung zu schaffen. Ist da ein wenig unglücklich das DataGridViewRowCollection schon eine sehr ~spezielles~ Konstrukt ist, sich explizit anders verhält und du das als Bespiel Collection benutzt.
Zitat:
DataGridViewRowCollection oder ControlCollection kennen bei mir kein ToList() oder andere Linq-Methoden

Weil, wie schon gesagt, DataGridViewRowCollection nicht auf generischen Klassen aufbaut. Und Linq basiert nun mal stark auf Generics. DataGridViewRowCollection gehört zu Winforms und ist damit älter als Generics und kommt ohne aus. Microsoft hat es nie für nötig gehalten das zu aktualisieren womit sich in der Winforms Welt Linq und Konsorten oft genug ein Fremdkörper ist. Die dachten damals schon, so ungefähr 2005, das Winforms alter Quatsch ist.
Zitat:
Eine neue Liste zu bearbeiten bringt mich nicht weiter, sondern macht es nur komplizierter.

Das war nicht das wo ich dich hinführen wollte. Eine neue Liste solltest du nur zum iterieren benutzen. Löschen solltest du die in der Original Liste. Auch wenn das 2 Listen sind die Objekte darin sind ja trotzdem die gleichen.
Ein simples

C#-Quelltext
1:
2:
3:
foreach (var row in rowCollection.ToList()) // hier ein ToList ergänzen
    if (condition.Invoke(row))
        rowCollection.Remove(row);

würde schon reichen. Deiner Feststellung das das nichts bringt würde ich aber in teilen teilen ;) Mindestens für DataGridViewRowCollection brauchst du bestimmt ein spezielle Lösung. Eine allgemeine Lösung die auch für DataGridViewRowCollection funktioniert wird hässlich.


erfahrener Neuling - Di 16.10.18 22:12

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Eine neue Liste solltest du nur zum iterieren benutzen. Löschen solltest du die in der Original Liste.

Ok jetzt hab ich's auch verstanden. Das behebt den Laufzeitfehler. Dann habe ich zwar trotzdem noch das Problem, das ich im Methodenkopf auf IList<T2> oder ICollection<T2> einschränken müsste (damit er Remove() kennt) und somit die ganze Sache nur für richtige Listen interessant wird, aber trotzdem danke für den Tipp :zustimm:

Thema erledigt!