Предупреждение: этот вопрос немного еретический ... религиозные программисты всегда придерживаются хороших практик, пожалуйста, не читайте его. :)
Кто-нибудь знает, почему использование TypedReference так не рекомендуется (неявно, из-за отсутствия документации)?
Я нашел для него отличные применения, например, при передаче общих параметров через функции, которые не должны быть универсальными (при использовании object
может быть излишним или медленным, если вам нужен тип значения), когда вам нужен непрозрачный указатель или когда вам нужно быстро получить доступ к элементу массива, спецификации которого вы найдете во время выполнения (используя Array.InternalGetReference
). Поскольку среда CLR даже не допускает неправильного использования этого типа, почему это не рекомендуется? Кажется, это небезопасно или что-то в этом роде ...
Я нашел другое применение TypedReference
:
"Специализированные" дженерики в C # (это типобезопасно):
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'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);
}
Написание методической версии 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*/ }
}
Так почему же такое использование «не рекомендуется» (из-за отсутствия документации)? Какие-то особые причины безопасности? Это кажется совершенно безопасным и поддающимся проверке, если оно не смешано с указателями (которые в любом случае небезопасны или проверены) ...
Обновить:
Пример кода, показывающий, что действительно 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
}
}
(Изменить: я отредактировал тест выше, так как последняя версия сообщения использовала отладочную версию кода [я забыл изменить ее для выпуска] и не оказывал давления на сборщик мусора. Эта версия немного более реалистична и в моей системе это в TypedReference
среднем более чем в три раза быстрее .)
источник
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. Независимо от того, что я пытаюсь (включая разные способы определения времени), упаковка / распаковка все еще быстрее в моей системе.int
->DockStyle
). Это коробки по-настоящему, и работает почти в десять раз медленнее.Ответы:
Краткий ответ: портативность .
Хотя
__arglist
,__makeref
и__refvalue
являются расширениями языка и недокументированы в Спецификации языка C #, конструкции, используемые для их реализации под капотом (vararg
соглашение о вызове,TypedReference
тип,arglist
,refanytype
,mkanyref
, иrefanyval
инструкция) прекрасно документированы в спецификации CLI (ECMA-335) в библиотека Vararg .Поскольку они определены в библиотеке Vararg, совершенно ясно, что они в первую очередь предназначены для поддержки списков аргументов переменной длины и не более того. Списки переменных-аргументов мало используются на платформах, которым не нужно взаимодействовать с внешним кодом C, использующим varargs. По этой причине библиотека Varargs не является частью какого-либо профиля CLI. Законные реализации CLI могут не поддерживать библиотеку Varargs, поскольку она не включена в профиль ядра CLI:
Обновление (ответ на
GetValueDirect
комментарий):FieldInfo.GetValueDirect
являютсяFieldInfo.SetValueDirect
являются не частью библиотеки базовых классов. Обратите внимание, что есть разница между библиотекой классов .NET Framework и библиотекой базовых классов. BCL - единственное, что требуется для соответствующей реализации CLI / C # и задокументировано в ECMA TR / 84 . (Фактически, онFieldInfo
сам является частью библиотеки Reflection и также не включен в профиль ядра CLI).Как только вы используете метод вне BCL, вы немного отказываетесь от переносимости (и это становится все более важным с появлением реализаций CLI, отличных от .NET, таких как Silverlight и MonoTouch). Даже если реализация хотела бы повысить совместимость с библиотекой классов Microsoft .NET Framework, она могла бы просто предоставить
GetValueDirect
иSetValueDirect
принять,TypedReference
не делаяTypedReference
специально обрабатываемые средой выполнения (в основном, делая их эквивалентными своимobject
аналогам без повышения производительности).Если бы они задокументировали это на C #, это имело бы как минимум пару последствий:
источник
FieldInfo.GetValueDirect
иFieldInfo.SetValueDirect
? Они являются частью BCL, и для их использования вам нужноTypedReference
, так что разве это не заставляетTypedReference
всегда быть определенным, независимо от спецификации языка? (Также еще одно примечание: даже если ключевые слова не существуют, пока существуют инструкции, вы все равно можете получить к ним доступ, динамически генерируя методы ... так что, пока ваша платформа взаимодействует с библиотеками C, вы можете использовать их, есть ли в C # ключевые слова.)TypedReference
был задокументирован только для одного языка - скажем, управляемого C ++ - но если ни один язык не документирует его, и поэтому, если никто действительно не может его использовать, тогда зачем вообще определять эту функцию?)[DllImport("...")] void Foo(__arglist);
), и они реализовали ее на C # для собственного использования. На дизайн интерфейса командной строки влияют многие языки (аннотации «Стандарт аннотированной инфраструктуры общего языка» демонстрируют этот факт). Быть подходящей средой выполнения для максимально возможного количества языков, включая непредвиденные, определенно было целью дизайна (отсюда и name), и это функция, от которой, вероятно, могла бы выиграть гипотетическая управляемая реализация C.Что ж, я не Эрик Липперт, поэтому я не могу прямо говорить о мотивах Microsoft, но если бы я рискнул предположить, я бы сказал, что
TypedReference
и др. плохо документированы, потому что, честно говоря, они вам не нужны.Каждое использование этих функций, которое вы упомянули, может быть выполнено без них, хотя в некоторых случаях это снижает производительность. Но C # (и .NET в целом) не предназначен для использования в качестве высокопроизводительного языка. (Я предполагаю, что целью производительности было «быстрее, чем Java».)
Это не означает, что не учитывались определенные соображения производительности. Действительно, такие функции, как указатели,
stackalloc
и некоторые оптимизированные функции фреймворка существуют в основном для повышения производительности в определенных ситуациях.Обобщения, основным преимуществом которых является безопасность типов, также улучшают производительность аналогично тому
TypedReference
, как они избегают упаковки и распаковки. На самом деле, мне было интересно, почему вы предпочли бы это:к этому:
На мой взгляд, компромиссы состоят в том, что для первого требуется меньше JIT (и, следовательно, меньше памяти), а для второго более привычно и, как я полагаю, немного быстрее (за счет избежания разыменования указателей).
Звоню
TypedReference
и друзьям детали реализации. Вы указали на некоторые изящные способы их использования, и я думаю, что их стоит изучить, но действует обычное предостережение - полагаться на детали реализации - следующая версия может нарушить ваш код.источник
call()
: Это потому, что код не всегда такой связный - я больше имел в виду пример, больше похожий на тотIAsyncResult.State
, где введение дженериков просто невозможно, потому что внезапно он представит дженерики для каждый задействованный класс / метод. +1 за ответ, хотя ... особенно за то, что указал на "быстрее, чем Java". :]TypedReference
вероятно, в ближайшее время не будут происходить критические изменения, учитывая, что от него зависит FieldInfo.SetValueDirect , который является общедоступным и, вероятно, используется некоторыми разработчиками. :)TypedReference
ни одному из них. (Ужасный синтаксис и общая громоздкость дисквалифицируют его, на мой взгляд, из категории приятных вещей.) Я бы сказал, что это просто хорошая вещь, когда вам действительно нужно сократить несколько микросекунд тут и там. Тем не менее, я думаю о паре мест в моем собственном коде, которые я собираюсь изучить прямо сейчас, чтобы увидеть, могу ли я их оптимизировать, используя указанные вами методы.TypedReference
s, но IIRC, единственное место, где я мог где-то избежать упаковки, было с элементами одномерных массивов примитивов. Небольшой выигрыш в скорости здесь не стоил той сложности, которую он добавил ко всему проекту, поэтому я отказался от него.delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
коллекция типовT
может предоставить методActOnItem<TParam>(int index, ActByRef<T,TParam> proc, ref TParam param)
, но JITter должен будет создать другую версию метода для каждого типа значенияTParam
. Использование типизированной ссылки позволит одной JITted-версии метода работать со всеми типами параметров.Я не могу понять, должен ли заголовок этого вопроса быть саркастическим: давно установлено, что
TypedReference
это медленный, раздутый и уродливый родственник «настоящих» управляемых указателей, причем последний - то, что мы получаем с C ++ / CLIinterior_ptr<T>
, или даже традиционные параметры по ссылке (ref
/out
) в C # . Фактически, довольно сложно добитьсяTypedReference
даже базовой производительности, просто используя целое число для повторной индексации исходного массива CLR каждый раз.Печальные подробности здесь , но, к счастью, сейчас все это не имеет значения ...
Эти новые языковые функции обеспечивают выдающуюся первоклассную поддержку в C # для объявления, совместного использования и управления настоящими
CLR
управляемыми ссылочными типами типов в тщательно продуманных ситуациях.Ограничения на использование не строже, чем то, что требовалось ранее
TypedReference
(а производительность буквально перескакивает от худшего к лучшему ), поэтому я не вижу оставшихся возможных вариантов использования в C # дляTypedReference
. Например, ранее не было возможности сохранить объектTypedReference
вGC
куче, поэтому то же самое, что и для вышестоящих управляемых указателей, теперь не является лишним.И, очевидно, прекращение существования
TypedReference
- или, по крайней мере, его почти полное прекращение - означает также выброс__makeref
на свалку.источник