C# Generic Collections, Interfaces explicit oder implicit implementieren

Magic1416

Lieutenant
Registriert
Dez. 2003
Beiträge
513
Hallo,
ich habe eine Designfrage zum Thema Generic Collections und Interfaces. In dem angehängten Code ist ein vereinfachtes Beispiel. Die Frage ist, soll man IResultCollection implicit oder explicit implementieren, oder ist das eine freie Entscheidung ?

Der Hintergrund ist nämlich folgender:
Ich habe in meiner Solution folgende Projeke:
MyProject.Contracts
MyProjects.Rest
MyProjects.SQL
MyProjects.WebSite

Die WebSite beziehte ursprünglich seine Daten zum Präsentieren über Rest. Die angebotene Rest Schnittstelle bietet nicht sehr viele Möglichkeiten. Das Zusammensuchen der Daten dauert zum Teil bis zu 5 Sekunden. Zu Lange.

Deshalb wurde dasselbe auch über SQL realisiert. Dort kann das gesamte Ergebnis in weniger als 100 ms gesucht und präsentiert werden. Deshalb soll der Weg über SQL unsere bevorzugter Weg zum Präsentieren der Daten werden

Die jeweiligen Methoden zum beschaffen der Daten als auch die Ergebnisklassen, sind für SQL als auch für Rest vollkommen unterschiedlich. Sie haben ihre Gemeinsamkeit in den Interfaces. Die Interfaces finden Anwendung im WebSite Projekt.

Das Rest Projekt kann nicht abgeschafft werden, denn für die schreibenden Aktionen wollen wir die offiziellen Schnittstellen weiterhin benutzen.

Nehmen wir die implizite Implementierung des Interfaces, würden wir einen Rework in den Methoden benötigen, denn innerhalb dieser wurde mit den Klassen gearbeitet.

Die explizite Implementierung würde einen Cast erfordern. Die Ergebnisklassen können bis zu 10000 Elemente lang sein

Daher die grundsätzliche Frage; Welcher Weg ist zu bevorzugen und wieso?

Beispiel Code:



C#:
public interface IMyResult
    {
        string MyProperty { get; set; }
    }

    public interface IResultListCollection
    {
        ICollection<IMyResult> Results { get; }
        void DoWork();

    }

    public class Result1 : IMyResult
    {
        public string MyProperty { get; set; }

    }

    public class Result2 : IMyResult
    {
        public string MyProperty { get; set; }

    }

    //Implementation with explicit Interface
    public class ResultList1_Solution1 : IResultListCollection
    {
        public List<Result1> Results { get; set;}
        ICollection<IMyResult> IResultListCollection.Results => Results.Cast<IMyResult>().ToList();

        public void DoWork()
        {
            Results = new List<Result1>();
            for (int i = 0; i < 100; i++)
            {
                Result1 result = new Result1();
                result.MyProperty = $"test {i}";
                Results.Add(result);
            }
        }
    }

    //Implementation with explicit Interface
    public class ResultList2_Solution1 : IResultListCollection
    {
        public List<Result2> Results { get; set; }
        ICollection<IMyResult> IResultListCollection.Results => Results.Cast<IMyResult>().ToList();

        public void DoWork()
        {
            Results = new List<Result2>();
            for (int i = 0; i < 100; i++)
            {
                Result2 result = new Result2();
                result.MyProperty = $"test {i}";
                Results.Add(result);
            }
        }
    }


    //Implementation with implicit Interface
    public class ResultList1_Solution2 : IResultListCollection
    {
        public ICollection<IMyResult> Results { get; set; }

        public void DoWork()
        {
            Results = new List<IMyResult>();
            for (int i = 0; i < 100; i++)
            {
                Result1 result = new Result1();
                result.MyProperty = $"test {i}";
                Results.Add(result);
            }
        }
    }

    //Implementation with implicit Interface
    public class ResultList2_Solution2 : IResultListCollection
    {
        public ICollection<IMyResult> Results { get; set; }

        public void DoWork()
        {
            Results = new List<IMyResult>();
            for (int i = 0; i < 100; i++)
            {
                Result2 result = new Result2();
                result.MyProperty = $"test {i}";
                Results.Add(result);
            }
        }
    }
 
Hey Magic 1416,

