Laden...
19 Antworten
4,051 Aufrufe
Letzter Beitrag: vor 16 Jahren
Magic Methods in c#?

In Sprachen wie PHP existieren so genannte Magic Methods oder Interceptor Methods über die es möglich ist während der Laufzeit Methoden hinzuzufügen.

Mittels der __call Methode lässt sich in PHP unter anderem folgendes schöne Ding realisieren:

Ich kapsel verschiedene Objekte ineinander, die zwar den selben Basistyp haben, jedoch nicht immer die selben Methoden. Rufe ich nun eine Methode des äußersten Objekts auf die es nicht besitzt, kann ich in der __call Methode die gekapselten Objekte nach dieser Methode durchsuchen und falls ich sie finde aufrufen.

Denn die __call Methode wird immer ausgeführt, wenn ich sie in der jeweiligen Klasse definiere und ich eine Methode anspreche die nicht existiert.

Gibt es eine Möglichkeit so etwas in C# zu realisieren?

class CallTest {
  	function __call($name, $parameterArray) {
  		echo 'Methodenname: ' . $name;
  		echo '<br>';
  		echo 'Parameter: '; 
  		print_r($parameterArray);	//Arraywerte anzeigen
  		return '<br>Ich wurde automatisch aufgerufen!';
  	}
  }
 
 $CallTest = new CallTest();
 $wert = $CallTest->unbekannt("par1", "par2");	//__call()
 echo $wert;

As a man thinketh in his heart, so he is.

  • Jun Fan
    Es gibt nichts Gutes, außer man tut es.
  • Erich Kästner
    Krawutzi-Kaputzi
  • Kasperl

Hallo der-schlingel,

C# ist streng typisiert. Du kannst nur Methoden aufrufen, die existieren. Das prüft schon der Compiler.

herbivore

Hallo der-schlingel,

Dazu könntest du Vererbung und abstract einsetzen, falls du die Objekte nicht dynamisch ineinander Schachteln musst. (Objekte und Klassen werden - so weit ich weis - in PHP nicht sonderlich unterschieden.)

Wenn die die Objekte dynamisch Schachteln musst, dann solltest du ein etwas aufgebortes Dekorator-Pattern einsetzen.

Gruß
Juy Juka

Andere Fragen, wofür braucht man sowas? Dir als Programmierer muss doch zur Entwicklungszeit klar sein, um was es sich für ein Typ gerade handelt und welche Member dieser Typ hat.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Hallo kleines_eichhoernchen,

bei C# schon. Es gibt aber auch objektorientierte Sprachen, bei denen du das nicht wissen musst, z.B. bei dem Klassiker Smalltalk. Ich denke aber viele Fälle, bei denen man das in Smalltalk einsetzen würde, kann man unter C# mit Interfaces abhandeln.

Hallo der-schlingel,

es bleibt aber in der Tat, die frage was genau du machen willst.

herbivore

alternativ kann man per reflection schauen, welche methoden eine instanz hat und ggf dann diese aufrufen, nur ist das.... suboptimal und wenn du das als normalen ablauf programmierst, ist das ein verstoß gegen die oop

Ja, wie zuvor angesprochen, suche ich nach so etwas ähnlichem wie ein aufgebohrtes Decorater Pattern. Nur leider weiß ich nicht, wie ich das umsetzen kann.

Es geht um folgendes:

Ich möchte eine kleine Lib für 2D-Zeichenoperationen schreiben. Diese Lib soll es vorallem Anfängern erleichtern in die 2D-Graphikprogrammierung reinzukommen. So ähnlich wie Processing für Java nur Objektorientiert.
Sie soll also einige Basis-Zeichenobjekte wie Rectanlge, Ellipse usw. besitzen und dann besondere "Looks" wie z.B. das Objekt drehen, ihm einen Rahmen verpassen usw.

Um das umzusetzen hab ich mir gedacht, es wäre eine gute das mit dem Decorater Pattern zu erledigen. Wodurch die "Looks" die Objekte kapseln und dann beim Zeichendurchgang die ganzen Operationen anwenden.

So z.B. ellipse = new Rotate(ellipse, 90.0f);

Was auch gut funktioniert. Doch sobald man ellipse weiter drehen möchte, artet das Konzept in sehr viel Arbeit aus, da man die gekapselten Objekte nacheinander auf ihren Typ prüfen muss. Ist man dann beim richtigen angekommen, setzt man das Angle Property.

