Programmiererpraxis

Dynamische Optionen mit der Gemini-Bibliothek

Ted Neward

Ted NewardLeser meiner Artikel oder Blog-Posts wissen das bereits, aber für diejenigen, die auf diesen Artikel gestoßen habe, (ob durch Zufall oder weil sie dachte, war es ein Horoskop), neige ich dazu, eine Menge Zeit Blick auf andere Sprachen und Plattformen zu verbringen.Dies geschieht in der Regel bei der Verfolgung Konzepte oder Ideen, die Modell-Software effektiver, effizienter oder genau helfen.

Ein neuer Trend – obwohl es nicht alles, was den letzten ist — innerhalb des Web-Gemeinschaft wurde das Streben nach "dynamisch" oder "scripting" Sprachen, besonders zwei von ihnen: Ruby (für die Ruby on Rails-Framework, manchmal auch als RoR, geschrieben wurde) und JavaScript (wofür wir Node.js haben für die Ausführung von Server-seitigen Anwendungen, zusammen mit Hunderten von Frameworks).Beide dieser Sprachen sind gekennzeichnet durch einen Mangel an was wir gewohnt in der c# und Visual Basic Welt sind: strikte Einhaltung welcher Klasse definiert als die einzige Definition für welches Objekt besteht.

In JavaScript (eine Sprache, die manchmal durch Smart Aleck Moderatoren wie ich als "Lisp mit geschweiften Klammern" gekennzeichnet ist), z. B. ein Objekt ist eine voll änderbare Einheit, d. h., Sie können Eigenschaften oder Methoden als erforderlich (oder wollte) hinzufügen:

var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;
myCar.makeSounds = function () {
  console.log("Vroom!
Vroom!")
}

Objekt-MyCar, wenn zuerst konstruiert, hat keine Eigenschaften oder Methoden darauf — diese werden implizit hinzugefügt, wenn die Datenwerte ("Ford" "Mustang", 1969 und die Funktion) auf diese Namen (Marke, Modell, Jahr und MakeSounds) festgelegt. Im Grunde ist jedes Objekt in JavaScript nur ein Wörterbuch mit Name/Wert-Paare, wo der Wert des Paares ein Datenelement oder eine Funktion aufgerufen werden kann. Klicken Sie unter Sprache Designern die Fähigkeit mit Typinformationen wie folgt spielen wird oft ein Metaobject Protokoll (MOP) genannt, und eine eingeschränkte Teilmenge davon heißt oft Aspektorientierte Programmierung (AOP). Es ist einen flexiblen und leistungsfähigen Ansatz Objekte, sondern eine, die von c# sehr anders ist. Vielmehr sagt als versuchen, eine komplexe Klasse-Hierarchie zu erstellen, in der Sie versuchen, jede mögliche Variante durch Vererbung, erfassen, wie traditionelle c#-Objektdesign zu vermuten wäre, der MOP-Ansatz, dass Dinge in der realen Welt sind nicht alle exakt die gleiche (außer ihre Daten natürlich), und die Art und Weise, in der Sie modellieren, sein sollte, entweder.

Entwickler, die Teil gewesen von den Microsoft .NET Framework Gemeinschaft seit vielen Jahren werden sich daran erinnern, dass eine frühere Version von c# den dynamischen Schlagwort/Typ, die ermöglicht Ihnen eingeführt, einen Verweis auf ein Objekt zu deklarieren, deren Mitglieder werden zur Laufzeit erkannt, aber das ist ein anderes Problem. (Dynamische Feature-Set macht es leichter Reflexion-Stil-Code schreiben, erstellen keine MOP Arten von Objekten.) Glücklicherweise haben die c#-Entwickler die Möglichkeit, beide: traditionellen statischen Typdefinitionen über die c#-Klasse Design Standardmechanismen; oder flexible Typdefinitionen durch eine open-Source-Bibliothek namens Gemini, die aufbaut auf dynamischer Funktionen, in der Nähe von JavaScriptian Funktionen zu geben.

Gemini-Grundlagen

Wie viele Pakete, die ich in diesem Artikel besprochen habe, ist Gemini über NuGet verfügbar: "Install-Package Gemini" in der Paket-Manager -Konsole bringt die Güte in Ihr Projekt. Im Gegensatz zu anderen Paketen haben Sie gesehen, jedoch wenn Gemini in das Projekt installiert ist es keine Assembly oder zwei (oder drei oder mehr) bringen. Stattdessen bringt mehrere Quelldateien und stellt sie in einen Ordner namens "Eiche" und dem Projekt direkt hinzugefügt. (Während ich dies schreibe, Gemini 1.2.7 besteht aus vier Dateien: Gemini.cs, GeminiInfo.cs, ObjectExtensions.cs und eine Textdatei mit den Releasenotes) Der Grund für den Ordner mit dem Namen Oak ist eigentlich sehr vernünftig: Gemini ist eigentlich ein Teil eines größeren Projekts (genannt, nicht überraschend, Eiche), die eine Menge für diese dynamische Programmierung Güte der ASP.NET -MVC-Welt bringt — ich werde das größere Eiche Paket in einem zukünftigen Artikel untersuchen.

Aus eigenem Antrieb, die Tatsache, dass Gemini wird geliefert als Quelle ist nicht wirklich eine große Sache – der Code lebt in einem eigenen Namespace (Oak) und wird einfach in das Projekt kompiliert werden, wie der Rest der Quelldateien sind. Auf eine praktische Note erleichtert jedoch die Quelldateien mit absurd Schritt durch den Gemini-Quellcode, wenn etwas schief geht, oder sogar Daumen durch den Code nur um zu sehen, was verfügbar ist, weil IntelliSense manchmal komplett durch die Verwendung von dynamischen Schlagwort/Typ besiegt wird.

Erste Schritte

Wieder, wie es meine Gewohnheit ist, beginne ich durch Erstellen eines Testprojekts Einheit, in dem einige Exploration-Tests geschrieben; in diesem Projekt ich Gemini installieren und testen es heraus indem Sie erstellen eine einfache "Hello World"-wie Test:

[TestMethod]
public void CanISetAndGetProperties()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = "Neward" });
  Assert.AreEqual(person.FirstName, "Ted");
  Assert.AreEqual(person.LastName, "Neward");
}

