
Dieser Artikel konzentriert sich auf die doppelte Verwendung der Expression Trees- API - zum Parsen von Ausdrücken und zum Generieren von Code. Die Analyse von Ausdrücken hilft beim Aufbau von Präsentationsstrukturen (sie sind auch Präsentationsstrukturen der problemorientierten Sprache Internal DSL ), und die Codegenerierung ermöglicht es Ihnen, effektiv effektive Funktionen zu erstellen - Befehlssätze, die durch Präsentationsstrukturen festgelegt werden.
Ich werde die dynamische Erstellung von Eigenschaftsiteratoren demonstrieren : serialisieren, kopieren, klonen, gleich . Am Beispiel der Serialisierung zeige ich Ihnen, wie Sie die Serialisierung (im Vergleich zu Stream-Serialisierern) in der klassischen Situation optimieren können, in der "vorläufiges" Wissen zur Verbesserung der Leistung verwendet wird. Die Idee ist, dass beim Aufrufen des Streaming-Serializers immer die Funktion "Nicht-Streaming" verloren geht und genau bekannt ist, welche Baumknoten umgangen werden müssen. Gleichzeitig wird ein solcher Serializer "nicht von Hand", sondern dynamisch, sondern nach vordefinierten Bypass-Regeln erstellt. Das vorgeschlagene Inernal DSL löst das Problem einer kompakten Beschreibung der Regeln zum Durchlaufen von Baumstrukturen von Objekten anhand ihrer Eigenschaften / Eigenschaften (und im allgemeinen Fall: Durchlaufen des Berechnungsbaums mit dem Namen von Knoten) . Der Serializer-Benchmark ist bescheiden, aber es ist wichtig, dass er dem Ansatz, der auf der Verwendung eines bestimmten internen DSL-Includes (einem Dialekt von Include / ThenInclude von EF Core ) und der Verwendung von internem DSL als Ganzes basiert, die notwendige Überzeugungskraft verleiht.
Einführung
Vergleichen Sie:
var p = new Point(){X=-1,Y=1};
Die zweite Methode ist offensichtlich schneller (die Knoten sind bekannt und „in Code eingepfercht“), während die Methode natürlich komplizierter ist. Wenn Sie diesen Code jedoch als Funktion erhalten (dynamisch generiert und kompiliert), wird die Komplexität ausgeblendet (selbst was unklar wird, wird ausgeblendet
Wo ist die Reflexion und wo ist die Laufzeit der Codegenerierung?
var p = new Point(){X=-1,Y=1};
Hier ist JsonManager.ComposeFormatter
das eigentliche Werkzeug . Die Regel, nach der die Strukturumgehung während der Serialisierung generiert wird, ist nicht offensichtlich, klingt jedoch so: "Mit den Standardparametern werden für benutzerdefinierte Werttypen alle Felder der ersten Ebene umgangen." Wenn Sie es explizit festlegen:
Dies ist die Beschreibung von Metadaten über DSL Includes. DSL hat die Analyse der Vor- und Nachteile der Beschreibung von Metadaten aufgeklärt. Da ich jedoch die Form der Aufzeichnung von Metadaten ignoriere, betone ich, dass C # die Möglichkeit bietet, den "idealen Serializer" mithilfe von Ausdrucksbäumen zu kompilieren und zu kompilieren.
Wie er es macht - viel Code und Anleitung zur Codegenerierung von Expression Trees ...Übergang vom formatter
zum serilizer
(bisher ohne Ausdrucksbäume):
Func<StringBuilder, Point, bool> serializer = ...
Der serializer
ist wiederum wie folgt aufgebaut (wenn er mit statischem Code festgelegt ist):
Expression<Func<StringBuilder, Point, bool>> serializerExpression = SerializeAssociativeArray(sb, p, (sb1, t1) => SerializeValueProperty(sb1, t1, "X", o => oX, SerializeValueToString), (sb4, t4) => SerializeValueProperty(sb1, t1, "Y", o => oY, SerializeValueToString) ); Func<StringBuilder, Point, bool> serializer = serializerExpression.Compile();
Warum ist es so "funktional", warum können Sie nicht zwei Felder durch ein Semikolon serialisieren? Kurz gesagt: weil dieser Ausdruck einer Variablen vom Typ Expression<Func<StringBuilder, Box, bool>>
zugewiesen werden kann, ein Semikolon jedoch nicht zulässig ist.
Warum konnte ich Func<StringBuilder, Point, bool> serializer = (sb,p)=>SerializeAssociativeArray(sb,p,...
nicht direkt schreiben? Es ist möglich, aber ich demonstriere nicht das Erstellen eines Delegaten, sondern einer Assembly (in diesem Fall statischer Code). Ausdrucksbaum, mit einer Kompilierung für den Delegaten in der Zukunft, in der Praxis wird serializerExpression
auf eine völlig andere Weise festgelegt - dynamisch (unten).
Was jedoch in der Lösung selbst wichtig ist: SerializeAssociativeArray
akzeptiert ein Array von params Func<..> propertySerializers
entsprechend der Anzahl der zu params Func<..> propertySerializers
Knoten. Das Umgehen einiger von ihnen kann durch SerializeValueProperty-Serialisierer von Blättern (Akzeptieren des SerializeValueToString
Formatierers) und andere wiederum durch SerializeAssociativeArray
( SerializeAssociativeArray
Zweige) festgelegt werden, und somit wird ein Iterator (Baum) des Durchlaufs erstellt.
Wenn Point die NextPoint-Eigenschaft enthielt:
var @delegate = SerializeAssociativeArray(sb, p, (sb1, t1) => SerializeValueProperty(sb1, t1, "X", o => oX, SerializeValueToString), (sb4, t4) => SerializeValueProperty(sb1, t1, "Y", o => oY, SerializeValueToString), (sb4, t4) => SerializeValueProperty(sb1, t1, "NextPoint", o => o.NextPoint, (sb4, t4) =>SerializeAssociativeArray(sb1, p1, (sb1, t1) => SerializeValueProperty(sb2, t2, "X", o => oX, SerializeValueToString), (sb4, t4) => SerializeValueProperty(sb2, t2, "Y", o => oY, SerializeValueToString) ) ) );
Das Gerät der drei Funktionen SerializeAssociativeArray
, SerializeValueProperty
, SerializeValueToString
nicht kompliziert:
Serialisieren ... public static bool SerializeAssociativeArray<T>(StringBuilder stringBuilder, T t, params Func<StringBuilder, T, bool>[] propertySerializers) { var @value = false; stringBuilder.Append('{'); foreach (var propertySerializer in propertySerializers) { var notEmpty = propertySerializer(stringBuilder, t); if (notEmpty) { if (!@value) @value = true; stringBuilder.Append(','); } }; stringBuilder.Length--; if (@value) stringBuilder.Append('}'); return @value; } public static bool SerializeValueProperty<T, TProp>(StringBuilder stringBuilder, T t, string propertyName, Func<T, TProp> getter, Func<StringBuilder, TProp, bool> serializer) where TProp : struct { stringBuilder.Append('"').Append(propertyName).Append('"').Append(':'); var value = getter(t); var notEmpty = serializer(stringBuilder, value); if (!notEmpty) stringBuilder.Length -= (propertyName.Length + 3); return notEmpty; } public static bool SerializeValueToString<T>(StringBuilder stringBuilder, T t) where T : struct { stringBuilder.Append(t); return true; }
Viele Details werden hier nicht angegeben (Listenunterstützung, Referenztyp und nullbar). Und doch ist es klar, dass ich wirklich json in der Ausgabe bekomme und alles andere noch mehr von den Standardfunktionen SerializeArray
, SerializeNullable
, SerializeRef
.
Es war ein statischer Ausdrucksbaum, nicht dynamisch, nicht in C # ausgewertet .
Sie können sehen, wie der Ausdrucksbaum in zwei Schritten dynamisch erstellt wird:
Schritt 1 - Der Dekompiler überprüft den durch Expression<T>
zugewiesenen Code

Dies wird Sie natürlich beim ersten Mal überraschen. Nichts ist klar, aber Sie können sehen, wie die ersten vier Zeilen so etwas zusammensetzen:
("sb","t") .. SerializeAssociativeArray..
Dann wird die Verbindung mit dem Quellcode erfasst. Und es sollte klar werden, dass Sie, wenn Sie einen solchen Datensatz beherrschen (indem Sie 'Expression.Const', 'Expression.Parameter', 'Expression.Call', 'Expression.Lambda' usw. kombinieren), wirklich dynamisch verknüpfen können - jede Umgehung von Knoten (basierend auf Metadaten). Dies ist in C # eval .
Schritt 2 - folgen Sie diesem Link ,
Der gleiche Dekompiler-Code, aber vom Menschen kompiliert.
Nur der Autor des Dolmetschers muss sich mit dieser Perlenstickerei befassen. Alle diese Künste verbleiben in der Serialisierungsbibliothek . Es ist wichtig zu lernen, dass Sie Bibliotheken bereitstellen können, die dynamisch kompilierte effiziente Funktionen in C # (und .NET Standard) generieren.
Ein Streaming-Serializer überholt jedoch eine dynamisch generierte Funktion, wenn Sie die Kompilierung jedes Mal vor der Serialisierung aufrufen (das Kompilieren im ComposeFormatter
ist eine kostspielige Operation). Sie können den Link jedoch speichern und wiederverwenden:
static Func<Point, string> formatter = JsonManager.ComposeFormatter<Point>(); public string Get(Point p){
Wenn Sie einen Serializer vom anonymen Typ zur Wiederverwendung erstellen und speichern müssen, benötigen Sie zusätzliche Infrastruktur:
static CachedFormatter cachedFormatter = new CachedFormatter(); public string Get(List<Point> list){
Danach berücksichtigen wir zuversichtlich die erste Mikrooptimierung für uns selbst und akkumulieren, akkumulieren, akkumulieren ... Wer ist der Witz, wer nicht, aber bevor ich zu der Frage übergehe, dass der neue Serializer neu ist, behebe ich den offensichtlichen Vorteil - er wird schneller sein.
Was dafür?
Das DSL enthält Interpreter in Serilize (und auf die gleiche Weise, wie es in Iteratoren gleich ist, kopieren, klonen - und das wird auch ungefähr sein), erforderte die folgenden Kosten:
1 - Kosten der Infrastruktur zum Speichern von Links zu kompiliertem Code.
Diese Kosten sind im Allgemeinen nicht erforderlich, ebenso wie die Verwendung von Ausdrucksbäumen bei der Kompilierung. Der Interpreter kann einen Serialisierer für Reflexe erstellen und ihn sogar so weit lecken, dass er sich der Geschwindigkeit in Bezug auf Stream-Serialisierer (übrigens Kopieren, Klonen und) nähert Gleichheit wird weder durch Ausdrucksbäume gesammelt noch geleckt, es gibt keine solche Aufgabe, im Gegensatz zum Überholen von ServiceStack und Json.NET im Rahmen der wohlverstandenen Aufgabe der Optimierung der Serialisierung in json - eine notwendige Voraussetzung für die Präsentation einer neuen Lösung).
2 - Sie müssen Abstraktionslecks sowie ein ähnliches Problem berücksichtigen: Änderungen in der Semantik im Vergleich zu vorhandenen Lösungen.
Zum Beispiel benötigen Point und IEnumerable zwei verschiedene Serializer zum Serialisieren.
var formatter1 = JsonManager.ComposeFormatter<Point>(); var formatter2 = JsonManager.ComposeEnumerableFormatter<Point>();
Oder: "Funktioniert die Schließung?". Es funktioniert, nur der Knoten muss einen Namen (eindeutig) festlegen:
string DATEFORMAT= "YYYY"; var formatter3 = JsonManager.ComposeFormatter<Record>( chain => chain .Include(i => i.RecordId) .Include(i => i.CreatedAt.ToString(DATEFORMAT) , "CreatedAt"); );
Dieses Verhalten wird ComposeFormatter
vom internen Gerät des ComposeFormatter
Interpreters ComposeFormatter
.
Kosten dieser Art sind unvermeidlich böse. Darüber hinaus wird festgestellt, dass durch die Erweiterung der Funktionalität und den Umfang des internen DSL auch Abstraktionslecks zunehmen. Es wird sicherlich den Entwickler von internem DSL unterdrücken, hier müssen Sie sich mit einer philosophischen Stimmung eindecken.
Für den Benutzer werden Abstraktionslecks durch die Kenntnis der technischen Details von internem DSL ( was ist zu erwarten? ) Und des Funktionsumfangs eines bestimmten DSL und seiner Interpreter ( was im Gegenzug? ) Überwunden . Daher die Antwort auf die Frage: "Lohnt es sich, internes DSL zu erstellen und zu verwenden?" Es kann nur eine Geschichte über die Funktionalität eines bestimmten DSL geben - über all seine Details und Annehmlichkeiten und seine möglichen Anwendungen (Dolmetscher), d. H. eine Geschichte über die Überwindung von Kosten.
In diesem Sinne kehre ich zur Effektivität eines bestimmten DSL-Includes zurück.
Eine signifikant höhere Effizienz wird erreicht, wenn das Ziel darin besteht, das Tripel (DTO, Transformation in DTO, Serialisierung von DTO) durch eine lokal detaillierte und generierte Serialisierungsfunktion zu ersetzen. Am Ende des Dualismus-Funktionsobjekts können Sie "DTO ist eine solche Funktion" sagen und ein Ziel festlegen: lernen, wie eine DTO-Funktion festgelegt wird.
Die Serialisierung muss konfiguriert sein:
- Bypass-Baum (um die Knoten zu beschreiben, über die die Serialisierung stattfinden wird, um das Problem der kreisförmigen Verknüpfungen zu lösen), weisen Sie im Fall von Blättern einen Formatierer (nach Typ) zu.
- Die Regel für das Einschließen von Blättern (falls nicht angegeben) - Eigenschaft gegen Felder? schreibgeschützt?
- Um sowohl einen Zweig (einen Knoten mit Navigation) als auch ein Blatt angeben zu können, muss nicht nur MemberExpression (
e=>e.Name
), sondern im Allgemeinen eine beliebige Funktion (`e => e.Name.ToUpper ()," MyMemberName ") den Formatierer auf einen bestimmten Wert festlegen Knoten.
Weitere Möglichkeiten zur Erhöhung der Flexibilität:
- Serialisieren Sie ein Blatt, das eine JSON-Zeichenfolge "as is" enthält (spezieller Zeichenfolgenformatierer).
- setze Formatierer auf Gruppen, d.h. ganze Zweige, in diesem Zweig wie diesem - auf eine andere Weise anders (zum Beispiel hier mit der Zeit und in diesem ohne Zeit).
Überall können Konstruktionen wie Bypass-Baum, Zweig, Blatt und all dies mit DSL Includes geschrieben werden.
DSL beinhaltet
Da jeder mit EF Core vertraut ist, sollte die Bedeutung der folgenden Ausdrücke sofort erfasst werden (dies ist eine Teilmenge von xpath).
Hier sind die Knoten "mit Navigation" - "Zweige".
Die Antwort auf die Frage, welche Knoten "Blätter" (Felder / Eigenschaften) in dem so definierten Baum enthalten sind - keine. Um Blätter einzuschließen, müssen Sie sie entweder explizit auflisten:
Include<User> include2 = chain=> chain .Include(e => e.UserName)
Oder fügen Sie dynamisch nach der Regel über einen spezialisierten Interpreter hinzu:
Hier ist die Regel eine Regel, die von ChainNode.Type ausgewählt werden kann, d.h. nach Art des vom Knoten zurückgegebenen Ausdrucks (ChainNode - interne Darstellung von DSL Includes, auf die später noch eingegangen wird) Eigenschaften (MemberInfo) für die Teilnahme an der Serialisierung, z. Nur eine Eigenschaft oder nur eine Lese- / Schreibeigenschaft oder nur diejenigen, für die es einen Formatierer gibt, können Sie aus einer Liste von Typen auswählen, und sogar der Include-Ausdruck selbst kann eine Regel festlegen (wenn er Blattknoten auflistet - d. h. die Form der Baumverknüpfung). .
Oder ... überlassen Sie es dem Benutzerinterpreter, der entscheidet, was mit den Knoten geschehen soll. DSL Includes ist nur ein Metadatensatz. Wie dieser Datensatz interpretiert wird, hängt vom Interpreter ab. Er kann die Metadaten so interpretieren, wie er möchte, bis er sie ignoriert. Einige Interpreter führen die Aktion selbst aus, während andere eine Funktion erstellen, die bereit ist, sie auszuführen (über Expression Tree oder sogar Reflection.Emit). Ein gutes internes DSL ist für den universellen Gebrauch und die Existenz vieler Dolmetscher konzipiert, von denen jeder seine eigenen Besonderheiten und Abstraktionslecks aufweist.
Code, der internes DSL verwendet, kann sich stark von dem unterscheiden, was er zuvor war.
Out of the Box
Integration mit EF Core.
Die laufende Aufgabe besteht darin, "zyklische Links abzubrechen", um nur das zu starten, was im Include-Ausdruck angegeben ist:
static CachedFormatter cachedFormatter1 = new CachedFormatter(); string GetJson() { using (var dbContext = GetEfCoreContext()) { string json = EfCoreExtensions.ToJsonEf<User>(cachedFormatter1, dbContext, chain=>chain .IncludeAll(e => e.Roles) .ThenIncludeAll(e => e.Privileges)); } }
ToJsonEf
akzeptiert die Navigationssequenz, verwendet sie beim Serialisieren (wählt Blätter nach der Regel "Standard für EF Core" aus, dh öffentliche Lese- / Schreibeigenschaft), interessiert sich für das Modell - wobei string / json Feldformatierer verwendet, um sie unverändert einzufügen Standardmäßig (Byte [] pro Zeichenfolge, Datum / Uhrzeit in ISO usw.). Daher muss er IQuaryable unter sich ausführen.
In dem Fall, in dem das Ergebnis transformiert wird, ändern sich die Regeln - es ist nicht erforderlich, DSL Includes zu verwenden, um die Navigation anzugeben (wenn die Regel nicht wiederverwendet wird), ein anderer Interpreter wird verwendet und die Konfiguration erfolgt lokal:
static CachedFormatter cachedFormatter1 = new CachedFormatter(); string GetJson() { using (var dbContext = GetEfCoreContext()) { var json = dbContext.ParentRecords
Es ist klar, dass all diese Details, all dies ist „standardmäßig“, nur dann berücksichtigt werden können, wenn Sie sie wirklich brauchen und / oder wenn dies Ihr eigener Dolmetscher ist. Auf der anderen Seite kehren wir noch einmal zu den Pluspunkten zurück: DTO wird nicht durch Code verschmiert, wird durch eine bestimmte Funktion spezifiziert, Interpreter sind universell. Der Code wird kleiner - das ist gut.
Es ist zu warnen : Obwohl es den Anschein hat, dass vorläufiges Wissen in ASP immer verfügbar ist und ein Streaming-Serializer in der Welt des Webs, in der sogar Datenbanken Daten in JSON übertragen, nicht unbedingt erforderlich ist, ist die Verwendung von DSL Includes in ASP MVC nicht die einfachste Geschichte . Wie man funktionale Programmierung mit ASP MVC kombiniert, verdient eine separate Studie.
In diesem Artikel beschränke ich mich auf die Feinheiten von DSL Includes. Ich werde sowohl neue Funktionen als auch das Durchsickern von Abstraktionen zeigen, um zu zeigen, dass das Problem der Analyse von "Kosten und Akquisitionen" tatsächlich erschöpfbar ist.
Mehr DSL beinhaltet
Include<Point> include = chain => chain.Include(e=>eX).Include(e=>eY);
Dies unterscheidet sich von EF Core Includes, die auf statischen Funktionen basieren, die nicht Variablen zugewiesen und als Parameter übergeben werden können. DSL Includes selbst entstand aus der Notwendigkeit heraus, "include" in meine Implementierung der Repository-Vorlage zu übergeben, ohne die Typinformationen zu verschlechtern, die bei der Standardisierung in Zeichenfolgen aufgetreten wären.
Der dramatischste Unterschied besteht immer noch in der Ernennung. EF Core Includes - Einbeziehung von Navigationseigenschaften (Knoten von Zweigen), DSL Includes - Aufzeichnung der Durchquerung eines Berechnungsbaums, wobei dem Ergebnis jeder Berechnung ein Name (Pfad) zugewiesen wird.
Die interne Darstellung von EF Core Includes ist eine Liste von Zeichenfolgen, die von MemberExpression.Member empfangen wurden (Der durch e=>User.Name
angegebene e=>User.Name
kann nur [MemberExpression] sein ( https://msdn.microsoft.com/en-us/library/system.linq.expressions). Mitgliedsausdruck (v = vs. 110) .aspx und in internen Ansichten wird nur die Namenszeile gespeichert).
In DSL Includes besteht die interne Darstellung aus den Klassen ChainNode und ChainMemberNode , die den gesamten Ausdruck (z. B. e=>User.Name
) e=>User.Name
, der so wie er ist in den Ausdrucksbaum integriert ist. Genau aus diesem Grund unterstützt DSL Includes sowohl Felder als auch Benutzerwerttypen und Funktionsaufrufe:
Ausführung von Funktionen:
Include<User> include = chain => chain .Include(i => i.UserName) .Include(i => i.Email.ToUpper(),"EAddress");
Was damit zu tun ist, hängt vom Dolmetscher ab. CreateFormatter- gibt {"UserName": "John", "EAddress": "JOHN@MAIL.COM"} zurück
Die Ausführung kann auch nützlich sein, um das Durchlaufen von nullbaren Strukturen festzulegen.
Include<StrangePointF> include = chain => chain .Include(e => e.NextPoint)
DSL Includes enthält auch einen kurzen Eintrag für die mehrstufige ThenIncluding-Problemumgehung.
Include<User> include = chain => chain .Include(i => i.UserName) .IncludeAll(i => i.Groups)
vergleiche mit
Include<User> include = chain => chain .Include(i => i.UserName) .IncludeAll(i => i.Groups) .ThenInclude(e => e.GroupName) .IncludeAll(i => i.Groups) .ThenInclude(e => e.GroupDescription) .IncludeAll(i => i.Groups) .ThenInclude(e => e.AdGroup);
Und auch hier gibt es ein Abstraktionsleck. Wenn ich die Navigation in dieser Form notiert habe, sollte ich wissen, wie ein Interpreter funktioniert, der QuaryableExtensions aufruft. Und er übersetzt die Aufrufe in Include und ThenInclude in Include "string". Was kann wichtig sein (Sie müssen bedenken).
Algebra Include-Ausdrücke .
Einschlussausdrücke können sein:
Vergleichen var b1 = InlcudeExtensions.IsEqualTo(include1, include2); var b2 = InlcudeExtensions.IsSubTreeOf(include1, include2); var b3 = InlcudeExtensions.IsSuperTreeOf(include1, include2);
Klon var include2 = InlcudeExtensions.Clone(include1);
Zusammenführen var include3 = InlcudeExtensions.Merge(include1, include2);
In XPath-Listen konvertieren - Alle Pfade zu Blättern IReadOnlyCollection<string> paths1 = InlcudeExtensions.ListLeafXPaths(include);
usw.
Die gute Nachricht ist: Es gibt keine Abstraktionslecks, hier wird die Ebene der reinen Abstraktion erreicht. Es gibt Metadaten und arbeiten mit Metadaten.
Dialektik
Mit DSL Includes können Sie eine neue Abstraktionsebene erreichen. Zum Zeitpunkt des Erreichens besteht jedoch die Notwendigkeit, zur nächsten Ebene zu gelangen: Include-Ausdrücke selbst generieren.
In diesem Fall ist es nicht erforderlich, DSL als fließende Kette zu generieren. Sie müssen lediglich interne Repräsentationsstrukturen erstellen.
var root = new ChainNode(typeof(Point)); var child = new ChainPropertyNode( typeof(int), expression: typeof(Point).CreatePropertyLambda("X"), memberName:"X", isEnumerable:false, parent:root ); root.Children.Add("X", child);
Sie können Präsentationsstrukturen auch an Dolmetscher übergeben. Warum enthält die fließende DSL-Aufzeichnung dann überhaupt? Dies ist eine rein spekulative Frage, deren Antwort darin besteht, dass es in der Praxis nur möglich ist, die interne Repräsentation (und auch die Entwicklung) zusammen mit der Entwicklung von DSL zu entwickeln (d. H. Eine kurze Ausdrucksaufzeichnung, die für statischen Code geeignet ist). Dies wird noch einmal näher an der Schlussfolgerung gesagt.
Kopieren, Klonen, Gleich
All dies gilt für Interpreter-Ausdrucksinterpreter, die Copy , Clone und Iteratoren implementieren.
GleichVergleich nur auf Blättern aus dem Include-Ausdruck.
Verstecktes semantisches Problem: Auswerten oder nicht in der Liste sortieren
Include<User> include = chain=>chain.Include(e=>e.UserId).IncludeAll(e=>e.Groups).ThenInclude(e=>e.GroupId) bool b1 = ObjectExtensions.Equals(user1, user2, include); bool b2 = ObjectExtensions.EqualsAll(userList1, userList2, include);
KlonÜbergeben Sie Ausdrucksknoten. Eigenschaften, die der Regel entsprechen, werden kopiert.
Include<User> include = chain=>chain.Include(e=>e.UserId).IncludeAll(e=>e.Groups).ThenInclude(e=>e.GroupId) var newUser = ObjectExtensions.Clone(user1, include, leafRule1); var newUserList = ObjectExtensions.CloneAll(userList1, leafRule1);
Möglicherweise gibt es einen Dolmetscher, der Blatt aus Includes auswählt. Warum wird es gemacht - durch eine separate Regel? Was war ähnlich der Semantik von ObjectExtensions.Copy
KopierenDurch Knoten gehen - ein Ausdruckszweig und die Identifizierung durch Blattknoten. Eigenschaften, die der Regel entsprechen, werden kopiert (ähnlich wie beim Klonen).
Include<User> include = chain=>chain.IncludeAll(e=>e.Groups); ObjectExtensions.Copy(user1, user2, include, supportedLeafsRule); ObjectExtensions.CopyAll(userList1, userList2, include, supportedLeafsRule);
Möglicherweise gibt es einen Interpreter, der Blatt aus Includes auswählt. Warum wird es gemacht - durch eine separate Regel? ObjectExtensions.Copy ( — include , supportedLeafsRule — ).
copy / clone :
- readonly , Tuple<,> Anonymous Type. , .
- (. IEnumerable ) — public .
- expression include-, — .
- " " .
DSL , .. . , Tuple<,>
, .. c readonly , ValueTuple<,>
c writabale ( ).
, ( Expression Trees) Includes — . Include DSL .
Detach, FindDifferences ..
run-time, .cs ?
.cs , , run-time :
- ( , , source control).
- , , , — .
- .
- " ". dev time , : "" "" , "" , , "" .
Roslyn', . Typescript ( DTO , .. ) — DSL Includes Roslyn' ( ) — typescript ( ). " " " " .cs ( Expression Trees).
: run time — , . ( Expression Trees).
Expression Trees
Internal DSL Expression Tree :
LambdaExpression.Compile
Lambda . , . , "" expression tree, CallExpression — LambdaExpression, (. LambdaExpression) ConstantExpression. , " /" — , Expression Trees.
ssmbly , ( 10 ) ( assembly , — ). , , , — .
, ( ), , . : . — — .cs .
— 600 15 . JSON.NET, ServiceStack reflection' GetProperties().
dslComposeFormatter — ComposeFormatter , .
BenchmarkDotNet =v0.10.14, OS=Windows 10.0.17134
Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), 1 CPU, 4 logical and 4 physical cores
.NET Core SDK=2.1.300
Method | Mean | Fehler | StdDev | Min | Max | Median | Allocated |
---|
dslComposeFormatter | 2.208 ms | 0.0093 ms | 0.0078 ms | 2.193 ms | 2.220 ms | 2.211 ms | 849.47 KB |
JsonNet_Default | 2.902 ms | 0.0160 ms | 0.0150 ms | 2.883 ms | 2.934 ms | 2.899 ms | 658.63 KB |
JsonNet_NullIgnore | 2.944 ms | 0.0089 ms | 0.0079 ms | 2.932 ms | 2.960 ms | 2.942 ms | 564.97 KB |
JsonNet_DateFormatFF | 3.480 ms | 0.0121 ms | 0.0113 ms | 3.458 ms | 3.497 ms | 3.479 ms | 757.41 KB |
JsonNet_DateFormatSS | 3.880 ms | 0.0139 ms | 0.0130 ms | 3.854 ms | 3.899 ms | 3.877 ms | 785.53 KB |
ServiceStack_SerializeToString | 4.225 ms | 0.0120 ms | 0.0106 ms | 4.201 ms | 4.243 ms | 4.226 ms | 805.13 KB |
fake_expressionManuallyConstruted | 54.396 ms | 0.1758 ms | 0.1644 ms | 54.104 ms | 54.629 ms | 54.383 ms | 7401.58 KB |
fake_expressionManuallyConstruted — expression ( ).
DSL : DSL ; Internal DSL run-time .
Expression Tree .NET Standard .
Expression Trees Internal DSL Fluent API. # .
fluent ( Expression Trees), Internal DSL # fluent, "" Expression Trees.
Expression Trees DSL Includes ( , ), / run-time — (run-time ).
Internal DSL : - serialize , copy , clone , equals "" . , " ", . : includes ( ) , ( , ).
Fazit
DSL Includes DTO — ( json). , , , " ", . = .
Internal DSL , DSL, Internal DSL ( Expression) ( Expression Tree).
DSL Includes json ComposeFormatter DashboardCodes.Routines nuget GitHub.