Closures in C# verwenden
Aha, schon wieder was gelernt! Gerade bin ich über ein Problem gestolbert, dessen Lösung mir zunächst ein wenig schleierhaft war, was sich jedoch sehr elegant lösen ließ.
Ich habe eine Klasse, die eine Art Primary Key verwendet. Dieser Primary Key wird durch das Property "Id" abgebildet. Diese Id soll Objekte der Klasse in einer Menge eindeutig identifizieren. Normalerweise könnte man für soetwas Dictionaries verwenden, jedoch habe ich das Problem, dass ich die Liste mit der Klasse XmlSerializer als XML serialisieren möchte, die jedoch leider keine Dictionaries unterstützt. Der Workaround hierfür wäre das Implementieren des Interfaces IXmlSerializable in einer eigenen, von Dictionary<TKey, TValue> abeleiteten Klasse, jedoch ist das nicht erwünscht. Ist ausserdem ein anderes Problem.
Nehmen wir also folgende Klasse an:
public class Container : IEquatable<Container>
{
private string _id = string.Empty;
public string Id
{
get { return _id; }
set { _id = value; }
}
// Weitere Properties & Implementierungen
public bool Equals(Container other)
{
return (_id == other._id);
}
}
Die Klasse implementiert IEquatable*. Dieses Interface ist notwendig, um beispielsweise
das Vorhandensein eines Objektes in einer generischen Liste List<Container>
mit der Methode List<Container>.Contains(Container c) festzustellen. Dabei
ist übrigens darauf zu achten, dass die hierzu verwendete Contains Methode die komplette
Liste durchläuft und daher eine sehr teure Operation darstellt.
Nun möchte ich aber auch in der Lage sein, konkrete Objekte aus der Liste anhand
der Id herauszufiltern. Contains gibt ja nur true oder false zurück, also die Aussage, ob das Objekt mit der gleichen Id vorhanden ist. Dazu kann die Find Methode verwendet werden, die von List<T>
bereitgestellt wird. Diese akzeptiert einen generischen Delegaten, nämlich Predicate<T>,
dessen Signatur die folgende ist:
public delegate bool Predicate<T>(T obj)
Damit kann ich also eine Methode angeben, die eine benutzerdefinierte Funktion als
Vergleichsoperation auf alle Elemente der Liste anwendet. Die Implementierung in
List<T> sieht wie folgt aus:
public T Find(Predicate<T> match)
{
if (match == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
for (int i = 0; i < this._size; i++)
{
if (match(this._items[i]))
{
return this._items[i];
}
}
return default(T);
}
Die Liste läuft über alle Elemente und führt den Delegaten aus. Die Operation im delegaten muss true oder false zurückliefern. Ist der Vergleich true, dann stoppt Find den durchlauf und gibt das aktuelle Element zurück.
Wie kann ich dem Delegaten, der ein Objekt vom gleichen Typ erwartet und true oder
false zurückgibt aber nun sagen, dass er die lediglich die Ids der Objekte vergleich
soll? Das geht am besten über eine anonyme Funktion:
public class ContainerCollection
{
private List<Container> containers = new List<Container>();
public ContainerCollection() { Fill(); }
private void Fill()
{
// Ein paar container hinzufügen:
Container c1 = new Container();
c1.Id = "ContainerOne";
containers.Add(c1);
// ...
}
public Container FindContainerById(string id)
{
Container container = null;
container = containers.Find(
delegate(Container c)
{
return (c.Id == id);
}
);
return container;
}
}
Gegegeben ist die ContainerCollection Klasse, die intern die Liste verwaltet. Die
Funktion FindContainerByIdverwendet eine anonyme Methode. Normalerweise besitzt
eine Methode ihren eigenen Methoden-Stack, der alle durch Eingabeparameter definierten
Werte beinhaltet, jedoch ist der hier definierte Delegat im Methodenkontext von
FindContainerById deklariert worden, und hat daher weiterhin Zugriff auf den "äußeren"
Methodenstack. Dadurch ist es möglich, die Id des aktuell iterierten Containers
c mit dem Eingabeparameter id zu vergleichen.
Diese künstliche Verlängerung der Lebensdauer wird auch als Closure bezeichnet.
Der Aufruf könnte so aussehen:
ContainerCollection c = new ContainerCollection();
Container container = c.FindContainerById("ContainerOne");
Programmierern von funktionalen Programmiersprachen dürfte dieses Konzept durchaus bekannt sein. In JavaScript geht vieles über Closures und erfordert ein ziemliches Umdenken. Ich halte es deswegen für sehr gewinnbringend, sich mal mit funktionalen Programmiersprachen auseinander zu setzten. Wieso nicht mal F# ausprobieren?
Die Methode FindContainerById geht übrigens noch kleiner zu schreiben, wenn man
einfach alle Umbrüche entfernt:
public Container FindContainerById(string id)
{
return containers.Find(delegate(Container c) {return c.Id == id;});
}
Schon wieder haben wir eine Zeile gespart und die Leserlichkeit verschlechtert Dafür hält uns unser Chef jetzt für genialer, weil wir Code schreiben, den er noch weniger versteht ;)
* Ich weiss, meine Nicht-Autoproperties sind sowas von 2005. Auch lässt sich darüber streiten, ob die Überladung von Equals zur Prüfung auf Wertegleichheit so sauber ist, jedoch gibt die MSDN Doku dazu an, dass das durchaus eine valide Implementierung ist. In so einem Fall muss man natürlich für eine genügende Dokumentation sorgen.
Macro: ReSharper Deaktivieren
Ich verwende den ReSharper auf der Arbeit sehr gerne, jedoch zickt er manchmal rum. So überschreibt er beispielsweise Code Snippets und funkt einem ins IntelliSense. Das ist natürlich das erklärte Ziel dieser Software, aber manchmal nervt es, weswegen ich ein Macro geschrieben habe, das den ReSharper an und wieder aus macht. Der Code ist trivial, dennoch viel Spass damit!
Option Strict Off
Option Explicit Off
Imports System
Imports EnvDTE
Imports EnvDTE80
Public Module RecordingModule
Sub ActivateResharper()
For Each item In DTE.AddIns
If item.Name.Contains("ReSharper") Then
If item.Connected Then
item.Connected = False
DTE.StatusBar.Text = "ReSharper Deactivated"
Else
item.Connected = True
DTE.StatusBar.Text = "ReSharper Activated"
End If
End If
Next
End Sub
End Module
Code Snippet: Events einer Assembly auflisten
Das folgende Code Snippet kann angepasst werden, um die Events einer Assembly aufzulisten. Ich habe vor einer Weile mal ein die genannte hineingucken wollen um eine Liste zu generieren (daher auch die IO Teile im Snippet)
/// <summary>
/// Prints the events contained in the Assembly
/// System.Web.UI.WebControls into a file
/// </summary>
public static void PrintEvents()
{
Dictionary<string, List<string>> controlEvents
= new Dictionary<string, List<string>>();
Type[] types = Assembly.GetAssembly(typeof (WebControl)).GetTypes();
foreach (Type type in types)
foreach (EventInfo info in type.GetEvents())
if (!controlEvents.ContainsKey(info.Name))
{
List<string> eventNames = new List<string>();
eventNames.Add(type.Name);
controlEvents.Add(info.Name, eventNames);
}
else if (!controlEvents[info.Name].Contains(type.Name))
{
controlEvents[info.Name].Add(type.Name);
controlEvents[info.Name].Sort();
}
using (FileStream stream =
new FileStream(@"C:\ControlTypes.txt", FileMode.CreateNew))
using (StreamWriter writer = new StreamWriter(stream))
foreach (KeyValuePair<string, List<string>> item in controlEvents)
{
writer.WriteLine(item.Key);
foreach (string str in item.Value)
writer.WriteLine(string.Format(" {0}", str));
}
}
- 1