Was passiert, hier ist subtil, aber stark: Gemini, das Objekt auf der anderen Seite des Verweises "Person" ist ein Typ, der alle Eigenschaften oder Methoden, im Wesentlichen leer ist, bis diese Member zugewiesen (z. B. wie im Fall des obigen Codes) oder explizit hinzugefügt das Objekt durch die Methoden SetMember und GetMember, etwa so:

[TestMethod]
public void CanISetAndGetPropertiesDifferentWays()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = "Neward" });
  Assert.AreEqual(person.FirstName, "Ted");
  Assert.AreEqual(person.LastName, "Neward");
  person = new Gemini();
  person.SetMember("FirstName", "Ted");
  person.SetMember("LastName", "Neward");
  Assert.AreEqual(person.GetMember("FirstName"), "Ted");
  Assert.AreEqual(person.GetMember("LastName"), "Neward");
}

Während ich das hier Datenmember tun, es ist auch ebenso einfach, dies zu tun für Verhaltens-Mitglieder (d. h. Methoden) indem sie auf Instanzen des DynamicMethod (das Void zurückgibt) oder dynamisch­-Funktion (die erwartet einen Wert zurückgeben), die keine Parameter annimmt. Oder Sie können festlegen, an ihre Partner "WithParam", wenn die Methode oder Funktion einen Parameter akzeptieren kann wie folgt:

[TestMethod]
public void MakeNoise()
{
  dynamic person =
    new Gemini(new { FirstName = "Ted", LastName = "Neward" });
  person.MakeNoise =
    new DynamicFunction(() => "Uh, is this thing on?");
  person.Greet =
    new DynamicFunctionWithParam(name => "Howdy, " + name);
    Assert.IsTrue(person.MakeNoise().Contains("this thing"));
}