Ich hab zwar schon gelesen, dass sich so etwas über den Umweg von Proxies lösen lässt, doch mit dieser Lösung war ich nicht ganz zufrieden. Da es ziemlich viel Aufwand war.

Edit:
Alternativ bliebe natürlich ein neues Konzept, z.B. die "Looks" mittels des Strategy Pattern und statischen Methoden zu realisieren. So z.B.

Looks.Rotate(ellipse, 40.0f);

wobei dann die Rotate Methode sich um alles kümmert. Aber das ist natürlich nicht ganz so anfängerfreundlich.

As a man thinketh in his heart, so he is.

  • Jun Fan
    Es gibt nichts Gutes, außer man tut es.
  • Erich Kästner
    Krawutzi-Kaputzi
  • Kasperl

Hallo der-schlingel,

naja, die Rotate-Klasse wird doch sicher Matrix.Rotate bzw. Matrix.RotateAt aufrufen, um die Drehung zu bewirken. Das kannst du aber auch mehrfach hintereinander aufrufen und die Drehungen addieren sich. Insofern sehe ich nicht, warum das besonderen Aufwand verursachen sollte.

Im Gegenteil kannst du ja nicht einfach die Winkel in einer Property addieren. Denn wenn du

new Rotate(new Translate (new Rotate (ellipse, 90.0f), 10), 90.0f);

machst, kommt ja was anders dabei heraus, als wenn du

new Translate (new Rotate (ellipse, 180.0f), 10);

oder

new Rotate (new Translate (ellipse, 10), 180.0f);

machst. Wenn du das Ganze aber einzeln in Aufrufe der Matrizen-Operationen umsetzt, geht alles gut.

herbivore

Das stimmt aber dennoch ist es unnötig umständlich so zu arbeiten und so was möchte ich vermeiden, da sich so der Benutzer immer merken müsste wie weit er das jeweilige Objekt gedreht hat.
Denn wenn ich schon mit Objekten arbeite, will ich auch die Daten gekapselt wissen.

As a man thinketh in his heart, so he is.

  • Jun Fan
    Es gibt nichts Gutes, außer man tut es.
  • Erich Kästner
    Krawutzi-Kaputzi
  • Kasperl

Hallo der-schlingel,

so ganz verstehe ich es nicht. Zum einen sind doch die genannten Eigenschaften (Drehung, Position, ...) für alle Objekte gültig. Du kannst sie doch auch in die Basis-Klasse packen.

Zum anderen könntest du, wenn immer nur der letzte Winkel gültig sein soll, doch alle inneren Rotate-Dekoratoren ignorieren, wenn der Winkel schon durch einen weiter außen liegenden Dekorator festgelegt wurde.

herbivore

Du könntest das zum Beispiel so umsetzen:


using System.Reflection;

public interface I__Call
{
  object __Call(string func_name, params object[] args);
}

public static class InvokeHelper
{
  public static void Invoke(I__Call obj, string func_name, params object[] args)
  {
    Type tObj = obj.GetType();
	  MethodInfo method = tObj.GetMethod(func_name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod);

	  if (method == null)
		  obj.__Call(func_name);
	  else
		  method.Invoke(obj, args);
  }

  public static RETURNTYPE Invoke<RETURNTYPE>(I__Call obj, string func_name, params object[] args)
  {
	  Type tObj = obj.GetType();
	  MethodInfo method = tObj.GetMethod(func_name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod);

	  if (method == null)
		  return (RETURNTYPE)obj.__Call(func_name);
	  else
	    return (RETURNTYPE)method.Invoke(obj, args);
  }
}

//Verwendung:
Foo obj = new Foo(); //Wichtig, implementiert I__Call
InvokeHelper.Invoke(obj, "Bar", null); //Aufruf der Funktion Bar

Durch die Verwendung von Reflection ist diese Art der Verwendung aber nicht performant. Das lässt sich aber durch die Verwendung eines Cache Beschleunigen.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Danke kleines_eichhörnchen für diese Lösung. Wieder etwas gelernt, doch glaube ich nicht für meine Zielgruppe geeignet, aber noch einmal danke.

so ganz verstehe ich es nicht. Zum einen sind doch die genannten Eigenschaften (Drehung, Position, ...) für alle Objekte gültig. Du kannst sie doch auch in die Basis-Klasse packen.

