Hallo,
hier ein Beispiel, wie ich DI verstanden habe.
class Program
{
private static void Main(string[] args)
{
var test = new List<string> { "A", "B", "C" };
var dosth = new DoSomething(test);//Dependency Injection
var input = dosth.Sort();
Console.WriteLine(input);
}
}
class DoSomething
{
IList<string> list; //Vermeidet Verwendungsabhängigkeit
public DoSomething(IList<string> arglist) //Vermeidet Erzeugungsabhängigkeit
{
list = arglist;
}
public string Sort()
{
return list[1];
}
}
Ausgabe: "B"
Es soll ja vermieden werden, dass die Klasse Objekte selbst erzeugt, sondern stattdessen diese vom aufrufenden Programm injiziert bekommt, falls sich das injizierte Objekt ändern sollte.
Man verwendet daher die Interfaces zur Übergabe.
Allerdings ist unklar, ob das z.B. im Falle von List oder anderen von der c# Bibliotheken bereitgestellten Klassen Sinn macht, denn deren funktionale Implementierung wird sich schon aus Gründen der Kompatibilität wohl nicht verändern, oder ?
Bei selbst erstellten Klassen kann ich mir das schon eher vorstellen.
Kurzes Feedback wäre nett (auch zu meinem Code 😉).
Bei DI geht es gewiss darum, dass sich die konsumierenden Klassen nicht um die Erzeugnung der Objekte kümmern müssen.
Dies ist ein wichtiger Aspekt um die Klasse selbst nicht mit konkreten Details zu belasten, was auch nicht die Zuständigkeit einer Klasse sein sollte und u.U. nicht mal machbar wäre.
Dein Code ist hier etwas zu einfach gehalten, zeigt aber den Kerngedanken von DI vereinfacht.
Hier ist es immer ratsam, gilt auch ohne DI, dass man gegen die Interfaces arbeiten sollte.
Dadurch vermeidet man eine strafe Kopplung zu konkreten Implementierungen.
Dies kann zwar u.U. gewünscht sein, sollte aber i.d.R. vermieden werden.
Gerade wenn man dann eine andere Implementierung braucht, müsste man den Code klonen und umschreiben, was unnötig und unsinnig ist.
Wenn du dich mit der Doku und .NET 7+ befasst, kommt man um das Thema nicht mal mehr drum herum.
Doku:
https://learn.microsoft.com/de-de/dotnet/core/extensions/dependency-injection
Ich bin gerade selbst dabei einige ältere aber wichtige ASP .NET Projekte die noch auf SOAP und WCF basierte Dienste anbieten nach .NET 7 und Docker zu migrieren.
Dort kommt man um DI nicht mal drum herum, was sogar sehr positiv ist.
Dies nimmt mir viel Arbeit ab, da ich die Erstellung von bestimmten Schlüsselobjekten an Microsofts Extension für DI verlagere.
Dadurch muss ich nur vorgeben wie die Objekte der jeweiligen Klassen erstellt werden (Singleton, Transient, Scoped).
Somit kann man auch sehr genau steuern ob es eben je nach Situation nur ein Objekt (Singleton), pro Request (Scoped) oder sogar pro benötigte Instanz (Transient) ein Objekt gibt.
Dadurch gibt man die Kontrolle zur Erzeugung der jeweiligen Instanzen auch aus der Hand bzw. verlagert diese aus den jeweiligen Klassen.
Das Thema kann bestimmt Abt und co. besser beschreiben 😃
Die sind da tiefer drin als ich.
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.
Ja, es geht darum, dass sich nicht die konsumierenden Klasse um die Aufgaben kümmern müssen, sondern diese Verantwortichkeiten in der jeweiligen Klasse bleibt. Bin aber nicht so der Erklärbär 😃
Habe mal in Anlehnung an deinem Beispiel was gebastelt.
public interface IDoSomething
{
List<string> List { get; set; }
string Sort();
}
public class DoSomething : IDoSomething
{
public List<string> List { get; set; }
public DoSomething()
{
List = new List<string>();
}
public string Sort()
{
return List.ElementAt(1);
}
}
public class DoSomethingElse : IDoSomething
{
public List<string> List { get; set; }
public DoSomethingElse()
{
List = new List<string>();
}
public string Sort()
{
return List.ElementAt(0);
}
}
public class Worker
{
private IDoSomething doSomething;
public List<string> List
{
get { return doSomething.List; }
set { doSomething.List = value; }
}
public Worker(IDoSomething doSomething)
{
this.doSomething = doSomething;
}
public void Add(string item)
{
doSomething.List.Add(item);
}
public string Execute()
{
return doSomething.Sort();
}
}
internal class Program
{
static void Main(string[] args)
{
Worker worker1 = new(new DoSomething());
Worker worker2 = new(new DoSomethingElse());
for (int i = 0; i < 10; i++)
{
worker1.Add($"item {i:D2}");
worker2.Add($"item {i:D2}");
}
Console.WriteLine($"Worker1: Sort => {worker1.Execute()}");
Console.WriteLine($"Worker2: Sort => {worker2.Execute()}");
Console.ReadKey();
}
}
Nicht alles, was ein Interface ist, ist DI. Interfaces sind auch nicht pauschal immer der beste Weg.
DI hat was mit "logischen Abhängigkeiten" zutun, die notwendig sind, um einen Use Case auszuführen; IList gehört aber eher zur Kategorie Datenstruktur.
Man verwendet bei solchen Datenstruktur i.d.R. die Implementierung - ausser man will bewusst ein Interface aufgrund von Kompatibilität annehmen/anbieten.
Besser ist es sich an die Grundregel zu halten:
Abstraktion nur so weit wie nötig, konkreter Typ so weit wie möglich.
Hier also ein IList zu nehmen, obwohl man als Typ List hat und auch nur dessen Funktionalität nutzt, macht also keinen Sinn ⇒ konkreter Typ besser Weg.
Bei DI brauchst Du zwar technisch kein Interface, aber konzeptionell schon. Daher ist bei einer logischen Abhängigkeit das Interface die Basis bei DI.
Daher nein: Dein Code hat so in der Form nichts im eigentlichen Sinne zutun (der Code macht in Summe (List im Konstruktor etc.. so einfach wenig sinn).
Ja, man kann hier zwar von Injection der Liste sprechen - aber das hier ist schon eine sehr konstruierte, fiktive Darstellung, die mit der realen Welt eher nix zutun hat.
Edit: Cavemans Beispiel is da deutlich besser.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Besten Dank, das hilft 😉
Zitat von Caveman
Ja, es geht darum, dass sich nicht die konsumierenden Klasse um die Aufgaben kümmern müssen, sondern diese Verantwortichkeiten in der jeweiligen Klasse bleibt. Bin aber nicht so der Erklärbär 😃
Habe mal in Anlehnung an deinem Beispiel was gebastelt.
Schönes und einfaches Beispiel – doch ein Erklärbär 😉
Danke!
René
René