ich persönlich würde die Verwendung von impliziten Typen nutzen (am besten ohne Interfaces sondern mit Basisklassen), da dies besser lesbar und nutzbar ist und der explizite Cast, wie du schon erwähnst, Rechenleistung kostet. Deiner Ausführung nach ist dir, aufgrund des Code Aufbaus, jedoch die explizite Variante lieber - wäre da nicht der zusätzliche Rechenaufwand?

10000 Elemente sind zwar schon ein paar, halte ich aber nicht für kritisch. Hast du mal gebenchmarked, wie lange die Ausführung dauert und ob der RAM Overhead für euch kritisch ist?
 
Wenn man schon mit Interfaces arbeitet, würde ich wohl meine Interfaces selbst generisch definieren, also speziell IResultListCollection.

Code:
public interface IResultListCollection3<T> where T: IMyResult
{
    ICollection<T> Results { get; }
    void DoWork();

}

public class ResultList1_Solution3 : IResultListCollection3<Result1>
{
    public ICollection<Result1> Results { get; set;}
    
    public void DoWork()
    {
        Results = new List<Result1>();
        for (int i = 0; i < 100; i++)
        {
            Result1 result = new Result1();
            result.MyProperty = $"test {i}";
            Results.Add(result);
        }
    }
}

public class ResultList2_Solution3 : IResultListCollection3<Result2>
{
    public ICollection<Result2> Results { get; set; }

    public void DoWork()
    {
        Results = new List<Result2>();
        for (int i = 0; i < 100; i++)
        {
            Result2 result = new Result2();
            result.MyProperty = $"test {i}";
            Results.Add(result);
        }
    }

}

So in die Richtung z.B., und natürlich alle notwendigen Stellen entsprechend anpassen.
 
Zuletzt bearbeitet:
Definitiv die implizite Variante. Auch wenn ich mir nicht vorstellen kann, dass der Cast bei 10.000 Elementen mehrere Sekunden dauert, ist er unnötig.
Nutze lieber die Polymorphie. Es liest sich viel einfacher.

Hast du die REST-Variante mal durch debuggt, wo die Zeit verloren geht? Das hört sich für mich eher an, als würden die Webrequests das ganze verlangsamen.
 
SomeDifferent schrieb:
Hast du die REST-Variante mal durch debuggt, wo die Zeit verloren geht? Das hört sich für mich eher an, als würden die Webrequests das ganze verlangsamen.

Ich habe mich verschrieben. Es ist SOAP, nicht Rest

Und ja, die Methode habe ich schon hunderte mal debuggt und optimiert. Diese API ist nicht dafür ausgelegt, Massenauswertungen mit allen Detailtiefen durchzuführen.
Will man viele Daten abfragen, muss man als Parameter einen LDAP Query mitgeben. Dann bekommt man allerdings nur die erste Ebene des Objekts geladen. Die Details darf man sich dann extra nachladen. Damit ist es aber noch nicht getan. Die Abhängigkeiten sind nämlich auch da nicht dabei. Hier gibts auch wieder nen extra Call. Und weil alles so schön ist, gibt es für verschiedene Objekttypen auch noch unterschiedliche Calls, um Details nachzuladen.
Die Methode die wir haben, lädt viele Ebenen seperat und fügt die Daten am Ende mittels Lookup zusammen. Die erste Fassung der Methode brauchte noch ca. 20 Sekunden um alles aufzubauen. Nun sind es 5.

Deswegen haben wir uns dann dem SQL gewidmet. Zuerst mit Entity Framework. Das brauchte für die Daten dann nur noch 3 Sekunden. Leider passt das generierte Objektmodell überhaupt nicht zu dem, was SOAP generiert hat.
Der optimierte SQL Query mit manuell erstellten Mapping braucht hingegen nur noch ca. 100 ms

Zu den Interfaces:
Der Soap Service generierte uns automatisch Klassen und Funktionen. Eine Datei mit über 16000 Zeilen an Code.
Die für uns wichtigen Klassen haben wir über "partial" in einem extra File, erweitert und mit Interfaces versehen.
Bei der Entity Framework Sache können wir die Interfaces nicht benutzen, denn sie passen nicht zur Objektstruktur die EF aufbaut.
Der optimierte Query mit manuellem Mapping erstellt hingegen Objekte, die zum Interface passen.

Auf der Webseite brauchen wir Dank der Interfaces einfach nur die Funktion zum Laden der Daten austauschen.

Um schreibende Vorgänge auszuführen, müssen die Objekte ebenfalls dem Interface entsprechen, denn diese finden aus Konsistenzgründen immer über die SOAP Api statt.
 
Zurück
Oben