Für den Fall von Rotate stimmt das vollkommen. Aber nicht nicht alle dieser "Looks" können in die Basisklasse hinein. Rotate war ja nur ein Beispiel.

As a man thinketh in his heart, so he is.

  • Jun Fan
    Es gibt nichts Gutes, außer man tut es.
  • Erich Kästner
    Krawutzi-Kaputzi
  • Kasperl

reflection ist nicht perfomant genug für zeichenoperationen.

@JAck30lena,
ich weiß, hatte ich noch unter den Code geschrieben. Eventuell hilft es wie beschrieben einen Cache zu implementieren, der die Sache beschleunigt und an "normale" Ausführungs herankommt (Speicherung der MethodInfo-Klassen). Das ganze wird aber wiederum problematisch, da auch die Klasse mit abgefragt werden muss (mehrere Klassen können die Selbe Funktion implementieren (gleicher Name), sind aber nicht von einander abgleitet.)

Es bleibt also entweder nur Reflection oder Decorator

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Ich hab mir nun folgendes überlegt:

Ich könnte die Looks ich haben möchte in einer Liste speichern. Diese Liste würde beim Aufruf von Draw abgearbeitet um die verschiedenen Zeichenoptionen bzw. Accessoires (wie Rahmen, Spiegelung des Bilds, Glaseffekt drüberlegen oder sonstiges) anzuwenden.

Um den Programmiere etwas Arbeit abzunehmen greife ich zu einem Trick: Ich nehme Extension-Methoden her für den Basistyp der Zeichenobjekte. Ruft der Programmierer nun z.B. ellipse.Rotate(34.0f); auf, prüft die Methode ob das Rotate-Objekt schon in der Looks-Liste ist, falls ja, übernimmt es den angegebenen Winkel, falls nicht füge ich ihn in der Methode hinzu.

Was haltet ihr von dieser Idee?

As a man thinketh in his heart, so he is.

  • Jun Fan
    Es gibt nichts Gutes, außer man tut es.
  • Erich Kästner
    Krawutzi-Kaputzi
  • Kasperl

Hallo,

schreibe erst jetzt was, weil ich nach dieser Funktionalität gesucht habe.

Andere Fragen, wofür braucht man sowas? Dir als Programmierer muss doch zur Entwicklungszeit klar sein, um was es sich für ein Typ gerade handelt und welche Member dieser Typ hat.

Programmierer sind bekanntlich faule Menschen, die alles Mögliche erfinden, um sich Arbeit zu sparen. PHP ist schwach typisiert, so das
[php]
echo 1 + 1;
[/php]
und
[php]
echo "1" + "1";
[/php]
eine 2 auf den Bildschirm zaubern. Diese Funktionalität erspart eine Menge (Tipp-) Arbeit, wenn man Eigenschaften einer Klasse über Getter/Setter verwalten will, aber der Typ eigentlich keine Rolle spielt. Wenn ich die Programmierrichtlinien richtig interpretiere, ist es nicht angebracht, Getter/Setter mit umfangreichem Code auszustatten, sondern sollte dann, wenn es nötig ist, Methoden verwenden. Es ist aber nett, wenn z. B. ein Setter auch ein Modified-Flag setzen kann. Und da PHP der Typ einer Variablen egal ist, geht den Setter das eigentlich auch nichts an; eine entsprechende Validierung muss an anderer Stelle vorgenommen werden.

Beispiel einer sinnvollen Anwendung eines faulen Programmierers:
[php]

!/usr/bin/php5

