C# warum braucht es Interfaces?

4 Antworten

Vom Beitragsersteller als hilfreich ausgezeichnet

Ich habe in meinem aktuellen Projekt Klassen mit verschiedenen Sprachen. Also alles so gesehen dasselbe, nur mit verschiedenen String Inhalten. Diese Klassen müssen alle gleich sein. Sie müssen nach einem bestimmten Bauplan gebaut sein. Hier nutze ich Interfaces. Das Interface gibt der Klasse vor, was diese implementieren MUSS und somit ist in allen Klassen einheitlich 100% derselbe Inhalt drin mit nur anderen Werten, wodurch es wunderbar miteinander kompatibel ist. Wenn ich eine andere Sprache hinzufügen will, muss ich wieder nur vom Interface erben.

Auch gut sind Klassen für andere Entwickler, wenn die Plugins für dein Programm entwickeln. Dein Programm muss dann ja irgendwas ansprechen. Damit das übereinstimmt, können die von deinem Interface erben.

Du musst es als Bauplan betrachten


Tyldu  15.05.2023, 21:42

Warum für jede Sprache direkt eine eigene Klasse? Ich kenn dein Projekt nicht aber das hört sich eher weniger nach der optimalen Lösung an... Wäre eine Art Translation-Service an der Stelle nicht sinnvoller?

0
FaTech  15.05.2023, 23:22
@Tyldu

In wiefern "Translation" Service? Google Übersetzer API? Das kostet nicht nur Geld, soweit ich weiß, die Übersetzung ist auch nicht gut

0
FaTech  16.05.2023, 16:08
@Tyldu

Es hat Gründe, warum ich mich gegen Ressourcen entschieden habe. Ich weiß nicht mehr warum, aber es gab da einen Nachteil der mir entgegen kam, den ich vermeiden wollte. Was genau es war, weiß ich nicht mehr. Aber das kann dir ja auch egal sein. Es geht ja um ein Interface Beispiel und da habe ich dir mehrere genannt

0
Tyldu  16.05.2023, 16:27
@FaTech

natürlich kanns mir egal sein, will nur helfen deinen code evtl etwas sauberer zu machen....

du musst ja auch nicht die standart implementation vom stringlocalizer verwenden sondern kannst auch deine eigene schreiben die ihre daten dann zb aus jsons oder ner datenbank zieht

ist halt ne menge duplicate code wenn die klassen alle gleich sind und sich dann nur die strings jeweils unterscheiden

0
FaTech  16.05.2023, 16:36
@Tyldu

Nicht unbedingt anders, als wenn ich eine Ressource hätte, vor allem, da es über ein Interface läuft. Danke für das Interface, aber ich setze oftmals nicht auf typische .Net Standards, weil ich auf Performance setze. Ich habe schon viele Codes entwickelt, die am Ende schneller waren, als die Standards. Ich bin keine Fan von langsam. .Net passt sich nach und nach an, ist aber leider an vielen Stellen langsam. Ich weiß schon was ich tue, keine sorge

0
Tyldu  16.05.2023, 16:45
@FaTech

Naja, IoC ist bei dir nicht mehr wirklich gegeben und wie gesagt führt es zu ziemlich viel duplicate code (vorallem wenn du in deinen klassen dann noch zusätliche logik zu der localization brauchst, die du dann in jeder klasse neu implementieren musst)

DI ist ja an sich nicht .NET sondern einfach branchenstandart für sauberen code, egal ob jetzt was im .NET umfeld, java oder sonstwas. Die mitgeschickte seite war eher als beispiel gedacht, es hindert dich nichts daran dein eigenes DI-Framework zu schreiben das dann auf performance ausgelegt ist.

aber gut, musst du selber wissen, ich hab deinen code ja auch nicht gesehen bzw weiß ich ja nichtmal um was für ein projekt es sich genau handelt.

0
FaTech  16.05.2023, 16:53
@Tyldu

Muss ich das? In den Klassen sind lediglich props, das war's. Ist nicht anders, als hätte ich mehrere sprach Ressourcen dafür...

Jeglicher anderer Code ist ausgelagert, wie es sich gehört. Bin doch nicht bescheuert.

