In C # 4.0 wurde ein neuer Typ namens "dynamisch" eingeführt. Es klingt alles gut, aber wofür würde ein Programmierer es verwenden?
Gibt es eine Situation, in der es den Tag retten kann?
In C # 4.0 wurde ein neuer Typ namens "dynamisch" eingeführt. Es klingt alles gut, aber wofür würde ein Programmierer es verwenden?
Gibt es eine Situation, in der es den Tag retten kann?
Antworten:
Das dynamische Schlüsselwort ist neu in C # 4.0 und wird verwendet, um dem Compiler mitzuteilen, dass sich der Typ einer Variablen ändern kann oder dass er erst zur Laufzeit bekannt ist. Stellen Sie sich vor, Sie könnten mit einem Objekt interagieren, ohne es wirken zu müssen.
dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!
Beachten Sie, dass wir cust nicht als Typ Kunde deklarieren oder deklarieren mussten. Da wir es als dynamisch deklariert haben, übernimmt die Laufzeit und sucht und legt die FirstName-Eigenschaft für uns fest. Wenn Sie jetzt eine dynamische Variable verwenden, geben Sie natürlich die Überprüfung des Compilertyps auf. Dies bedeutet, dass der Aufruf cust.MissingMethod () kompiliert wird und erst zur Laufzeit fehlschlägt. Das Ergebnis dieser Operation ist eine RuntimeBinderException, da MissingMethod in der Customer-Klasse nicht definiert ist.
Das obige Beispiel zeigt, wie dynamisch beim Aufrufen von Methoden und Eigenschaften funktioniert. Eine weitere leistungsstarke (und möglicherweise gefährliche) Funktion ist die Wiederverwendung von Variablen für verschiedene Datentypen. Ich bin mir sicher, dass die Programmierer von Python, Ruby und Perl sich eine Million Möglichkeiten vorstellen können, dies zu nutzen, aber ich habe C # so lange verwendet, dass es sich für mich einfach "falsch" anfühlt.
dynamic foo = 123;
foo = "bar";
OK, Sie werden also wahrscheinlich nicht sehr oft Code wie den oben genannten schreiben. Es kann jedoch vorkommen, dass die variable Wiederverwendung nützlich sein oder einen schmutzigen Teil des Legacy-Codes bereinigen kann. Ein einfacher Fall, auf den ich oft stoße, ist das ständige Umschalten zwischen Dezimal und Doppel.
decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");
Die zweite Zeile wird nicht kompiliert, da 2.5 als Double eingegeben wird, und Zeile 3 wird nicht kompiliert, da Math.Sqrt ein Double erwartet. Natürlich müssen Sie nur Ihren Variablentyp umwandeln und / oder ändern, aber es kann Situationen geben, in denen die Verwendung von Dynamik sinnvoll ist.
dynamic foo = GetDecimalValue(); // still returns a decimal
foo = foo / 2.5; // The runtime takes care of this for us
foo = Math.Sqrt(foo); // Again, the DLR works its magic
string bar = foo.ToString("c");
Weitere Informationen finden Sie unter: http://www.codeproject.com/KB/cs/CSharp4Features.aspx
dynamic
in c # zum Lösen von Problemen zu verwenden, die durch Standard-c # -Funktionen und statische Typisierung oder höchstens mit Typinferenz ( var
) gelöst werden können (vielleicht sogar noch besser ). dynamic
sollte nur verwendet werden, wenn es um Interoperabilitätsprobleme mit dem DLR geht. Wenn Sie Code in einer statisch typisierten Sprache wie c # schreiben, tun Sie dies und emulieren Sie keine dynamische Sprache. Das ist einfach hässlich.
dynamic
Variablen in Ihrem Code stark nutzen, wo Sie sie nicht benötigen (wie in Ihrem Beispiel mit dem Squareroot), geben Sie die Fehlerprüfung bei der Kompilierungszeit auf. Stattdessen erhalten Sie jetzt mögliche Laufzeitfehler.
Das dynamic
Schlüsselwort wurde zusammen mit vielen anderen neuen Funktionen von C # 4.0 hinzugefügt, um die Kommunikation mit Code zu vereinfachen, der in anderen Laufzeiten mit unterschiedlichen APIs lebt oder von diesen stammt.
Nehmen Sie ein Beispiel.
Wenn Sie ein COM-Objekt wie das Word.Application
Objekt haben und ein Dokument öffnen möchten, enthält die entsprechende Methode nicht weniger als 15 Parameter, von denen die meisten optional sind.
Um diese Methode aufzurufen, benötigen Sie etwa Folgendes (ich vereinfache, dies ist kein tatsächlicher Code):
object missing = System.Reflection.Missing.Value;
object fileName = "C:\\test.docx";
object readOnly = true;
wordApplication.Documents.Open(ref fileName, ref missing, ref readOnly,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing);
Alle diese Argumente notieren? Sie müssen diese übergeben, da C # vor Version 4.0 keine Vorstellung von optionalen Argumenten hatte. In C # 4.0 wurde die Arbeit mit COM-APIs vereinfacht, indem Folgendes eingeführt wurde:
ref
optional für COM - APIsDie neue Syntax für den obigen Aufruf wäre:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
Sehen Sie, wie viel einfacher es aussieht, wie viel lesbarer es wird?
Lassen Sie uns das auseinander brechen:
named argument, can skip the rest
|
v
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
^ ^
| |
notice no ref keyword, can pass
actual parameter values instead
Die Magie ist, dass der C # -Compiler jetzt den erforderlichen Code einfügt und zur Laufzeit mit neuen Klassen arbeitet, um fast genau das zu tun, was Sie zuvor getan haben, aber die Syntax wurde Ihnen verborgen, jetzt können Sie sich auf das konzentrieren was und nicht so sehr auf das wie . Anders Hejlsberg sagt gern, dass man verschiedene "Beschwörungsformeln" aufrufen muss, was eine Art Wortspiel für die Magie des Ganzen ist, bei dem man normalerweise mit den Händen winken und einige magische Wörter in der richtigen Reihenfolge sagen muss um eine bestimmte Art von Zauber in Gang zu bringen. Die alte API-Art, mit COM-Objekten zu kommunizieren, war eine Menge davon. Sie mussten durch viele Rahmen springen, um den Compiler zu überreden, den Code für Sie zu kompilieren.
In C # vor Version 4.0 wird es noch schlimmer, wenn Sie versuchen, mit einem COM-Objekt zu sprechen, für das Sie keine Schnittstelle oder Klasse haben IDispatch
. Sie haben lediglich eine Referenz.
Wenn Sie nicht wissen, was es ist, IDispatch
ist im Grunde Reflexion für COM-Objekte. Mit einer IDispatch
Schnittstelle können Sie das Objekt "Wie lautet die ID-Nummer für die als Speichern bekannte Methode?" Fragen, Arrays eines bestimmten Typs erstellen, die die Argumentwerte enthalten, und schließlich eine Invoke
Methode auf der IDispatch
Schnittstelle aufrufen, um die Methode aufzurufen, wobei alle übergeben werden die Informationen, die Sie zusammengetragen haben.
Die obige Speichermethode könnte folgendermaßen aussehen (dies ist definitiv nicht der richtige Code):
string[] methodNames = new[] { "Open" };
Guid IID = ...
int methodId = wordApplication.GetIDsOfNames(IID, methodNames, methodNames.Length, lcid, dispid);
SafeArray args = new SafeArray(new[] { fileName, missing, missing, .... });
wordApplication.Invoke(methodId, ... args, ...);
All dies, um nur ein Dokument zu öffnen.
VB hatte vor langer Zeit optionale Argumente und Unterstützung für das meiste davon, also diesen C # -Code:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
ist im Grunde nur C #, das VB in Bezug auf Ausdruckskraft einholt, aber es richtig macht, indem es erweiterbar gemacht wird, und nicht nur für COM. Dies ist natürlich auch für VB.NET oder eine andere Sprache verfügbar, die auf der .NET-Laufzeit basiert.
Weitere Informationen zur IDispatch
Benutzeroberfläche finden Sie auf Wikipedia: IDispatch, wenn Sie mehr darüber lesen möchten. Es ist wirklich blutiges Zeug.
Was ist jedoch, wenn Sie mit einem Python-Objekt sprechen möchten? Dafür gibt es eine andere API als die für COM-Objekte verwendete. Da Python-Objekte ebenfalls dynamischer Natur sind, müssen Sie auf Reflection Magic zurückgreifen, um die richtigen Methoden zum Aufrufen, ihre Parameter usw. zu finden, nicht jedoch das .NET Reflexion, etwas, das für Python geschrieben wurde, ziemlich ähnlich dem obigen IDispatch-Code, nur ganz anders.
Und für Ruby? Noch eine andere API.
JavaScript? Gleiches Angebot, auch dafür unterschiedliche API.
Das dynamische Schlüsselwort besteht aus zwei Dingen:
dynamic
dynamic
Schlüsselwort erforderlich ist, und die Aufrufe der richtigen Vorgehensweise zuordnen. Die API ist sogar dokumentiert. Wenn Sie also Objekte haben, die aus einer nicht abgedeckten Laufzeit stammen, können Sie sie hinzufügen.Das dynamic
Schlüsselwort soll jedoch keinen vorhandenen Nur-.NET-Code ersetzen. Sicher, Sie können es tun, aber es wurde aus diesem Grund nicht hinzugefügt, und die Autoren der C # -Programmiersprache mit Anders Hejlsberg an der Spitze waren äußerst fest davon überzeugt, dass sie C # immer noch als stark typisierte Sprache betrachten und nicht opfern werden dieses Prinzip.
Dies bedeutet, dass Sie zwar Code wie folgt schreiben können:
dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;
und lassen Sie es kompilieren, es war nicht als eine Art magisches System gedacht, mit dem Sie herausfinden können, was Sie zur Laufzeit gemeint haben.
Der ganze Zweck war es, es einfacher zu machen, mit anderen Arten von Objekten zu sprechen.
Im Internet gibt es viel Material über das Schlüsselwort, Befürworter, Gegner, Diskussionen, Beschimpfungen, Lob usw.
Ich schlage vor, Sie beginnen mit den folgenden Links und googeln dann für mehr:
dynamic
wurde hinzugefügt, um andere Ökosysteme dabei zu unterstützen, wie reflexionsähnliche Methodenaufrufe durchgeführt werden können, und um eine Art Black-Box-Ansatz für Datenstrukturen bereitzustellen, mit dem dies dokumentiert werden kann.
Ich bin überrascht, dass niemand den Mehrfachversand erwähnt hat . Die übliche Art, dies zu umgehen , ist das Besuchermuster. Dies ist nicht immer möglich, sodass Sie gestapelte is
Schecks erhalten.
Hier ist ein Beispiel aus dem wirklichen Leben für eine eigene Anwendung. Anstatt zu tun:
public static MapDtoBase CreateDto(ChartItem item)
{
if (item is ElevationPoint) return CreateDtoImpl((ElevationPoint)item);
if (item is MapPoint) return CreateDtoImpl((MapPoint)item);
if (item is MapPolyline) return CreateDtoImpl((MapPolyline)item);
//other subtypes follow
throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
Sie machen:
public static MapDtoBase CreateDto(ChartItem item)
{
return CreateDtoImpl(item as dynamic);
}
private static MapDtoBase CreateDtoImpl(ChartItem item)
{
throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
private static MapDtoBase CreateDtoImpl(MapPoint item)
{
return new MapPointDto(item);
}
private static MapDtoBase CreateDtoImpl(ElevationPoint item)
{
return new ElevationDto(item);
}
Beachten Sie, dass es sich im ersten Fall ElevationPoint
um eine Unterklasse von handelt MapPoint
und wenn es nicht platziert wird, bevor MapPoint
es niemals erreicht wird. Dies ist bei Dynamic nicht der Fall, da die Methode mit der engsten Übereinstimmung aufgerufen wird.
Wie Sie dem Code entnehmen können, war diese Funktion praktisch, als ich die Übersetzung von ChartItem-Objekten in ihre serialisierbaren Versionen durchführte. Ich wollte meinen Code nicht mit Besuchern verschmutzen und ich wollte meine ChartItem
Objekte auch nicht mit nutzlosen serialisierungsspezifischen Attributen verschmutzen .
is
übereinander.
magic
; Es gibt keine Magie.
Es erleichtert statisch typisierten Sprachen (CLR) die Zusammenarbeit mit dynamischen Sprachen (Python, Ruby ...), die auf dem DLR (Dynamic Language Runtime) ausgeführt werden (siehe MSDN) :
Beispielsweise können Sie den folgenden Code verwenden, um einen Zähler in XML in C # zu erhöhen.
Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);
Bei Verwendung des DLR können Sie stattdessen den folgenden Code für denselben Vorgang verwenden.
scriptobj.Count += 1;
MSDN listet diese Vorteile auf:
- Vereinfacht das Portieren dynamischer Sprachen in .NET Framework
- Aktiviert dynamische Funktionen in statisch typisierten Sprachen
- Bietet zukünftige Vorteile von DLR und .NET Framework
- Ermöglicht die gemeinsame Nutzung von Bibliotheken und Objekten
- Bietet schnellen dynamischen Versand und Aufruf
Weitere Informationen finden Sie unter MSDN .
Ein Anwendungsbeispiel:
Sie verwenden viele Klassen mit der kommunalen Eigenschaft 'CreationDate':
public class Contact
{
// some properties
public DateTime CreationDate { get; set; }
}
public class Company
{
// some properties
public DateTime CreationDate { get; set; }
}
public class Opportunity
{
// some properties
public DateTime CreationDate { get; set; }
}
Wenn Sie eine Kommunikationsmethode schreiben, die den Wert der Eigenschaft 'CreationDate' abruft, müssen Sie Reflection verwenden:
static DateTime RetrieveValueOfCreationDate(Object item)
{
return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
}
Mit dem "dynamischen" Konzept ist Ihr Code viel eleganter:
static DateTime RetrieveValueOfCreationDate(dynamic item)
{
return item.CreationDate;
}
Es wird hauptsächlich von RAD- und Python-Opfern verwendet, um die Codequalität, IntelliSense und die Fehlererkennung bei der Kompilierung zu zerstören .
Es wird zur Laufzeit ausgewertet, sodass Sie den Typ wie in JavaScript nach Belieben ändern können. Das ist echt:
dynamic i = 12;
i = "text";
Und so können Sie den Typ nach Bedarf ändern. Verwenden Sie es als letzten Ausweg; Es ist von Vorteil, aber ich habe gehört, dass in Bezug auf die generierte IL unter den Kulissen viel passiert, und das kann zu einem Leistungspreis führen.
Der beste Anwendungsfall für Variablen vom Typ 'dynamisch' war für mich, als ich kürzlich eine Datenzugriffsschicht in ADO.NET ( mit SQLDataReader ) schrieb und der Code die bereits geschriebenen gespeicherten Legacy-Prozeduren aufrief . Es gibt Hunderte dieser alten gespeicherten Prozeduren, die einen Großteil der Geschäftslogik enthalten. Meine Datenzugriffsschicht musste eine Art strukturierter Daten an die C # -basierte Geschäftslogikschicht zurückgeben, um einige Manipulationen durchzuführen ( obwohl es fast keine gibt ). Jede gespeicherte Prozedur gibt einen anderen Datensatz zurück ( Tabellenspalten ). Anstatt Dutzende von Klassen oder Strukturen zu erstellen, um die zurückgegebenen Daten zu speichern und an die BLL zu übergeben, habe ich den folgenden Code geschrieben, der recht elegant und ordentlich aussieht.
public static dynamic GetSomeData(ParameterDTO dto)
{
dynamic result = null;
string SPName = "a_legacy_stored_procedure";
using (SqlConnection connection = new SqlConnection(DataConnection.ConnectionString))
{
SqlCommand command = new SqlCommand(SPName, connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("@empid", dto.EmpID));
command.Parameters.Add(new SqlParameter("@deptid", dto.DeptID));
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
dynamic row = new ExpandoObject();
row.EmpName = reader["EmpFullName"].ToString();
row.DeptName = reader["DeptName"].ToString();
row.AnotherColumn = reader["AnotherColumn"].ToString();
result = row;
}
}
}
return result;
}
dynamic np = Py.Import("numpy")
dynamic
wenn Sie numerische Operatoren auf sie anwenden. Dies bietet Typensicherheit und vermeidet Einschränkungen von Generika. Dies ist im Wesentlichen * Ententypisierung:T y = x * (dynamic)x
, wo typeof(x) is T
Ein weiterer Anwendungsfall für die dynamic
Eingabe sind virtuelle Methoden, bei denen ein Problem mit Kovarianz oder Kontravarianz auftritt. Ein solches Beispiel ist die berüchtigte Clone
Methode, die ein Objekt des gleichen Typs wie das Objekt zurückgibt, für das es aufgerufen wird. Dieses Problem wird mit einer dynamischen Rückgabe nicht vollständig gelöst, da die statische Typprüfung umgangen wird. Zumindest müssen Sie bei der Verwendung von Plain nicht immer hässliche Casts verwenden object
. Ansonsten werden die Casts implizit.
public class A
{
// attributes and constructor here
public virtual dynamic Clone()
{
var clone = new A();
// Do more cloning stuff here
return clone;
}
}
public class B : A
{
// more attributes and constructor here
public override dynamic Clone()
{
var clone = new B();
// Do more cloning stuff here
return clone;
}
}
public class Program
{
public static void Main()
{
A a = new A().Clone(); // No cast needed here
B b = new B().Clone(); // and here
// do more stuff with a and b
}
}