Предупреждение: этот вопрос немного еретический... религиозные программисты всегда соблюдают хорошие практики, пожалуйста, не читайте его.:)
Кто-нибудь знает, почему использование TypedReference настолько обескуражено (неявно, из-за отсутствия документации)?
Я нашел для него большое применение, например, при передаче общих параметров с помощью функций, которые не должны быть общими (при использовании object
может быть чрезмерным или медленным, если вам нужен тип значения), поскольку, когда вы нужен непрозрачный указатель или когда вам нужно быстро получить доступ к элементу массива, спецификации которого вы найдете во время выполнения (используя Array.InternalGetReference
). Поскольку CLR даже не допускает неправильного использования этого типа, почему это не рекомендуется? Кажется, это небезопасно или что-то еще...
Другие использования, которые я нашел для TypedReference
:
"Специализируется" в С# (это безопасно для типов):
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); }
}
Написание кода, который работает с универсальными указателями (это очень опасно, если неправильно использовать, но быстро и безопасно, если оно используется правильно):
//This bypasses the restriction that you can't have a pointer to T,
//letting you write very high-performance generic code.
//It 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 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);
}
Написание версии метода инструкции sizeof
, которая может быть иногда полезной:
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)); }
}
}
Написание метода, который передает параметр состояния, который хочет избежать бокса:
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*/ }
}
Так почему же использование таких "обескураженных" (из-за отсутствия документации)? Какие-либо конкретные соображения безопасности? Это кажется совершенно безопасным и проверяемым, если оно не смешивается с указателями (которые в любом случае не являются безопасными или проверяемыми)...
Update:
Пример кода, чтобы показать, что, действительно, TypedReference
может быть в два раза быстрее (или больше):
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
}
}
(Edit: я отредактировал вышеприведенный тест, поскольку в последней версии сообщения использовалась отладочная версия кода [я забыл изменить его для выпуска] и не нажимал на GC. Эта версия немного больше реалистично, а в моей системе он более чем в три раза быстрее с TypedReference
в среднем.)