Laden...

Generische Methoden - wofür, wann, warum?

Erstellt von pommesgabel vor 13 Jahren Letzter Beitrag vor 13 Jahren 6.454 Views
P
pommesgabel Themenstarter:in
3 Beiträge seit 2010
vor 13 Jahren
Generische Methoden - wofür, wann, warum?

Sehr geehrte Damen und Herren 😜 ,
ich arbeite mich seit einiger Zeit in kleinen Stücken in die Grundlagen von C# ein und bin jetzt auf sog. generische Methoden gestoßen.

Das ganze generische System leuchtet mir bis jetzt nur für Array-artige Klassen ein, und um Objekte von Klassen verwalten zu können, deren Klasse eben noch nicht bekannt ist (wann tritt sowas auf? 🤔 ). Gut, ich hab' da ne Auflistung, und innerhalb dieser Auflistung möchte ich nur Objekte einer Klasse (oder verwandter Klassen) haben, und damit ich DAS garantiert habe, aber nicht für jede Klasse eine extra Auflistungsklasse anlegen möchte, verwende ich einfach generische Klassen als Auflistungen o.ä.

Warum ich in diesem Online-Buch jetzt aber gelehrt bekomme:

Class GenericClass<T> {
public void GenericMethod<K>(K obj) { ... }
}
  • leuchtet mir nicht ein. Unten steht:
[CSHARP]obj.GenericMethod<int>(25);[/CSHARP]
Sie können sogar auf die Typangabe verzichten, denn auch in diesem Fall wird der C#-Compiler die richtige Schlussfolgerung ziehen:
[CSHARP]obj.GenericMethod(25);[/CSHARP]

...hä?
Warum verwende ich dann nicht gleich einfach

public void GenericMethod(Object obj)

? Was macht das für einen Unterschied? Und wann setze ich was am besten ein?
Ich hoffe, dass das keine ultimativ blöde Frage ist, weil ich grade ziemlich aufm Schlauch stehe.

MfG
pommesgabel

2.891 Beiträge seit 2004
vor 13 Jahren

Nimm als Beispiel mal die generische Klasse List<T>. In Listen kann man (u.A.) was einfügen und auf Elemente zugreifen. Die Implementierung dieser Logik ist immer gleich, welchen Typ die Elemente haben, ist dabei an sich egal.

