Warnung: Diese Frage ist etwas ketzerisch ... religiöse Programmierer halten sich immer an gute Praktiken, bitte lesen Sie sie nicht. :) :)
Weiß jemand, warum von der Verwendung von TypedReference so abgeraten wird (implizit aufgrund fehlender Dokumentation)?
Ich habe großartige Verwendungsmöglichkeiten dafür gefunden, z. B. wenn generische Parameter über Funktionen übergeben werden, die nicht generisch sein sollten (wenn Sie einen verwenden, der object
möglicherweise übertrieben oder langsam ist, wenn Sie einen Werttyp benötigen), wenn Sie einen undurchsichtigen Zeiger benötigen, oder für den Fall, dass Sie schnell auf ein Element eines Arrays zugreifen müssen, dessen Spezifikationen Sie zur Laufzeit finden (mit Array.InternalGetReference
). Warum wird davon abgeraten, da die CLR nicht einmal eine falsche Verwendung dieses Typs zulässt? Es scheint nicht unsicher zu sein oder so ...
Andere Verwendungen, die ich gefunden habe für TypedReference
:
"Spezialisierung" von Generika in C # (dies ist typsicher):
static void foo<T>(ref T value)
{
//This is the ONLY way to treat value as int, without boxing/unboxing objects
if (value is int)
{ __refvalue(__makeref(value), int) = 1; }
else { value = default(T); }
}
Schreiben von Code, der mit generischen Zeigern funktioniert (dies ist sehr unsicher, wenn es missbraucht wird, aber schnell und sicher, wenn es richtig verwendet wird):
//This bypasses the restriction that you can't have a pointer to T,
//letting you write very high-performance generic code.
//It's dangerous if you don't know what you're doing, but very worth if you do.
static T Read<T>(IntPtr address)
{
var obj = default(T);
var tr = __makeref(obj);
//This is equivalent to shooting yourself in the foot
//but it's the only high-perf solution in some cases
//it sets the first field of the TypedReference (which is a pointer)
//to the address you give it, then it dereferences the value.
//Better be 10000% sure that your type T is unmanaged/blittable...
unsafe { *(IntPtr*)(&tr) = address; }
return __refvalue(tr, T);
}
Schreiben einer Methodenversion der sizeof
Anweisung, die gelegentlich nützlich sein kann:
static class ArrayOfTwoElements<T> { static readonly Value = new T[2]; }
static uint SizeOf<T>()
{
unsafe
{
TypedReference
elem1 = __makeref(ArrayOfTwoElements<T>.Value[0] ),
elem2 = __makeref(ArrayOfTwoElements<T>.Value[1] );
unsafe
{ return (uint)((byte*)*(IntPtr*)(&elem2) - (byte*)*(IntPtr*)(&elem1)); }
}
}
Schreiben einer Methode, die einen "state" -Parameter übergibt, der das Boxen vermeiden möchte:
static void call(Action<int, TypedReference> action, TypedReference state)
{
//Note: I could've said "object" instead of "TypedReference",
//but if I had, then the user would've had to box any value types
try
{
action(0, state);
}
finally { /*Do any cleanup needed*/ }
}
Warum werden solche Verwendungen "entmutigt" (mangels Dokumentation)? Irgendwelche besonderen Sicherheitsgründe? Es scheint vollkommen sicher und überprüfbar zu sein, wenn es nicht mit Zeigern gemischt ist (die sowieso nicht sicher oder überprüfbar sind) ...
Aktualisieren:
Beispielcode, der zeigt, dass er tatsächlich TypedReference
doppelt so schnell (oder mehr) sein kann:
using System;
using System.Collections.Generic;
static class Program
{
static void Set1<T>(T[] a, int i, int v)
{ __refvalue(__makeref(a[i]), int) = v; }
static void Set2<T>(T[] a, int i, int v)
{ a[i] = (T)(object)v; }
static void Main(string[] args)
{
var root = new List<object>();
var rand = new Random();
for (int i = 0; i < 1024; i++)
{ root.Add(new byte[rand.Next(1024 * 64)]); }
//The above code is to put just a bit of pressure on the GC
var arr = new int[5];
int start;
const int COUNT = 40000000;
start = Environment.TickCount;
for (int i = 0; i < COUNT; i++)
{ Set1(arr, 0, i); }
Console.WriteLine("Using TypedReference: {0} ticks",
Environment.TickCount - start);
start = Environment.TickCount;
for (int i = 0; i < COUNT; i++)
{ Set2(arr, 0, i); }
Console.WriteLine("Using boxing/unboxing: {0} ticks",
Environment.TickCount - start);
//Output Using TypedReference: 156 ticks
//Output Using boxing/unboxing: 484 ticks
}
}
(Bearbeiten: Ich habe den obigen Benchmark bearbeitet, da in der letzten Version des Beitrags eine Debug-Version des Codes verwendet wurde [ich habe vergessen, ihn in Release zu ändern] und keinen Druck auf den GC ausgeübt hat. Diese Version ist etwas realistischer und auf meinem System ist es TypedReference
im Durchschnitt mehr als dreimal schneller .)
quelle
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. Egal was ich versuche (einschließlich verschiedener Möglichkeiten, das Timing durchzuführen), das Boxen / Unboxen ist auf meinem System immer noch schneller.int
->DockStyle
) umwandeln . Diese Box ist echt und fast zehnmal langsamer.Antworten:
Kurze Antwort: Portabilität .
Während
__arglist
,__makeref
und__refvalue
sind Spracherweiterungen und sind in der C # Language Specification ohne Papiere, die verwendeten Konstrukte sie unter der Haube zu implementieren (vararg
Aufrufkonvention,TypedReference
Art,arglist
,refanytype
,mkanyref
, undrefanyval
Anweisungen) sind perfekt in der dokumentierten CLI - Spezifikation (ECMA-335) in die Vararg Bibliothek .Die Definition in der Vararg-Bibliothek macht deutlich, dass sie in erster Linie Argumentlisten mit variabler Länge unterstützen sollen und nicht viel mehr. Listen mit variablen Argumenten sind auf Plattformen, die keine Schnittstelle zu externem C-Code mit varargs verwenden müssen, wenig nützlich. Aus diesem Grund ist die Varargs-Bibliothek nicht Teil eines CLI-Profils. In legitimen CLI-Implementierungen wird die Varargs-Bibliothek möglicherweise nicht unterstützt, da sie nicht im CLI-Kernel-Profil enthalten ist:
Update (Antwort auf
GetValueDirect
Kommentar):FieldInfo.GetValueDirect
sindFieldInfo.SetValueDirect
sind nicht von Basisklassenbibliothek Teil. Beachten Sie, dass es einen Unterschied zwischen der .NET Framework-Klassenbibliothek und der Basisklassenbibliothek gibt. BCL ist das einzige, was für eine konforme Implementierung der CLI / C # erforderlich ist, und ist in ECMA TR / 84 dokumentiert . (Tatsächlich ist esFieldInfo
selbst Teil der Reflection-Bibliothek und auch nicht im CLI-Kernel-Profil enthalten.)Sobald Sie eine Methode außerhalb von BCL verwenden, geben Sie ein wenig Portabilität auf (und dies wird mit dem Aufkommen von Nicht-.NET-CLI-Implementierungen wie Silverlight und MonoTouch immer wichtiger). Selbst wenn eine Implementierung die Kompatibilität mit der Microsoft .NET Framework-Klassenbibliothek verbessern wollte, konnte sie einfach eine bereitstellen
GetValueDirect
undSetValueDirect
ausführen,TypedReference
ohne dassTypedReference
die Laufzeit speziell dafür behandelt wird (im Grunde genommen sind sie ihrenobject
Gegenstücken ohne Leistungsvorteil gleichwertig ).Hätten sie es in C # dokumentiert, hätte es zumindest ein paar Auswirkungen gehabt:
quelle
FieldInfo.GetValueDirect
undFieldInfo.SetValueDirect
? Sie sind Teil der BCL, und um sie zu verwenden, müssen Sie sie verwenden. MussTypedReference
das nicht grundsätzlichTypedReference
immer definiert werden, unabhängig von der Sprachspezifikation? (Noch ein Hinweis: Auch wenn die Schlüsselwörter nicht vorhanden waren, konnten Sie, solange die Anweisungen vorhanden waren, durch dynamisches Ausgeben von Methoden darauf zugreifen. Solange Ihre Plattform mit C-Bibliotheken zusammenarbeitet, können Sie diese verwenden. ob C # die Schlüsselwörter hat oder nicht.)TypedReference
nur eine Sprache dokumentiert wäre - beispielsweise verwaltetes C ++ -, aber wenn keine Sprache dies dokumentiert und wenn niemand es wirklich verwenden kann, warum sollte man sich dann überhaupt die Mühe machen, die Funktion zu definieren?)[DllImport("...")] void Foo(__arglist);
) und sie haben sie in C # für ihren eigenen Gebrauch implementiert. Das Design der CLI wird von vielen Sprachen beeinflusst (Anmerkungen "The Common Language Infrastructure Annotated Standard" belegen diese Tatsache). Eine geeignete Laufzeit für so viele Sprachen wie möglich, einschließlich unvorhergesehener, zu sein, war definitiv ein Designziel (daher das name) und dies ist eine Funktion, von der beispielsweise eine hypothetisch verwaltete C-Implementierung wahrscheinlich profitieren könnte.Nun, ich bin kein Eric Lippert, daher kann ich nicht direkt über die Motivationen von Microsoft sprechen, aber wenn ich eine Vermutung wagen würde, würde ich sagen, dass
TypedReference
et al. sind nicht gut dokumentiert, weil Sie sie ehrlich gesagt nicht brauchen.Jede Verwendung, die Sie für diese Funktionen erwähnt haben, kann ohne sie ausgeführt werden, wenn auch in einigen Fällen mit einem Leistungsverlust. C # (und .NET im Allgemeinen) ist jedoch nicht als Hochleistungssprache konzipiert. (Ich vermute, dass "schneller als Java" das Leistungsziel war.)
Das heißt nicht, dass bestimmte Leistungsaspekte nicht berücksichtigt wurden. In der Tat existieren Funktionen wie Zeiger
stackalloc
und bestimmte optimierte Framework-Funktionen weitgehend, um die Leistung in bestimmten Situationen zu steigern.Generics, die ich würde sagen , die haben primäre Nutzen der Typsicherheit, verbessert auch die Leistung ähnlich wie
TypedReference
durch Box und Unboxing zu vermeiden. Tatsächlich habe ich mich gefragt, warum Sie dies bevorzugen würden:dazu:
Die Kompromisse sind meines Erachtens, dass erstere weniger JITs (und folglich weniger Speicher) benötigen, während letztere vertrauter und, wie ich annehmen würde, etwas schneller sind (durch Vermeidung von Zeiger-Dereferenzierungen).
Ich würde anrufen
TypedReference
und Freunde Implementierungsdetails. Sie haben auf einige nützliche Verwendungszwecke hingewiesen, und ich denke, sie sind es wert, untersucht zu werden, aber die übliche Einschränkung, sich auf Implementierungsdetails zu verlassen, gilt - die nächste Version kann Ihren Code beschädigen.quelle
call()
: Es liegt daran, dass der Code nicht immer so zusammenhängend ist - ich bezog mich eher auf ein Beispiel wie das vonIAsyncResult.State
, bei dem die Einführung von Generika einfach nicht durchführbar wäre, weil plötzlich Generika für eingeführt würden jede Klasse / Methode beteiligt. +1 für die Antwort ... vor allem, um auf den Teil "schneller als Java" hinzuweisen. :]TypedReference
wird wahrscheinlich bald keine bahnbrechenden Änderungen mehr geben, da FieldInfo.SetValueDirect , das öffentlich ist und wahrscheinlich von einigen Entwicklern verwendet wird, davon abhängt. :)TypedReference
beiden anrufen . (Die grausame Syntax und die allgemeine Unhandlichkeit disqualifizieren es meiner Meinung nach von der Kategorie "schön zu haben".) Ich würde sagen, es ist nur eine gute Sache, wenn man hier und da wirklich ein paar Mikrosekunden kürzen muss. Trotzdem denke ich an einige Stellen in meinem eigenen Code, die ich mir jetzt ansehen werde, um zu sehen, ob ich sie mit den von Ihnen genannten Techniken optimieren kann.TypedReference
s vermeiden , aber IIRC, der einzige Ort, an dem ich das Boxen irgendwo vermeiden konnte, waren die Elemente eindimensionaler Anordnungen von Grundelementen. Der leichte Geschwindigkeitsvorteil hier war die Komplexität, die er dem gesamten Projekt hinzufügte, nicht wert, also habe ich ihn herausgenommen.delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
einer gegebenen TypensammlungT
könnte eine MethodeActOnItem<TParam>(int index, ActByRef<T,TParam> proc, ref TParam param)
bereitgestellt werden , aber der JITter müsste für jeden Werttyp eine andere Version der Methode erstellenTParam
. Die Verwendung einer typisierten Referenz würde es einer JITted-Version der Methode ermöglichen, mit allen Parametertypen zu arbeiten.Ich kann nicht herausfinden, ob der Titel dieser Frage sarkastisch sein soll: Es ist seit langem bekannt, dass
TypedReference
es sich um den langsamen, aufgeblähten, hässlichen Cousin von "echten" verwalteten Zeigern handelt. Letzteres ist das, was wir mit C ++ / CLI erhalteninterior_ptr<T>
, oder sogar traditionelle Referenzparameter (ref
/out
) in C # . Tatsächlich ist es ziemlich schwierig, dieTypedReference
Basisleistung zu erreichen , wenn nur eine Ganzzahl verwendet wird, um das ursprüngliche CLR-Array jedes Mal neu zu indizieren.Die traurigen Details sind hier , aber zum Glück ist nichts davon jetzt wichtig ...
Diese neuen Sprachfunktionen bieten in C # eine erstklassige Unterstützung für das Deklarieren, Freigeben und Bearbeiten von echten
CLR
verwalteten Referenztyp- Typen in sorgfältig vorgegebenen Situationen.Die Nutzungsbeschränkungen sind nicht strenger als das, was bisher erforderlich für
TypedReference
(und die Leistung ist buchstäblich einen Sprung von Schlimmste am besten ), so sehe ich keinen verbleibenden denkbar Anwendungsfall in C # fürTypedReference
. Bisher gab es beispielsweise keine Möglichkeit, einTypedReference
imGC
Heap beizubehalten, sodass das Gleiche für die überlegenen verwalteten Zeiger jetzt kein Take-Away mehr ist.Und natürlich bedeutet der Niedergang von
TypedReference
- oder zumindest seine fast vollständige Abwertung - auch,__makeref
den Müll zu werfen .quelle