Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Utilizzo di funzionalità dinamiche con la libreria Gemini
I lettori dei miei articoli o post di blog si saprà già, ma per chi hai inciampato su questo articolo (accidentalmente o perché pensavano che fosse un oroscopo), tendono a spendere un sacco di tempo a guardare altri linguaggi e piattaforme. Questo è fatto solitamente all'inseguimento di concetti o idee che aiutano il modello software in modo più efficace, efficiente o con precisione.
Una tendenza recente — anche se non è tutto ciò che di recente, all'interno del Web community è stato l'inseguimento di lingue "dinamiche" o "script", soprattutto due di loro: Ruby (per il quale è stato scritto il rubino su framework Rails, spesso chiamato RoR,) e JavaScript (per il quale abbiamo node. js per l'esecuzione di applicazioni server-side, insieme a centinaia di quadri).Entrambe queste lingue sono caratterizzati da una mancanza di quello cui siamo abituati nel mondo C# e Visual Basic : rispetto rigoroso di una classe che definisce come la definizione unica per la quale un oggetto è costituito.
In JavaScript (un linguaggio che è a volte caratterizzato da smart aleck presentatori come me come "Lisp con parentesi graffe"), ad esempio, un oggetto è un'entità completamente static, significa che è possibile aggiungere proprietà o metodi come necessario (o voluto):
var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;
myCar.makeSounds = function () {
console.log("Vroom!
Vroom!")
}
MyCar l'oggetto, quando in primo luogo costruito, non ha proprietà o metodi su di esso, questi vengono aggiunti in modo implicito quando i valori dei dati ("Ford," "Mustang", 1969 e la funzione) sono impostate su quei nomi (marca, modello, anno e makeSounds). In sostanza, ogni oggetto in JavaScript è solo un dizionario di coppie nome/valore, dove il valore della coppia può essere un elemento di dati o una funzione da richiamare. Tra i progettisti del linguaggio, la possibilità di giocare con informazioni di tipo come questo è spesso chiamata un Metaobject Protocol (MOP), e un sottoinsieme limitato di questo è spesso chiamato aspect-oriented programming (AOP). È un approccio flessibile e potente per gli oggetti, ma uno che è molto diverso da quello di C#. Piuttosto che cercare di creare una gerarchia di classi complesse in cui si tenta di catturare ogni possibile variazione attraverso l'ereditarietà, come design tradizionale C# oggetto vorrei suggerire, l'approccio MOP dice che le cose nel mondo reale non tutti l'esatto stesso (fatta eccezione per i dati, naturalmente) e il modo in cui voi li modello non dovrebbe essere, neanche.
Gli sviluppatori che sono stati parte della Comunità Microsoft .NET Framework per molti anni ora ricorderanno che una versione precedente di C# introdotta il parola chiave/tipo dinamico, che consente di dichiarare un riferimento a un oggetto i cui membri vengono scoperti in fase di esecuzione, ma questo è un problema diverso. (Il set di funzionalità dinamica rende più facile scrivere codice riflesso-stile, non creare tipi MOP degli oggetti.) Fortunatamente, gli sviluppatori c# hanno l'opzione di entrambi: definizioni tradizionali di tipo statico, attraverso i C# classe design meccanismi standard; o le definizioni di tipo flessibile, attraverso una libreria open source chiamato Gemini che si basa sulla cima della funzionalità dinamica per darvi caratteristiche vicino JavaScriptian.
Gemini Basics
Come un sacco di pacchetti che ho discusso in questa colonna, Gemini è disponibile attraverso NuGet: "Pacchetto di installazione Gemini" nella Console di Gestione pacchetti porta la bontà nel vostro progetto. A differenza di altri pacchetti che hai visto, tuttavia, quando Gemini è installato nel progetto non porta un assembly o due (o tre o più). Invece, esso porta con sé diversi file di origine e li mette in una cartella denominata "Quercia" e li aggiunge direttamente al progetto. (A partire da questa scrittura, Gemini 1.2.7 è costituito da quattro file: Gemini.cs, GeminiInfo.cs, ObjectExtensions.cs e un file di testo contenente le note di rilascio.) Il motivo per cui la cartella denominata Oak è in realtà molto ragionevole: Gemini è in realtà un sottoinsieme di un più ampio progetto (chiamato, non sorprendentemente, quercia) che porta un sacco di questa bontà di programmazione dinamica al mondo MVC ASP.NET — esplorerò il pacchetto più grande quercia in un articolo futuro.
Di propria iniziativa, il fatto che Gemini viene consegnato come fonte non è davvero un grande affare — il codice vive in un proprio spazio dei nomi (quercia) e semplicemente verrà compilato nel progetto come il resto dei file di origine. Su una nota pratica, tuttavia, avendo i file sorgente rende assurdamente facile passo attraverso il codice sorgente di Gemini, quando qualcosa va storto, o anche del pollice attraverso il codice giusto per vedere cosa è disponibile, perché IntelliSense è talvolta completamente sconfitto dall'uso del dinamico tipo di parola chiave.
Guida introduttiva
Ancora una volta, come è mia abitudine, iniziare creando un progetto di unit test in cui scrivere alcuni test di esplorazione; in tale progetto installare Gemini e testarlo fuori con la creazione di un semplice "hello world"-come prova:
[TestMethod]
public void CanISetAndGetProperties()
{
dynamic person = new Gemini(
new { FirstName = "Ted", LastName = "Neward" });
Assert.AreEqual(person.FirstName, "Ted");
Assert.AreEqual(person.LastName, "Neward");
}
Che cosa sta accadendo qui è sottile, ma potente: Gemini, l'oggetto su altro lato del riferimento "persona", è un tipo che è essenzialmente vuoto di tutte le proprietà o metodi, finché tali membri sono assegnati a (ad esempio in caso di codice precedente) o in modo esplicito l'oggetto aggiunto attraverso i metodi SetMember e GetMember, in questo modo:
[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");
}
Mentre faccio questo per i membri dati qui, è anche altrettanto facile fare questo per i membri del comportamento (cioè, metodi) impostandoli a istanze di classe DynamicMethod (che restituisce void) o dinamicofunzione (che prevede di restituire un valore), ciascuno dei quali non accetta parametri. Oppure è possibile impostare per i loro partner di "WithParam", se il metodo o la funzione può assumere un parametro, in questo modo:
[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"));
}
Un interessante piccolo bocconcino nasce dalla libreria Gemini, tra l'altro: Oggetti di Gemini (assente qualsiasi tipo di implementazione alternativa) utilizzano "strutturali digitando" per determinare se sono uguali o se soddisfano una particolare implementazione. Contrariamente a quanto i sistemi di OOP-tipo, che utilizzano l'ereditarietà/IS-un test per determinare se un determinato oggetto in grado di soddisfare le restrizioni sul tipo di parametro di un oggetto, sistemi strutturalmente tipizzati invece basta chiedono se l'oggetto passato ha tutti i requisiti (membri, in questo caso) necessari per eseguire il codice correttamente. Digitando strutturale, come è noto tra i linguaggi funzionali, va anche con il termine "duck typing" in linguaggi dinamici (ma che non suono come fresco).
Si consideri, per un momento, un metodo che accetta un oggetto e viene stampato un messaggio amichevole su quell'oggetto, come mostrato Figura 1.
Figura 1 Metodo che accetta un oggetto e stampe un messaggio
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);
}
Normalmente, in una gerarchia di oggetti tradizionale, persona e Pet sarebbe probabilmente provengono da diversi rami dell'albero di ereditarietà — persone e gli animali domestici generalmente non condividono un sacco di attributi comuni in un sistema software (nonostante ciò che gatti). In un strutturalmente o sistema duck-tipizzato, tuttavia, meno lavoro deve andare a fare la catena di ereditarietà profonda e totalizzante, se c'è un uomo che anche a caccia, poi, Ehi, che umano è un membro di "Hunter", e su qualsiasi routine che vuole controllare lo stato del cacciatore dell'oggetto passato può utilizzare tale membro, sia che si tratti di un essere umano, gatto o telecomandato.
Interrogatorio
Il compromesso nell'approccio duck typing, come molti si nota, è che il compilatore non può imporre che solo certi tipi di oggetti possono essere passati, e lo stesso è vero per tipi di Gemini — soprattutto perché gran parte del codice Gemini idiomatico memorizza l'oggetto dietro un riferimento dinamico. Hai bisogno di prendere un piccolo extra tempo e sforzo per assicurare l'oggetto viene consegnato soddisfa i requisiti, o altrimenti affrontare alcune eccezioni runtime. Questo significa interrogare l'oggetto per vedere se ha il membro necessario, che è fatto di Gemini utilizzando il metodo RespondsTo; ci sono anche alcuni metodi per restituire i vari membri che Gemini riconosce come parte di un determinato oggetto.
Si consideri, ad esempio, un metodo che prevede un oggetto che sa come andare a caccia:
int Hunt(dynamic thing)
{
return thing.Hunt();
}
Quando Scooter è passato, le cose funzionano bene, come mostrato Figura 2.
Figura 2 programmazione dinamica quando funziona
[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);
// ...
}
Ma quando qualcosa che non so come andare a caccia è passata, le eccezioni si tradurrà, come mostrato Figura 3.
Figura 3 programmazione dinamica quando non riesce
[TestMethod]
public void AHuntingWeWillGo()
{
// ...
dynamic person = new Gemini(
new
{
FirstName = "Ted",
LastName = "Neward",
Age = 42
});
hunted = Hunt(person);
Assert.IsTrue(hunted >= 0 && hunted < 4);
}
Per evitare questo, il metodo di caccia deve testare per vedere se il membro in questione esiste utilizzando il metodo RespondsTo. Questo è un semplice wrapper per il metodo TryGetMember ed è inteso per Boolean semplice sì/no risposte:
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;
}
A proposito, se tutto questo sembra abbastanza semplice boilerplate o un wrapper per un dizionario < string, object >, che non è una valutazione errata — sottostante la classe Gemini è quella esatta interfaccia Dictionary. Ma i tipi wrapper contribuire ad alleviare alcuni delle rotazioni di sistema tipo che altrimenti sarebbe necessari, come fa l'uso della parola chiave dinamica.
Ma cosa succede quando diversi oggetti condividono simili tipi di comportamento? Per esempio, quattro gatti tutti sanno come andare a caccia, e sarebbe alquanto inefficiente per scrivere una nuova definizione di metodo anonimo per tutti e quattro, specialmente come tutti gli istinti di quota quattro caccia felina. In OOP tradizionale questo sarebbe un problema, perché vuoi essere tutti membri della classe gatto e quindi condividono la stessa implementazione. Nei sistemi MOP, ad esempio JavaScript, c'è in genere un meccanismo per consentire un oggetto per posticipare o 'catena' una chiamata proprietà o richiesta a un altro oggetto, chiamato un "prototipo". In Gemini è utilizzare un'interessante combinazione di tipizzazione statica e MOP chiamato "extensions".
Prototype
In primo luogo, è necessario un tipo di base che identifica i gatti:
public class Cat : Gemini
{
public Cat() : base() { }
public Cat(string name) : base(new { FirstName = name }) { }
}
Si noti che la classe gatto eredita da Gemini, che è quello che consentirà la classe gatto hanno tutti la flessibilità dinamica che è stato discusso finora, infatti, il secondo costruttore di gatto utilizza il costruttore Gemini stesso che è stato utilizzato per creare tutte le istanze dinamiche finora. Questo significa che tutta la prosa precedente detiene ancora per qualsiasi istanza di Cat.
Ma Gemini ci permette anche di fare dichiarazioni di come i gatti possono essere "esteso", così che ogni gatto acquisisce la stessa funzionalità senza dover aggiungere esplicitamente a ogni istanza.
Estensione di una classe
Per un utilizzo pratico di questa funzionalità, presumo che questa è un'applicazione Web che si sta sviluppando. Frequentemente, è necessario HTML -sfuggire i valori di nome, essendo memorizzati e restituiti, al fine di evitare accidentalmente permettendo ad iniezione HTML (o, peggio, SQL injection):
string Htmlize(string incoming)
{
string temp = incoming;
temp = temp.Replace("&", "&");
temp = temp.Replace("<", "<");
temp = temp.Replace(">", ">");
return temp;
}
Questo è un dolore da ricordare su ogni modello di oggetto definito nel sistema; Fortunatamente, MOP permette sistematicamente "raggiungere" e definire nuovi membri comportamentali per gli oggetti del modello, come mostrato Figura 4.
Figura 4 metodi di scrittura senza metodi di scrittura
[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<tag>oter", scooter.FirstNameHtml());
}
Essenzialmente, la chiamata di Extend è l'aggiunta di nuovi metodi per ogni tipo di gatto, in "Html", quindi la proprietà FirstName accessibili in versione HTML-safe chiamando il metodo FirstNameHtml invece.
E questo può essere fatto interamente in fase di esecuzione per qualsiasi Gemini -ereditando tipo nel sistema.
Persistenza e più
Gemini non è destinato a sostituire l'interezza dell'ambiente c# con un mucchio di oggetti dinamicamente risolti — lontano da esso. Nella sua casa di utilizzo, all'interno del framework MVC di quercia, Gemini è utilizzato per aggiungere persistenza e altri comportamenti utili alle classi modello (tra le altre cose) e per aggiungere la convalida senza ingombrare il codice utente o che richiedono le classi parziali. Anche di fuori della quercia, tuttavia, Gemini rappresenta alcuni meccanici design potente, che alcuni lettori possono ricordare da parte 8 del mio serie multiparadigmatica .NET da un po ' indietro (msdn.microsoft.com/magazine/hh205754).
Parlando di quercia, che è su tutti i fronti per la prossima volta, così bastone per vedere come tutta questa roba dinamica si svolge in uno scenario reale.
Codificazione felice!
Ted Neward è un'entità con Neward & Associates LLC. Ha scritto oltre 100 articoli e autore e coautore di una dozzina di libri, tra cui "Professional F # 2.0" (Wrox, 2010). Egli è un MVP F # notato Java esperto e parla a sia Java e .NET conferenze in giro per il mondo. Egli consulta e mentors regolarmente — è possibile contattarlo al ted@tedneward.com o Ted.Neward@neudesic.com se siete interessati ad avere lui venire a lavorare con il vostro team. Ha Blog at blogs.tedneward.com e possono essere seguiti su Twitter a twitter.com/tedneward.
Grazie all'esperto tecnica seguente per la revisione di questo articolo: Amir rossi (miglioramento delle imprese)
Amir rossi è un consulente principale con le imprese a migliorare. Egli è un membro attivo della Comunità di sviluppo e ha esperienza in ASP.NET MVC, HTML5, architetture di resto, Ruby, JavaScript/CoffeeScript, NodeJS, iOS/ObjectiveC e F #. Rossi sono un vero poliglotta con un incrollabile passione per il software. Egli è su Twitter a @amirrajan e all'indirizzo Web github.com/amirrajan, amirrajan.net e improvingenterprises.com.