Ostrzeżenie: To pytanie jest trochę heretyckie ... religijni programiści zawsze przestrzegają dobrych praktyk, proszę go nie czytać. :)
Czy ktoś wie, dlaczego korzystanie z TypedReference jest tak odradzane (domyślnie z powodu braku dokumentacji)?
Znalazłem dla niego świetne zastosowania, na przykład podczas przekazywania parametrów ogólnych przez funkcje, które nie powinny być ogólne (gdy użycie object
może być przesadne lub wolne, jeśli potrzebujesz typu wartości), gdy potrzebujesz nieprzezroczystego wskaźnika lub gdy potrzebujesz szybko uzyskać dostęp do elementu tablicy, którego specyfikacje znajdziesz w czasie wykonywania (używając Array.InternalGetReference
). Skoro CLR nie pozwala nawet na nieprawidłowe użycie tego typu, dlaczego jest odradzane? To nie wydaje się być niebezpieczne ani nic ...
Inne zastosowania, które znalazłem TypedReference
:
„Specjalizacja” typów ogólnych w C # (to jest bezpieczne dla typów):
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); }
}
Pisanie kodu, który działa z ogólnymi wskaźnikami (jest to bardzo niebezpieczne, jeśli zostanie niewłaściwie użyte, ale szybkie i bezpieczne, jeśli zostanie użyte poprawnie):
//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);
}
Pisanie wersji instrukcji metodysizeof
, która może być czasami przydatna:
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)); }
}
}
Pisanie metody, która przekazuje parametr „stan”, który chce uniknąć pakowania:
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*/ }
}
Dlaczego więc takie zastosowania są „odradzane” (z powodu braku dokumentacji)? Jakieś szczególne względy bezpieczeństwa? Wydaje się całkowicie bezpieczne i sprawdzalne, jeśli nie jest pomieszane ze wskaźnikami (które i tak nie są bezpieczne ani weryfikowalne) ...
Aktualizacja:
Przykładowy kod, aby pokazać, że rzeczywiście TypedReference
może być dwukrotnie szybszy (lub więcej):
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
}
}
(Edycja: edytowałem powyższy test porównawczy, ponieważ ostatnia wersja postu używała wersji kodu do debugowania [zapomniałem go zmienić do wydania] i nie naciskałem na GC. Ta wersja jest nieco bardziej realistyczna i w moim systemie jest TypedReference
średnio ponad trzy razy szybszy ).
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. Bez względu na to, co próbuję (w tym różne sposoby mierzenia czasu), pakowanie / rozpakowywanie jest nadal szybsze w moim systemie.