Es handelt sich um ein blazor WASM Projekt auf Basis von Schnelligkeit und hoher Sicherheit. Aber wie schon gesagt, ich weiß was ich tue. Ich habe alles ins tiefste durchdacht, bevor ich damit angefangen habe und wenn ich ermittelt habe, dass meine Variante sich besser lohnt, dann greife ich natürlich auch darauf zurück. Nur weil andere von der Brücke springen, muss ich es nicht auch tun.

Außerdem hat es nichts mit der Frage zu tun, also gehört es nicht hier her

0
DrClean 
Beitragsersteller
 16.05.2023, 22:13

Das hilft mir - vielen Dank. - interessant auch, wie die Frage eine Diskussion ausgelöst hat. Sehr interessant und ich hoffe ihr konntet euch so helfen. coole Sache!

0

Interface = Schnittstelle

Die Analogie zu einem "Vertrag" ist in der Dokumentation etwas ungünstig gewählt.

Eine Schnittstelle stellt lediglich die "Bedienelemente" für etwas bereit was dem Nutzer verborgen in der "Kiste" steckt. Damit muss sich der Nutzer nicht mehr interessieren was in der Kiste ist.

Nehmen wir ein Klavier...

Du hast deine Tasten , welche beim drücken einen Ton auslösen. Du musst nicht wissen ob in dem Klavier ein Holzrahmen oder einer aus Bronze steckt, oder Elektronik und Lautsprecher. Der Klavierbauer stellt Dir (dem Pianisten), mit den standardisierten Tasten eine Schnittstelle zur Verfügung , um dem "Inneren" die richtigen Töne zu entlocken.

Genauso ist es mit Deinem C#-Objekt . Du definierst Namen, welche später ein Du oder ein anderer Programmiere benutzt auf eine Funktion/Eigenschaft zuzugreifen. Was sich hinter diesem "namen" verbirgt ist total Wurst, Hauptsache es macht was es soll.

class Klavier
{
  //du musst niemanden erzählen wie Du auf  die Frequenzen kommst und wie diese im Speicher angordnet sind
  private int Ton_A4 = 440;
  private int Ton_C4 = 261;
  private int Ton_E4 = 329;
  //alle anderen Grundtöne schön durcheinander
  //eine Oktave höher  
  private int Ton_A5 = Ton_A4*2;
  private int Ton_E5 = 659;
  // usw.
  private void soundcard (int Freqenz){
     //irgendwelche Anweisungen für den Treiber oder  was auch immer
  }
     
   //jetzt die Schnittstelle, welche man zur Bedienung wirklich kennen muss
   public void Play(string Ton) {
        switch(Ton) {
          case "A4":  soundcard(Ton_A4);
          case "C6":  soundcard(Ton_C6);
          case "F1":  soundcard(Ton_F1);
          // alles andere
        }
    }

nach dem compilieren ist davon nichts mehr interessant. Du schreibst in die Dokumentation lediglich noch:

Aufruf: Play(string Ton);
der zu spielende Ton ist ein String aus der Bezeichnung des Tons und der Oktave zB. C4 , Fis3, G1...

...mehr muss ein Nutzer Deiner Klasse eigentlich nicht wissen.

der schreibt dann lediglich in sein Programm:

    static void Main()
    {
        Klavier blubb = new Klavier();
        blubb.play("A4");
        blubb.play("Gis2");
    }

Was wirklich hinter den Kulissen Deines Klavier-Objekts passiert ist für den Nutzer völlig irrelevant, bei einem Update könntest Du die Frequenzen auch live über die 12.Wurzel aus 2 und deren vielfache berechen. Dem Nutzer kann es egal sein, der kennt lediglich die Methode Play() ... (es sei denn ihm läge der Quellcode des Objekts vor)

Ein vertrag im weitesten Sinne besteht lediglich darin, das die Methode Play tut was sie soll.

Würde irgendein "Freak" die Felder obigen Objekts auf Grund der Vermuteten Integer und Zeiger (an Interface vorbei) ansprechen , hätt er er Quasi nicht die Gewisheit, das wirklich alles dort ist wo er es erwartet.

Ich bin einer von denen welcher gern mal mit PInvoke arbeitet . Dabei ist natürlich nicht 100% sicher, das auch alles wirklich dort ist wo man es erwartet. Bei Pinvoke hat man lediglich nackte Bytes in welche man irgendwie Ordnung bringt indem man selbst ein Interface definiert.

kann sehr abenteuerlich aussehen: (es gibt kein offizielles Interface für die programmgesteuerte Bedienung des Audiomixers!!!! also muss man selbst eine definieren)

using System.Runtime.InteropServices;

[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAudioEndpointVolume {
  void _VtblGap1_4(); // 4 unbekannte Methoden einsproungpunkte
  int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext);
  void _VtblGap2_1();
  int GetMasterVolumeLevelScalar(out float pfLevel);
  void _VtblGap2_4();
  int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext);
  int GetMute(out bool pbMute);
}

