Wahrscheinlich hab ich die falschen Worte benutzt, aber ich habe in der Suche hier nichts dazu gefunden.
Ist es möglich - und wenn ja, wie - z.B. folgendes zu machen:
alle Methoden über denen ein Attribut (z.B. hier Log("Calc") angegeben ist, sollen automatisch einen Log-Eintrag über das Attribut bekommen. So könnte man die Log-Logik von dem Rest trennen und das Programm wäre nicht mehr übersät mit einzelnen Log-Aufrufen. Aber ich weiß nicht, ob Dot-Net so einen Mechanismus bietet.
Klar, die Attribute abfragen geht, aber wie bekomme ich ein Event, das zeigt, dass jetzt die Methode mit dem Attribut "Log" aufgerufen wurde?
class Test()
{
[Log("Calc")]
public void Calc()
{
}
}
Ich denke, dass irgendwo in der Reflection der Weg zur Lösung liegt. Habe aber keine Ahnung, wo ich suchen soll.
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
die Attribut-Objekte werden erst erzeugt, wenn man per Reflection darauf zugreift. Also ohne Code *in* der Methode wirst du leider kein Logging hinbekommen.
Allerdings laufen die i.d.R. nicht über Attribute, sondern über Konfigurationsfiles (ist auch praktisch, weil Logging im Code zu verankern ja irgendwie genau das ist, was man nicht will; und dazu gehört ja auch, ob man überhaupt loggen will, soll ja ggf. schnell umschaltbar sein). Man definiert dort sogenannten Interceptoren. Die Klassen, die den Logging-Code (oder anderen) enthalten heissen Crosscuts.
Etwas anders als die hier angedachte Lösung funktioniert AspectDNG (http://aspectdng.tigris.org/). Hier wird aber der CrossCut per Attribut mit dem Namen der Methode gefüttert, die zu intercepten ist. Also genau umgedreht.
Performance reicht von grauenhaft bis zu "ähnlich wie handgeschriebener Code". Letzteres erfordert aber immer Codemanipulation auf MSIL-Ebene.
Ich denke auf Basis eines Codeausschnittes wie diesem hier
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
namespace TestDebug
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MethodInfo info = this.GetType().GetMethod("Test");
MethodBase methodBase = MethodBuilder.GetCurrentMethod();
//TypeBuilder builder = methodBase.;
AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();
AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll", true);
TypeBuilder typeBuilder = moduleBuilder.DefineType("TestDebug.Form1", TypeAttributes.AutoClass | TypeAttributes.Public | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, typeof(object), new Type[] { typeof(void) });
MethodBuilder methodBuilder = typeBuilder.DefineMethod("Test",MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final,typeof(void),new Type[]{typeof(void)});
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
ILGenerator methodILGenerator = methodBuilder.GetILGenerator();
DynamicMethod dynMeth = new DynamicMethod("Test", typeof(void),null,GetType());
dynMeth.GetILGenerator().EmitCall(OpCodes.Call,GetType().GetMethod("Test"), null);
}
public void Test()
{
MessageBox.Show("Hallo");
}
}
}
der natürlich noch nicht funktioniert,
sollte es möglich sein, so etwas zu schaffen.
Ich weiß natürlich noch nicht, ob es da ein unüberwindbares Hinernis dazwischen geben könnte,
aber wenn man es schafft, nur für eine Methode einen Wrapper zu machen, dann sollte es möglich sein.
d.h. ungefähre Vorgehensweise:
Am Anfang Assembly durchgehen: Methode im IL-Code neu generieren mit neuem Aufruf, z.B. vorhandene Methode kapseln.
Dann aufrufen. Fertig!!!
nur das sind im kleinen ausgeführt ganz schön viele einzelne Schritte.
Wobei ich auch nicht weiss, ist es möglich eine Assembly zur Laufzeit so zu verändern, dass statt Aufruf von Methode A eine Methode A' generiert wird (Wrapper), die ihrerseits A aufruft?
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
quasi das Gleiche hatte ich auch schon überlegt. Allerdings bin ich bei der Überlegung stecken geblieben, weil ich eben keine einfache Möglichkeit gefunden habe, eine Assembly als Syntaxbaum einzulesen, bis runter zu den Statements bzw. Statementbestandteilen zu traversieren, gezielt an einigen wenigen Stellen zu modifizieren und wieder zu schreiben.
Teile davon gehen einfach, z.B. das Einlesen und das Traversieren bis zur Deklarationsebene (also normales Reflection), aber das ganze Verfahren mit allen Teilen scheint mir leider sehr, sehr aufwändig. Wenn du aber eine Lösung findest, bin ich sehr interessiert. Das können man nämlich nicht nur für Attribute verwenden, sondern für jegliche Instrumentierung des Code, z.B. für Pfadtests.
Wenn man eine solches Verfahren hätte, denke ich nicht, dass es dann zur Laufzeit stattfinden müsste. Ich würde dieses Verfahren eher als Teil des Build-Processes sehen.
@Golo: Das ist uns klar (ich denke ich kann da auch für Herbivore das Wort mit ergreifen)
Wir möchten aber solche Mechanismen im "normalen" Visual.Net Compiler nutzen können.
Deshalb ist es wichtig, wie er schon sagte, einen Weg zu finden, die Methoden zu traversieren und die Assembly dann - wie oben beschrieben - wieder zurückzuschreiben.
Sprich:
Gesucht wird eine Möglichkeit um z.B. die Methode X aus Klasse A, so zur Laufzeit/Buildprozess umzuschreiben, dass eine Methode X' dann X aufruft.
So können eigene Algorithmen usw. zwischen dem Aufrufen von X' und X eingepflanzt werden. Möglich wäre dann damit z.B. auch sowas:
[Log("Test")] //Automatischen Logeintrag beim Aufrufen dieser methode
public void TestMethode()
{
}
und dies würde nur geschehen, weil z.B. (1 Möglichkeit):
Original von dr4g0n76
Wir möchten aber solche Mechanismen im "normalen" Visual.Net Compiler nutzen können.
Fast alle AOP-Geschichten arbeiten mit dem normalen Compiler. Entweder sie gehen den Weg, der dir vorschwebt (IL-Code-Manipulation), oder es wird die Profiling-API genutzt. Schade nur, dass selbst mit C# 3.0 keine AOP-Features kommen werden.
Svenson: Ja, das ist mir bewusst. Habe mich dazu leider falsch ausgedrückt.
Ich wollte ja einen etwas anderen Weg gehen als von Herbivore angedacht,
nämlich den, dass ja die X' von X - Methode zur Laufzeit geschrieben wird, eben alls - wie du es sagst - IL-Code-Manipulation. Aber das eben auch zur Laufzeit.
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
stimmt und ich würde es stattdessen gerne zur Compilezeit haben, u.a. deshalb, um das aufwändige Reflection zur Laufzeit zu sparen. Hintergrund ist eine performante Lösung für [Artikel] Attribute zur Prüfung von Properties verwenden
Ohne Detailverstaendnis: Wuerde Mono.Cecil [1] helfen? Der Entwickler der Library ist recht umgaenglich und die Library sollte auch mit .Net funktionieren..
Ihr wollt also den erzeugten IL Code nachträglich bearbeiten um die Log Attribute durch einen Methodenaufruf zu ersetzen? (Hab den Thread nur überflogen) Eventuell kann NRefactory helfen.
Also, inzwischen habe ich herausgefunden, dass es 3 Methoden geben müsste:
1. Profiling-Callbacks (wie auch schon von Svenson genannt), hier muss aber der komplette Profiler so wie es aussieht in C++ oder einer anderen Sprache vorliegen.
C# geht meines Erachtens nicht.
2. Abstract IL von Microsoft benutzen (mache gerade Experimente damit). Müsste garantiert funktionieren.
3. Mit Reflection und IL-Code-Generierung zur Laufzeit (wäre mir das liebste).
EDIT:
4. es geht auch nachträglich am IL-Code
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication7
{
class CCalc
{
public int Sum(int n1, int n2)
{
return n1 + n2;
}
public static void Calculate()
{
LogMessage();
MessageBox.Show("Calculate");
}
public static void LogMessage()
{
MessageBox.Show("Nachricht");
}
}
}
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
@FZelle und andere: danke an die Hinweise, ich bin noch am recherchieren der ganzen Links und was ich selber gefunden habe.
Bei einem bin ich momentan jedoch steckengeblieben, habe im ganzen Internet noch(!) kein einziges Beispiel dazu gefunden, nur Implementierungsauszüge dieser Klasse, die mir bisher noch nicht allzuviel verraten bzgl. woher ich den Methodenzeiger bekomme
System.Reflection.Emit.MethodRental //Provides a fast way to swap method body implementation given a method of a class.
und mit der statischen Methode "MethodSwapBody" dieser Klasse kann (soweit der Name vermuten läßt)
eine Methode wohl zur Laufzeit ausgetauscht werden.
byte[] aByteSignatureMethod = this.GetType().GetMethod().GetMethodBody().GetILAsByteArray();
DynamicILInfo d = new DynamicILInfo();
d.GetTokenFor(aByteSignatureMethod);
MethodRental.SwapMethodBody(this.GetType(), nToken,new IntPtr(pointerToMethod),0,0);
Das Beispiel würde wohl funktionieren, aber ich weiß nicht, wie ich für den new IntPtr(pointerToMethod) den PointerToMethod ermitteln soll.
Weiss einer von euch wie das geht?
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
Original von dr4g0n76
Wir möchten aber solche Mechanismen im "normalen" Visual.Net Compiler nutzen können.
Gesucht wird eine Möglichkeit um z.B. die Methode X aus Klasse A, so zur Laufzeit/Buildprozess umzuschreiben, dass eine Methode X' dann X aufruft.
So können eigene Algorithmen usw. zwischen dem Aufrufen von X' und X eingepflanzt werden.
Kleiner Einwurf von mir dazu (da ich mich in letzter Zeit mit AOP beschäftigt habe): Eigentlich kann das, was du möchstest, Rapier-Loom.NET schon. Standardbeispiel für AOP ist Logging Und in RL kannst du einzelne Aspekte als Attribute an die Zielklassen schreiben. Mehtoden ersetzen bzw. neue hinzufügen geht damit alles.
Und da das ganze so gemacht wird, wie du das andenkst (beim Instanziieren wird dynamisch ein Proxy mittels TypeBuilder und ILGenerator erzeugt).
Gruß
dN!3L
P.S.: Ich hoffe, ich bekomme bald meinen Artikel zu AOP hin, da stünden dann Details zu RL drin.
Edit:
Zitat
Bei einem bin ich momentan jedoch steckengeblieben, habe im ganzen Internet noch(!) kein einziges Beispiel dazu gefunden...
Nun, Class1 ist von Interceptor abgeleitet, Interceptor wiederum von ContextBoundObject, dass es uns dann ermöglicht sogenannte MessageSinks zu benutzen, diesen gesamten Mechanismus machen wir uns dann zunutze um Logdateien zu schreiben. Was natürlich nur 1 der Anwendungsmöglichkeiten ist.
[AttributeSpy("Interceptor")]
class Interceptor : ContextBoundObject
{
}