class Foo
{
protected $_items = array
(
"Eins" => 5,
"Zwei" => "test"
);

public $Modified = false;

public function __set($name, $value)
{
_checkPropertyName($name);
$this->_items[$name] = $value;
$this->Modified = true;
}

public function __get($name)
{
_checkPropertyName($name);
return $this->_items[$name];
}

protected function _checkPropertyName($name)
{
if (!array_key_exists($name, $this->_items)
throw new Exception("Property $name does not exist");
}
}

$foo = new Foo();
$foo->Eins = 1;
$foo->Zwei = $_SERVER;
if ($foo->Modified)
echo "$foo->Eins = {$foo->Eins}, $foo->Zwei = {$foo->Zwei}\r\n";
$foo->Drei = "test"; /* <- Führt zur Exception */

[/php]

Diese so genannten Magic Functions erlauben es dem faulen Programmierer mit zwei relativ übersichtlichen Methoden und einer anfänglichen Definition alle Eigenschaften eines Objekts festzulegen. Spätere Änderungen gestalten sich einfach und der Code bleibt schlank. Wenn ich beispielsweise Personenstammdaten verwalten möchte, dann geht das über obige Konstruktion sehr einfach, da alle Felder zunächst als Zeichenkette behandelt werden können (Namen, Titel, Anreden, Wohnorte, Telefonnummern, Geburtsdaten [z. B. 25 MAR 1015] usw.)

Die strenge Typisierung von C# steht dieser Variante entgegen, obwohl sie das imho eigentlich nicht müsste. Mit var existiert ja schon ein (zumindest für den Programmierer) typenloses Konstrukt. Wenn man C# um ein Schlüsselwort erweitern würde, welches bei der Definition von Feldern angewendet werden würde, dann wäre eine Kennzeichnung derjenigen Felder möglich, die von einer Magic Function genutzt werden könnte und somit der Typ bekannt. Der Source in vielen Klassen ließe sich so imho drastisch verkürzen.

Hinrich

Hallo

Mit var existiert ja schon ein (zumindest für den Programmierer) typenloses Konstrukt.

nö, var ist nicht typenlos, sondern dient nur dazu, den Typ nicht zweimal schreiben zu müssen.

var list = new List<int> ();

ist genau das gleiche wie

List<int> list = new List<int> ();

Das heißt in beiden Fällen ist list streng als List<int> typisiert.

Der Source in vielen Klassen ließe sich so imho drastisch verkürzen.

Erstmal bezweifle ich das, aber selbst wenn, dann eben auf Kosten der Typsicherheit. C# nicht PHP. Das sind zwei völlig unterschiedliche Paar Schuhe. Fehlende Typisierung ist für kleine Programme nett, für große und robuste ist Typsicherheit vorzuziehen.

Trotzdem werden in C# 4.0 bestimmte dynamische Fähigkeiten eingebaut, die von PHP-Programmierern bestimmt mit Freuden "ausgenutzt" werden. Wenn du nicht so lange waren willst, verwende einen Indexer vom Typ Object und ein dahinterliegendes Dictionary<String, Object>. Da dadurch aber nicht C# als ganzes typenlos wird, hast du damit zwar deine dynamischen Properties, aber du wird dauernd casten müssen. Ich kann dir das nicht empfehlen. Du solltest in C# wie in C# programmieren und nicht wie in PHP.

Wenn du Codezeilen sparen willst, verwende die automatischen Properties von C# 3.0.

public int Length { get }

herbivore

Was mich derzeit noch an C#/VB stört, ist die strikte Bindung von Generics zur Compile-Zeit.


class List1 : List<Class1> { }
class List2 : List<Class2> { }
class List3 : List<Class3> { }

class Class1 {
  public List1 List1 { get; }
  public List2 List2 { get; }
}

class Class2 {
  public List1 List1 { get; }
  public List3 List3 { get; }
}

class Class3 {
  public List3 List3 { get; }
  public List2 List2 { get; }
}

class Class4 {
  public List1 List1 { get; }
}

/*************************************************/

void Foo(Class4 value) {
  Foo<Class1>(value.List1);
}

void Foo<T>(IEnumerable<T> list) {
  for each (T f in list) {
    Foo(f); //Fehler
  }
}

void Foo(Class1 value) {
  Foo<Class1>(value.List1);
  Foo<Class2>(value.List2);
  
  ...
}
void Foo(Class2 value) {
  Foo<Class1>(value.List1);
  Foo<Class2>(value.List3);

  ...
}
void Foo(Class3 value) {
  Foo<Class3>(value.List3);
  Foo<Class2>(value.List2);
    
  ...
}

Den Fehler kriegt man zwar mit einer Foo(obect value) Funktion wieder weg, allerdings bindet der Compiler alle Foo-Aufrufe von Foo<T> an die Funktion wo value ein Objekt ist.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

nö, var ist nicht typenlos, sondern dient nur dazu, den Typ nicht zweimal schreiben zu müssen.

            var v5 = new { Color=108, Price = 12.2};

var ist sicherlich nicht typenlos, aber mehr als "dient nur dazu , den Typ nicht zweimal schreiben zu müssen"

MSDN: Anonyme Typen (C#-Programmierhandbuch)
MSDN: Implizit typisierte lokale Variablen (C#-Programmierhandbuch)