[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDevice {
  int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev);
}

[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDeviceEnumerator {
  int f(); // Unused
  int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
}


[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
public class Audio {
  static IAudioEndpointVolume Vol() {
    var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
    IMMDevice dev = null;
    Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(/*eRender*/ 0, /*eMultimedia*/ 1, out dev));
    IAudioEndpointVolume epv = null;
    var epvid = typeof(IAudioEndpointVolume).GUID;
    Marshal.ThrowExceptionForHR(dev.Activate(ref epvid, /*CLSCTX_ALL*/ 23, 0, out epv));
    return epv;
  }
  public static float Volume {
    get {float v = -1; Marshal.ThrowExceptionForHR(Vol().GetMasterVolumeLevelScalar(out v)); return v;}
    set {Marshal.ThrowExceptionForHR(Vol().SetMasterVolumeLevelScalar(value, System.Guid.Empty));}
  }
  public static bool Mute {
    get { bool mute; Marshal.ThrowExceptionForHR(Vol().GetMute(out mute)); return mute; }
    set { Marshal.ThrowExceptionForHR(Vol().SetMute(value, System.Guid.Empty)); }
  }
}

...und wehe dem, das es Microsoft einfällt die Anordnung der Adressen im Com-Objekt für den AudioMixer zu verändern,


DrClean 
Beitragsersteller
 16.05.2023, 22:14

Besten Dank - sehr interessant..

1

Normalerweise benutzt man Interfaces, um zu definieren, dass die Klassen eine bestimmte Funktion unterstützen, obwohl sie ansonsten nicht viel miteinander zu tun haben. Z.B. eins der am Häufigsten benutzen Interfaces dürfte IComparable sein, womit du definieren kannst, dass die Objekte der Klasse untereinander vergleichbar sind und in irgendeiner Weise sortiert werden können, aber ansonsten halt keine Gemeinsamkeiten mit anderen sortierbaren Objekten haben. Wenn du also eine Sortierfunktion baust, brauchst du die nicht für jeden Objekttyp, der sortierbar sein könnte anpassen, sondern kannst einfach auf die CompareTo Funktion zugreifen, die vom IComparable Interface bereitgestellt wird.

In deinem Beispiel ist HumanoideKI keine Subklasse von Mensch, weil eine KI logischerweise kein Mensch ist. Aber die HumanoideKI implementiert das Interface IMensch, weil sie das Gleiche kann, wie ein Mensch. Bzw. sie lässt sich genauso ansprechen, wie ein Mensch.

Eine andere Stelle, an der gerne Interfaces benutzt werden, sind sogenannte Mock-Objekte, die bei Unit-Tests vorkommen. Die benutzt man, wenn man auf manche echte Objekte während des Tests nicht zugreifen kann (oder sollte). Beispiel: Du hast eine Klasse, die irgendwelche Daten in einer Datenbank verändert, willst aber logischerweise nicht bei jedem Test ein Backup der Datenbank laden. Also kannst du ein Interface definieren, was die gleichen Funktionen, wie die Datenbankverbindung unterstützt, nur dass beim Unit Test halt nur irgendwelche temporären Dummy-Daten verändert werden und nicht die echten Daten in der Datenbank. Oder anderes Beispiel: Deine Objekte verschicken automatisch E-Mails bei bestimmten Ereignissen. Die sollen natürlich bei einem Testlauf nicht verschickt werden.

  1. Um zu entkoppeln.
  2. zb. Security Gründe. Man sollte nur das Interface kennen und nicht die Implementierung dahinter. :-)
Woher ich das weiß:Studium / Ausbildung

DrClean 
Beitragsersteller
 16.05.2023, 22:13

Das hilft mir - vielen Dank.

0