При использовании текущего компилятора защитные копии действительно создаются как для «примитивных» типов значений, так и для других структур, не предназначенных только для чтения.В частности, они генерируются аналогично тому, как они используются для полей readonly
: при доступе к свойству или методу, которые потенциально могут изменять содержимое.Копии появляются на каждом сайте вызовов для потенциально мутирующего участника, поэтому, если вы вызовете n таких участников, в итоге вы получите n защитных копий.Как и в случае полей readonly
, вы можете избежать нескольких копий, вручную скопировав оригинал в локальную папку.
Взгляните на этот набор примеров .Можно просматривать как сборку IL, так и сборку JIT.
Безопасно ли передавать примитивные типы через аргументы и не делать защитных копий?
Зависит от того,Вы получаете доступ к методу или свойству в параметре in
.Если вы это сделаете, вы можете увидеть защитные копии.Если нет, вы, вероятно, не будете:
// Original:
int In(in int _) {
_.ToString();
_.GetHashCode();
return _ >= 0 ? _ + 42 : _ - 42;
}
// Decompiled:
int In([In] [IsReadOnly] ref int _) {
int num = _;
num.ToString(); // invoke on copy
num = _;
num.GetHashCode(); // invoke on second copy
if (_ < 0)
return _ - 42; // use original in arithmetic
return _ + 42;
}
Являются ли другие часто используемые структуры фреймворка, такие как DateTime, TimeSpan, Guid, ... считающимися [только для чтения компилятором]?
Нет, защитные копии по-прежнему будут создаваться на сайтах вызовов для потенциально мутирующих участников с параметрами in
этих типов.Интересно, однако, что не все методы и свойства считаются «потенциально мутирующими».Я заметил, что если я вызывал реализацию метода по умолчанию (например, ToString
или GetHashCode
), защитные копии не создавались.Однако, как только я переопределил эти методы, компилятор создал копии:
struct WithDefault {}
struct WithOverride { public override string ToString() => "RO"; }
// Original:
void In(in WithDefault d, in WithOverride o) {
d.ToString();
o.ToString();
}
// Decompiled:
private void In([In] [IsReadOnly] ref WithDefault d,
[In] [IsReadOnly] ref WithOverride o) {
d.ToString(); // invoke on original
WithOverride withOverride = o;
withOverride.ToString(); // invoke on copy
}
Если это зависит от платформы, как мы можем узнать, какие типы безопасны в данной ситуации?
Ну, все типы «безопасны» - копии гарантируют это.Я предполагаю, что вы спрашиваете, какие типы будут избегать защитной копии.Как мы видели выше, это сложнее, чем «какой тип параметра»?Единой копии не существует: копии генерируются при определенных ссылках на параметры in
, например, где ссылка является целью вызова.Если таких ссылок нет, копии делать не нужно.Более того, решение о копировании может зависеть от того, вызываете ли вы элемент, который, как известно, является безопасным или «чистым», по сравнению с элементом, который потенциально может изменить содержимое типа значения.
На данный момент определенное значение по умолчаниюметоды кажутся чистыми, и в этих случаях компилятор избегает делать копии.Если бы мне пришлось угадывать, это результат существовавшего ранее поведения, и компилятор использует некоторое понятие ссылок «только для чтения», которое изначально было разработано для полей readonly
.Как вы можете видеть ниже (или в SharpLab ), поведение аналогично.Обратите внимание, как IL использует ldflda
(поле загрузки по адресу ), чтобы поместить цель вызова в стек при вызове WithDefault.ToString
, но использует последовательность ldfld
, stloc
, ldloca
длядобавьте копию в стек при вызове WithOverride.ToString
:
struct WithDefault {}
struct WithOverride { public override string ToString() => "RO"; }
static readonly WithDefault D;
static readonly WithOverride O;
// Original:
static void Test() {
D.ToString();
O.ToString();
}
// IL Disassembly:
.method private hidebysig static void Test () cil managed {
.maxstack 1
.locals init ([0] valuetype Overrides/WithOverride)
// [WithDefault] Invoke on original by address:
IL_0000: ldsflda valuetype Overrides/WithDefault Overrides::D
IL_0005: constrained. Overrides/WithDefault
IL_000b: callvirt instance string [mscorlib]System.Object::ToString()
IL_0010: pop
// [WithOverride] Copy original to local, invoke on copy by address:
IL_0011: ldsfld valuetype Overrides/WithOverride Overrides::O
IL_0016: stloc.0
IL_0017: ldloca.s 0
IL_0019: constrained. Overrides/WithOverride
IL_001f: callvirt instance string [mscorlib]System.Object::ToString()
IL_0024: pop
IL_0025: ret
}
Тем не менее теперь, когда ссылки только для чтения станут более распространенными, «белый список» методов, которые могутбыть вызванным без защитные копии могут вырасти в будущем.Пока что это выглядит несколько произвольно.