Einen interessanter kleinen Leckerbissen ergibt sich übrigens aus der Gemini-Bibliothek: Gemini-Objekte (Fehlen jeglicher Art von alternative Implementierung) "strukturelle Typisierung" verwenden um zu bestimmen, ob sie gleich sind oder wenn sie eine bestimmte Implementierung erfüllen. Im Gegensatz zu dem OOP-Typ-Systeme, die den Vererbung/IS-A-Test verwenden, um festzustellen, ob ein bestimmtes Objekt einen Object-Parameter Typ auferlegten Einschränkungen erfüllen kann, Fragen strukturell typisierter Systeme stattdessen nur, ob das Objekt übergebene hat alle Anforderungen (Mitglieder, in diesem Fall) musste den Code ordnungsgemäß ausgeführt wird. Strukturelle eingeben, wie es unter den funktionalen Sprachen bekannt ist geht auch unter dem Begriff "Duck typing" in dynamischen Sprachen (aber das hört sich nicht so Cool).

Betrachten, für einen Augenblick, eine Methode, die ein Objekt und druckt eine freundliche Meldung über das Objekt, wie im Abbildung 1.

Abbildung 1-Methode, die ein Objekt und druckt eine Nachricht annimmt

string SayHello(dynamic thing)
{
  return String.Format("Hello, {0}, you are {1} years old!",
    thing.FirstName, thing.Age);
}
[TestMethod]
public void DemonstrateStructuralTyping()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = 
      "Neward", Age = 42 });
    string message = SayHello(person);
    Assert.AreEqual("Hello, Ted, you are 42 years old!", 
      message);
    dynamic pet = new Gemini(
      new { FirstName = "Scooter", Age = 3, Hunter = true });
  string otherMessage = SayHello(pet);
  Assert.AreEqual("Hello, Scooter, you are 3 years old!", 
      otherMessage);
}

Normalerweise in einer traditionellen objektorientierten Hierarchie, Person und Pet würde wahrscheinlich kommen aus sehr unterschiedlichen Branchen die Vererbungsstruktur — Menschen und Haustiere nicht im allgemeinen Teile viele gemeinsame Attribute in einem Softwaresystem (ungeachtet dessen, was Katzen denken). In einer strukturell oder Ente-typisierten System, allerdings weniger Arbeit muss rein zu machen die Vererbungskette Tiefe und allumfassende — wenn es ist ein Mensch, der auch jagt, dann He, Mensch hat ein Mitglied der "Hunter" drauf, und jede Routine, die den Hunter-Status des Objekts überprüfen will übergeben können, Mitglied, ob es ein Mensch ist, Katze oder Predator-Drohne.

Verhör

Der Kompromiss in der Duck-typing-Ansatz, wie viele feststellen werden, ist, dass der Compiler, dass nur bestimmte Arten von Objekten übergeben werden können, und das gleiche für Gemini-Typen gilt nicht durchsetzen kann – vor allem, weil die meisten Gemini-Code idiomatisch das Objekt hinter einen dynamischen Verweis speichert. Sie müssen nehmen, ein wenig mehr Zeit und Mühe um sicherzustellen, dass das Objekt abgegeben werden die Anforderungen erfüllt, sonst stehen einige Common Language Runtime-Ausnahmen. Dies bedeutet, verhören das Objekt, um festzustellen, ob es den erforderlichen Member besitzt, die in Gemini erfolgt mithilfe der RespondsTo-Methode; auch gibt es einige Methoden, um die verschiedenen Mitgliedern zurückgeben, die Gemini erkennt als Teil eines bestimmten Objekts.

Betrachten Sie beispielsweise eine Methode, die ein Objekt, der weiß erwartet, wie man jagt:

int Hunt(dynamic thing)
{
  return thing.Hunt();
}

Beim Scooter übergeben wird, Dinge funktionieren, wie in Abbildung 2.

Abbildung 2 dynamische Programmierung wenn es funktioniert