So kannst du eine List<int> erstellen, die dir die Methode Add(int value) anbietet, mit der du Zahlen hinzufügen kannst, und mit list_ bekommst du ein int zurück. Ebenso mit List<string>: Die hat die Methode Add(string value), mit der du Zeichenketten hinzufügen kannst, mit list_[/tt] erhältst du einen string `zurück.

Angenommen, du hast jetzt eine ungenerische Liste. Die bietet die Methode Add(object value). Da kannst du alles mögliche reinwerfen. Du kannst also gleichzeitig Zahlen und Zeichenketten in der gleichen Liste haben. Ebenso musst du beim Lesen aus der Liste (gibt dir ja object zurück) dauernd casten, zudem musst du dabei aufpassen, da ja Zahlen und Zeichenketten in der Liste sein können.

Wenn du nun aber eine Liste haben möchtest, die nur Zahlen aufnimmt, musst du die extra Implementieren. Willst du eine Liste nur für Zeichenketten, musst du dafür eine extra Liste implementieren. Nur Typ X, neue Implementierung, nur Typ Y, neue Implementierung, ...

Damit du Typsicherheit hast, aber nicht jedesmal neu implementieren musst, wird dir mittels Generika eine Möglichkeit gegeben, quasi mit Platzhaltern für konkrete Typen zu programmieren.

Siehe auch Generika (C#-Programmierhandbuch)

Gruß,
dN!3L

R
68 Beiträge seit 2010
vor 13 Jahren

Warum verwende ich dann nicht gleich einfach

  
public void GenericMethod(Object obj)  

? Was macht das für einen
Unterschied? Und wann setze ich was am besten ein?
Ich hoffe, dass das keine ultimativ blöde Frage ist, weil ich grade ziemlich aufm
Schlauch stehe.

Wenn Du nur mit Objekten arbeiten würdest, müsstest Du ziemlich viel
mit Casting und Boxing arbeiten, zudem prüft der Compiler bei den generischen
Typen schon beim Compilieren auf Typsicherheit. Ich meine auch, dass das
Laufzeitverhalten bei der Objekt-Lösung stark nachteilig ist.

Wann immer möglich, setze Generics anstatt einfache Objekteverweise
mit Boxing/Casting ein.

Hier wird es auch noch mal ganz gut anhand von Beispielen erklärt:
http://www.dotnetjohn.com/articles.aspx?articleid=250

Robin

1.044 Beiträge seit 2008
vor 13 Jahren

Hallo pommesgabel,

nehmen wir zum Verständnis die Stack- sowie die Stack<T>-Klasse. Beide Klassen machen im Grunde das selbe, nur die eine Klasse ist generisch und die andere nicht. Bei der nicht-generischen Klasse musst du darauf achten, dass bei der Pop-Methode auch wirklich das selbe zurückkommt, was du auch erwarten willst. Dort arbeitet man mit Objekten und Objekte sind alles. Bei der generischen Implementierung der Stack-Klasse verwendet man direkt auch die generischen Typen. So wird dann sichergestellt, dass auch das zurückkommt, was du auch haben möchtest. Das stellt sicher, dass keine Fehler beim Casten oder ähnliches auftreten. Man kann so sagen, dass Stack<T>-Klasse sicherer als die Stack-Klasse ist.

zero_x

P
pommesgabel Themenstarter:in
3 Beiträge seit 2010
vor 13 Jahren

Ihr seid ja wunderbar schnell, ist man kurz essen und wird sofort mit Antworten überhäuft. Ich les mir das mal eben durch.
Danke! 🙂

// Edit.
Also, was ich gelesen hab, hab ich meines Wissens schon verstanden - oder ich hab was unterschwelliges überlesen, wo die Antwort auf mein Unwissen enthalten ist...

z.B. das mit der Stack<>-Klasse hab ich verstanden. Da ergibt das für mich Sinn.
Aber wenn ich wie gesagt diese Klassen- und Methodendefinition hab:

Class GenericClass<T> {
public void GenericMethod<K>(K obj) { ... }
}

Dann kann ich ja beim Aufruf von GenericMethod eigentlich alles angeben.
GenericMethod<Flugzeug>(Boeing), GenericMethod<Ziegenbock>(Meckermann), ...
Wo ist da die durch die Generics gewollte Sicherheit? o_O

5.742 Beiträge seit 2007
vor 13 Jahren

Hallo pommesgabel,

herzlich willkommen hier auf myCSharp.de!

Was macht das für einen Unterschied? Und wann setze ich was am besten ein?

Naja - ich gebe dir recht: Das Beispiel aus dem Buch scheint derart abstrakt zu sein, dass es auch nicht wirklich sinnvoll ist.

Einen wichtigen Einsatzort hast du ja schon genannt:
Listen (wie List<T>, Dictionary<TKey, TValue> usw.).

Generische Methoden mache vor allem Sinn, wenn man auch etwas zurückgeben will. Ein Beispiel sind die ganzen LINQ-spezifischen Methoden (Where, ...).
Würde man diese einfach nicht-generisch machen (und dann ein IEnumerable oder gar ein object zurück geben lassen), wäre die ganze Typsicherheit verloren.
Dadurch, dass sie generisch sind, spart man sich so einiges an Casts und kann schreiben:


List<Customer> customers = /*...*/;
Customer c = EnumerableExtensions.First(customers);

(ich habe das extra mal so formuliert um jetzt nicht gleich mit Lambdas und Extensionmethods zu verwirren).

Wäre EnumerableExtensions.First nicht generisch:


public static object First(IEnumerable items) //...

müsste man ein Object zurückliefern - nicht sehr praktisch:


Customer c = (Customer) EnumerableExtensions.First(customers);

Aber so:


public static T First<T>(IEnumerable<T> items) //...

erhält man direkt einen Customer (oder was man auch immer übergibt) zurück.

Weiteres Beispiel:
Statt eine Methode


public object Create(Type type) //...

zu schreiben (kommt bei DI-Containern zum Einsatz) kann man mithilfe von Generics schreiben:


public T Create<T>() //...

Aber wie gesagt: Ohne Rückgabewert machen generische Methoden wirklich nur sehr selten Sinn.

Ich denke, dass es dir vorerst reichen wird, zu wissen, dass e so etwas wie generische Methoden gibt.
Mit der Zeit wirst du deren praktischen Nutzen sicherlich zu schätzen lernen.

[Edit]Ok - ein wenig zu spät, aber ich hoffe, es hilft trotzdem 😉[/Edit]

P
pommesgabel Themenstarter:in
3 Beiträge seit 2010
vor 13 Jahren

Ah! Mit dem Rückgabetyp ergibt das verdammt viel Sinn auf einmal.
Danke, hab' ich bis da hin kapiert, warum man das in dem Fall braucht. Aber im Beispiel ist die Methode eben ne void-Methode, von daher fand ich's irgendwie sinnfrei...
Danke :]

5.299 Beiträge seit 2008
vor 13 Jahren

generische Methoden sind afaik rel. selten, viel häufiger sind generische Klassen. Die Auflistungen wurden ja schon genannt.
Und soo häufig implementiert werden sie auch nicht, eben weil sie so flexibel sind, also dieselbe Klasse kann enorm vielfältig eingesetzt werden.
zb Aus Framework 1 gibts noch hunderte verschiedener Auflistungen: ControlCollection, ListviewItemCollection, BindingsCollection, StringCollection,...
generisch hätte für ein gut Teil von dem Kram einfach eine List<T> gereicht, also List<Control>, List<Binding>, List<ListviewItem>, List<String>,....

Bes. interessant finde ich generische Delegaten. ZB Eventhandler sind Delegaten, und davon gibts im FW1 wohl tausende: ShutDownEventHandler, AddingNewEventHandler, DesignerEventHandler, DoWorkEventHandler, ItemCheckEventHandler,...

Die wären alle komplett ersetzbar durch den einzigen


System.EventHandler<T>(object sender, T e)where T:System.EventArgs

(nämlich EventHandler<ShutDownEventArgs>, EventHandler<AddingNewEventArgs>, EventHandler<DesignerEventArgs>, ...)

Interessant wären auch generische Interfaces.
ZB. die ICloneable-Schnittstelle ist m.E. aus heutiger Sicht vermurkst:


public interface ICloneable{
object Clone();
}

Das definiert, dass beim clonen immer Typ Object zurückgegeben wird, (und wers verwendet, muß immer auf den richtigen Typ zurück-casten)

Heute würde man glaub machen:


public interface ICloneable<T>{
T Clone();
}

Das definiert, dass der Clon gleich richtig typisiert zurückgegeben wird.

Ich hab zB. mir eine generische CloneX-Extension-Methode gebastelt:


      public static T CloneX<T>(this T Obj) where T : ICloneable {
         return (T)Obj.Clone();
      }

Die nimmt mir beim Clonen die Casterei ab.

zu guter letzt: vlt. ist dein buch nicht wirklich gut, welches isses denn?

Der frühe Apfel fängt den Wurm.

1.044 Beiträge seit 2008
vor 13 Jahren

Hallo ErfinderDesRades,

Bücher für Anfänger richten sich in erster Linie auf das Sprachkonzept von C#. Wie man allerdings etwas programmiert, wird sicherlich später erläutert. Wahrscheinlich wurde dort einfach nur beschrieben, wie generische Typen funktionieren. Es ist ja schließlich erstmal gut zu wissen, wie es funktioniert, bevor man sich mit der eigentlichen Programmierung widmet.

Bezüglich deines Codebeispiels ist es empfehlenswert auf Ko- und Kontravarianz hinzuweisen sowie die Erneuerungen in .NET 4.

zero_x

5.299 Beiträge seit 2008
vor 13 Jahren

Bücher für Anfänger richten sich in erster Linie auf das Sprachkonzept von C#. Wie man allerdings etwas programmiert, wird sicherlich später erläutert. Wahrscheinlich wurde dort einfach nur beschrieben, wie generische Typen funktionieren. Es ist ja schließlich erstmal gut zu wissen, wie es funktioniert, bevor man sich mit der eigentlichen Programmierung widmet.

Hmm.
Ich finde, ein Buch sollte allen Stoff an nachvollziehbaren Beispielen zeigen. Ich glaub, das ist fast immer möglich. Evtl. sogar dasselbe Beispiel von Kapitel zu Kapitel immer weiter ausdifferenzieren.
Dann kommt auch sowas unfug-verdächtiges (eine generische Klasse<T> mit einer noch generischeren Methode<K> ) gar nicht erst auf.
Unfug-verdächtig ist das nämlich, weil die Klassen-Methoden üblicherweise das in der Klassendefinition angegebene <T> verwenden, und nicht noch zusätzliche TypParameter deklarieren. (was sollte eine Klasse List<string> mit einer Methode Add<Point> ?)

Bezüglich deines Codebeispiels ist es empfehlenswert auf Ko- und Kontravarianz hinzuweisen sowie die Erneuerungen in .NET 4. K.A., was du meinst. Meine CloneX-Methode ist .Net 3, kommt also ohne Varianzen zurecht.
Was aber fehlt, ist wohl ein Hinweis auf das .Net 3-Konzept der Extension-Methods .

Damit simmer auch schon in der Nähe der Doku - was ich mir nämlich noch vonnem guten Buch wünschen täte, wäre ein gründliches Einlernen in die engere und weitere Programmierumgebung. Das wäre zwar auf VisualStudio eingeschränkt, würde aber den überwiegenden Teil der Leser sehr gut bedienen.
@pommesgabel zum thema Doku: [Tipp] Schau in die Doku! - Möglichkeiten der Informationsgewinnung

Achso, vlt. meintest du System.EventHandler<T>. Der ist aber nicht von mir, sondern ist Bestandteil des Framework 2. Und MSDN empfiehlt auch, keine weiteren spezifischen EventHandler mehr zu deklarieren, sondern diesen gegebenen zu benutzen.

Der frühe Apfel fängt den Wurm.

U
282 Beiträge seit 2008
vor 13 Jahren

Generische Methoden können auch sinn machen, wenn der Typ-Parameter Eingeschränkt ist. Gerade bei zwei oder mehr Constraints lässt sich das sonst nicht wirklich formulieren.

5.299 Beiträge seit 2008
vor 13 Jahren

Jo, ganz wesentlich. Die Einschränkung definiert nach außen, was der Typ T können muß, und nach innen, was er kann.
Anhand der Einschränkung zB. ist definiert, dass mein CloneX nur mit ICloneable Objekten funktioniert, und nur deshalb kann ich "innen" subj.Clone() aufrufen, ohne zu casten.
Insofern ist die Abwesenheit einer Einschränkung bei generische Auflistungen eine Ausnahme, was aber logisch ist, denn zum Hineintun oder Entfernen muß man zunächst mal nichts übers Objekt wissen.

Der frühe Apfel fängt den Wurm.