[TestMethod]
public void AHuntingWeWillGo()
{
  dynamic pet = new Gemini(
    new
    {
      FirstName = "Scooter",
      Age = 3,
      Hunter = true,
      Hunt = new DynamicFunction(() => new Random().Next(4))
    });
  int hunted = Hunt(pet);
  Assert.IsTrue(hunted >= 0 && hunted < 4);
  // ...
}

Aber wenn etwas, das nicht weiß, wie man jagt übergeben wird, Ausnahmen führen werden, wie in Abbildung 3.

Abbildung 3: dynamische Programmierung wenn es scheitert

[TestMethod]
public void AHuntingWeWillGo()
{
  // ...
dynamic person = new Gemini(
    new
    {
      FirstName = "Ted",
      LastName = "Neward",
      Age = 42
    });
  hunted = Hunt(person);
  Assert.IsTrue(hunted >= 0 && hunted < 4);
}

Um dies zu verhindern, sollten die Jagd-Methode testen, um festzustellen, ob das fragliche Element vorhanden ist, indem mithilfe der RespondsTo-Methode. Dies ist ein einfacher Wrapper um die TryGetMember-Methode und ist gedacht für einfache Boolean Ja/Nein Antworten:

int Hunt(dynamic thing)
{
  if (thing.RespondsTo("Hunt"))
    return thing.Hunt();
  else
    // If you don't know how to hunt, you probably can't catch anything.
return 0;
}

By the Way, wenn das alles scheint ziemlich einfach Textbausteine oder einen Wrapper für ein Wörterbuch < String, Object >, das keine unzutreffende Beurteilung ist — zugrunde liegt der Gemini-Klasse ist, dass genaue Wörterbuchschnittstelle. Aber die Wrapper-Arten helfen, einige der die Rotationen des Typ-System zu erleichtern, die sonst erforderlich, wie der Einsatz des dynamic-Schlüsselworts.

Aber was passiert, wenn mehrere Objekte ähnliche Arten des Verhaltens teilen? Beispielsweise alle vier Katzen wissen, wie man jagt, und es wäre eine neue anonyme Methode-Definition für alle vier, zumal alle vier Aktie Katzenartige Jagdinstinkte schreiben etwas ineffizient. Traditionelle OOP wäre dies kein Problem, da würden sie alle Member der Klasse Cat und somit dieselbe Implementierung teilen. MOP-Systeme, wie z.B. JavaScript ist in der Regel ein Mechanismus, um ein Objekt zu verschieben oder "Kette" ermöglichen einen Eigenschaft oder Anfrage Anruf auf ein anderes Objekt, genannt einen "Prototyp". In Gemini verwenden Sie eine interessante Kombination von statische Typisierung und MOP genannt "Extensions".

Prototype

Zunächst benötigen Sie einen Basistyp, der Katzen identifiziert:

public class Cat : Gemini
{
  public Cat() : base() { }
  public Cat(string name) : base(new { FirstName = name }) { }
}

Hinweis, die Cat-Klasse von Gemini, erbt das ist, was ermöglicht die Cat-Klasse, um die dynamische Flexibilität verfügen, die bisher diskutiert worden ist — in der Tat, der zweite Katze-Konstruktor verwendet den gleichen Gemini-Konstruktor, der verwendet wird, um die dynamischen Instanzen bisher zu erstellen. Dies bedeutet, dass alle vorhergehenden Prosa enthält noch für jede Katze-Instanz.

Sondern Gemini erlaubt uns zu Erklärungen wie Katzen sein können auch "Erweitert", sodass jeder Katze die gleiche Funktionalität erbt, ohne dass explizit es jede Instanz hinzu.

Erweitern einer Klasse

Vermuten Sie für eine praktische Nutzung dieser Funktionalität, dass dies ist eine Web-Anwendung, die entwickelt wird. Häufig müssen Sie, um HTML -­fliehen die Namenswerte werden gespeichert und zurückkehrte, um zu vermeiden, versehentlich erlaubt HTML-Injection (oder noch schlimmer, SQL-Injection):

string Htmlize(string incoming)
{
  string temp = incoming;
  temp = temp.Replace("&", "&amp;");
  temp = temp.Replace("<", "&lt;");
  temp = temp.Replace(">", "&gt;");
  return temp;
}

Das ist ein Schmerz zu erinnern, auf jedes Modellobjekt im System definiert; Glücklicherweise MOP können Sie systematisch "erreichen" und definieren neue Verhaltens-Mitglieder auf die Modellobjekte wie in Abbildung 4.

Abbildung 4 Schreibmethoden ohne Schreibmethoden

[TestMethod]
public void HtmlizeKittyNames()
{
  Gemini.Extend<Cat>(cat =>
  {
    cat.MakeNoise = new DynamicFunction(() => "Meow");
    cat.Hunt = new DynamicFunction(() => new Random().Next(4));
    var members =
      (cat.HashOfProperties() as IDictionary<string, object>).ToList();
    members.ForEach(keyValuePair =>
    {
      cat.SetMember(keyValuePair.Key + "Html",
        new DynamicFunction( () =>
          Htmlize(cat.GetMember(keyValuePair.Key))));
    });
  });
  dynamic scooter = new Cat("Sco<tag>oter");
  Assert.AreEqual("Sco<tag>oter", scooter.FirstName);
  Assert.AreEqual("Sco&lt;tag&gt;oter", scooter.FirstNameHtml());
}

Im Wesentlichen ist der erweitern-Aufruf hinzufügen neue Methoden an jede Katze, Suffix mit "Html", sodass die FirstName-Eigenschaft in einer HTML-sicher-Version zugegriffen werden kann, durch Aufrufen der FirstNameHtml-Methode statt.

Und dies kann ganz zur Laufzeit für jedes Gemini -­erben Typ im System.

Persistenz und vieles mehr

Gemini ist nicht Ziel, die Gesamtheit der c#-Umgebung mit einem Haufen dynamisch aufgelöst Objekte zu ersetzen – ganz im Gegenteil. In seiner Heimat Verwendung innerhalb der Eiche MVC-Framework dient Gemini Modellklassen (unter anderem) Persistenz und andere nützliche Verhalten hinzu und Hinzufügen von Validierungen ohne überladen des Benutzers Code oder Teilklassen zu benötigen. Auch außerhalb der Eiche, jedoch Gemini stellt einige leistungsstarke Design-Mechanik, die einige Leser von Teil 8 meiner Multiparadigmatic .NET-Serie von einer Weile erinnern sich vielleicht, wie (msdn.microsoft.com/magazine/hh205754).

Apropos Eiche, das Faß für das nächste Mal, also halten um zu sehen wie das dynamische alles in einem realen Szenario abspielt.

Viel Spaß beim Programmieren!

Ted Neward*, Geschäftsführer von Neward & Associates LLC, Er hat mehr als 100 Artikel geschrieben und Autor und Mitautor von einem Dutzend Bücher, darunter "professionelle f# 2.0" (Wrox, 2010). Er ist F#-MVP, ein bekannter Experte für Java, und er hält auf Java- und .NET-Konferenzen in der ganzen Welt Vorträge. Neward berät und hilft regelmäßig. Wenn Sie Interesse an seiner Mitarbeit in Ihrem Team haben, können Sie ihn unter ted@tedneward.com oder Ted.Neward@neudesic.com erreichen. Unter blogs.tedneward.com können Sie seinen Blog lesen, und Sie können Neward unter twitter.com/tedneward auf Twitter folgen.*

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Amir Rajan (Verbesserung der Unternehmen)
Amir Rajan ist principal Consultant bei Verbesserung der Unternehmen. Er ist aktives Mitglied der Entwicklungsgemeinschaft und verfügt über Expertise in ASP.NET MVC, HTML5, REST-Architekturen, Ruby, JavaScript/CoffeeScript, NodeJS, iOS/ZielC und f#. Rajan ist eine wahre Polyglot mit einer unerschütterlichen Leidenschaft für Software. Er ist auf Twitter bei @amirrajan und im Web unter github.com/amirrajan, amirrajan.net und improvingenterprises.com.